I'm writing a library for testing of HTTP services. In my tests (I'm using Python 3.4) I use http.server as an example HTTP service. It's run in a separate process created with the same Python interpreter, somewhat like this:
subprocess.Popen([sys.executable, '-m', 'http.server', '9090'])
The problem
After the http.server process is killed, its port isn't usable (seems like it's still taken) from the parent Python process. Why is that?
More details
There is no problem with creating a new process that will use the same port as the last one, but I can't start listening on it from the parent process. The port also isn't shown as available by port_for.
If a non-Python process (i.e. Mountebank) is spawned in the same way, the port is properly freed.
Also, if I never call http.server over HTTP after it's started, then the port will still be available.
So what? Does Linux keep the resources (socket) taken by my child Python process attached to my parent one because it's created from the same executable? Or is it Python's doing?
EDIT 1
There's one thing missing from your test, that is actually my scenario: acquiring the socket from parent process. I've updated your test (below) and it shows that the socket still isn't free after the server stops.
import subprocess
import sys
import requests
import time
import signal
import os
import socket
for sig in [signal.SIGINT,signal.SIGTERM,signal.SIGKILL]:
#sp = subprocess.Popen([sys.executable, '-m', 'http.server', '9090'])
sp = subprocess.Popen([sys.executable, '-m', 'SimpleHTTPServer', '9090'])
time.sleep(1)
r = requests.get("http://localhost:9090/")
print(r.content)
os.kill(sp.pid,sig)
time.sleep(1)
# test if the socket is free
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('localhost', 9090))
print('socket is FREE')
except Exception:
print('socket is NOT FREE')
I've checked for Python 2 and 3. The situation is the same whether I use os.kill or Popen (as expected). About my code, you can check for port availability before this line. I have to warn that my code is only for Python 3.
EDIT 2
Attempting to connect to the socket with SO_REUSEADDR like Foon said actually works. But it doesn't answer my original question.
I still don't know why a port taken by a Python subprocess will remain taken for the parent process unless you use REUSEADDR. I've also checked spawning simple HTTP server from a different interpreter than the one running master process (master - Python3, slave - Python2) and using a different program - a simple Bottle service.
When I spawn Mountebank (NodeJS) or NetCat subprocesses their ports are available after their cleanup even without using REUSEADDR.
After reading this post I've got the idea that it may have something to do with http.server running on 0.0.0.0 and Mountebank running only on localhost, but that had no impact.
So the question remains. Is there a bug in Python? Or some strange connection even between separate interpreters? Or did I pick non-Python processes that handle cleanup really well and I could observer the same behavior even in some non-Python programs?