4

I'm using python 2.5.2 or 2.7 which is a HTTP server (BaseHTTPServer) that launches various tasks. One of the processes is a long running process. What I would like to be able to do is to launch this process then close my HTTP server and restart.

The problem is that my server shuts down (closes all threads and the python.exe process goes out of the active task list shown by Windows, the launched process is still running, but netstat -ab shows that the sytem process has my port the HTTP server listens on in a LISTENING state and associated to the Process ID which used to be my HTTP server. That port is kept open until the launched process completes, which makes it impossible to restart my HTTP server.

Whether I kill the python process, or CTRL-C the window, the same behavior is exhibited. I've read a ton of documentation and everyone suggests using subprocess.Popen, but even using that seems to associate parts of the main process to the launched process.

I'm launching the utility as follows:

try:

    # NOTE: subprocess.Popen is hanging up the 8091 port until the utility finishes.
    #       This needs to be addressed, otherwise, I'll never be able to restart the
    #       client when the utility has been launched.

    listParams = [ 'C:/MyPath/My.exe', '-f', os.path.join ( sXMLDir, sXmlFile ) ]

    proc = subprocess.Popen ( listParams, cwd='C:/MyPath', creationflags=0x00000008 )
    iSts = 200
    sStatus = 'Utility was successfully launched.'

except:
    iSts = CMClasses.HTTPSTS_STARTSLEDGE_SYSTEM
    sStatus = 'An exception occurred launching utility: ' + str ( sys.exc_type ) + ":" + str ( sys.exc_value  ) + '.'

My HTTP server is implemented as follows which allows my main program to process a CTRL-C:

class LaunchHTTPServer ( Thread ):

    def __init__ ( self, sPort, CMRequestHandler ):
        Thread.__init__ ( self )
        self.notifyWindow     =  None
        self.Port             = sPort
        self.CMRequestHandler = CMRequestHandler
        self.bExecute         = True

    def run ( self ):
        server = stoppableHttpServer(('',self.Port), self.CMRequestHandler )
        server.serve_forever()
        server.socket.close()

    def getExecute ( self ):
        return ( self.bExecute )

    def endThread ( self ):
        pass


class stoppableHttpServer ( BaseHTTPServer.HTTPServer ):

    def serve_forever ( self ):
        self.stop = False
        while not self.stop:
            self.handle_request()


def main ( argv ):

    ...

    try:
        ....
        tLaunchHTTPServer = LaunchHTTPServer ( iCMClientPort, CMRequestHandler )
        tLaunchHTTPServer.start()
        ...

    except KeyboardInterrupt:
        logging.info ( 'main: Request to stop received' )

    # End the communication threads

    logging.info ( 'Requesting CMRequestHandler to close.' )
    conn = httplib.HTTPConnection ( "localhost:%d" % iCMClientPort )
    conn.request ( "QUIT", "/" )
    conn.getresponse()
    conn.close()

Here are the results from the netstat -ab (my python process is 3728, my port is 8091) before starting the utility:

Active Connections

Proto Local Address Foreign Address State PID

TCP vtxshm-po-0101:8091 vtxshm-po-0101:0 LISTENING 3728 [python.exe]

TCP vtxshm-po-0101:8091 vtxshm-po-0101:23193 TIME_WAIT 0 [FrameworkService.exe]

Here are the results from the netstat -ab after starting the utility and after hitting Control-C and having python stop. (note that the OS thinks that this port is still in a LISTENING state, assigned to PID 3728, but that process no longer exists in Task Manager and this is now owned by System and somehow related to snmp.exe (which we don't even use) ). These connections are understood as they are the requests from another server to start the utility.

Active Connections

Proto Local Address Foreign Address State PID

TCP vtxshm-po-0101:8091 vtxshm-po-0101:0 LISTENING 3728 [System]

TCP vtxshm-po-0101:8091 CH2ChaosMonkeyServer:2133 TIME_WAIT 0 TCP vtxshm-po-0101:8091 CH2ChaosMonkeyServer:2134 TIME_WAIT 0 TCP vtxshm-po-0101:8091 vtxshm-po-0101:23223 TIME_WAIT 0 [snmp.exe]

Has anyone successfully launched a process from python and completely had it run independently from the launching process? If so, could you please share the secret?

ebarr
  • 7,704
  • 1
  • 29
  • 40
  • Have a look at [setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)](http://stackoverflow.com/questions/10705680/how-can-i-restart-a-basehttpserver-instance) – Torxed May 27 '14 at 06:30

2 Answers2

2

I'm going to answer a slightly different question, because this is the closest similar problem I could find.

I was having issues with a program that listens on a port and executes scripts in subprocesses using subprocess.Popen(). If a script I was executing runs a long running job in the background (via &), and I killed the main program, the long running job would take over the main program's listening port.

Main program:

import BaseHTTPServer
from BaseHTTPServer import BaseHTTPRequestHandler
import subprocess

class RequestHandler(BaseHTTPRequestHandler):
   def do_GET(self):
        proc = subprocess.Popen('start_long_proc.sh', shell=True)
        stdout, stderr = proc.communicate(input)

        self.send_response(200)
        self.end_headers()
        self.wfile.write('request served')

httpd = BaseHTTPServer.HTTPServer(('', 8000), RequestHandler)
httpd.serve_forever()

When that httpd server receives a request, it runs 'start_long_proc.sh'. That script looks like this:

sleep 600&

start_long_proc.sh returns immediately and the request serving is finished. And now comes the problem:

If I kill the webserver while the sleep is still running, sleep will take over the listening port:

$ netstat -pant | grep 0.0.0.0:8000
tcp    0   0 0.0.0.0:8000    0.0.0.0:*   LISTEN      24809/sleep     

The solution here is to close all file descriptors other than 0, 1 and 2 (stdin, stdout, stderr) when calling the script. Popen() provides a flag 'close_fds' for this:

proc = subprocess.Popen('start_long_proc.sh', shell=True, close_fds=True)

Now the sleep no longer ties op the listening port:

$ netstat -pant | grep 0.0.0.0:8000 $

The reason this is happening is because child processes inherit open file descriptors from their parents. What threw me off is that that includes listening ports. I saw the 'close_fds' option before, but thought it also closed STDIN, STDOUT and STDERR. But it doesn't, so you can still communicate with the subprocess like normal.

Ferry Boender
  • 668
  • 1
  • 7
  • 14
1

So you define:

def run ( self ):
    server = stoppableHttpServer(('',self.Port), self.CMRequestHandler )
    server.serve_forever()
    server.socket.close()

Here no reference is kept in your instance to the variable server. This is a problem, as it means that it is impossible to change the self.stop flag in the class below:

class stoppableHttpServer ( BaseHTTPServer.HTTPServer ): 
    def serve_forever ( self ):
        self.stop = False
        while not self.stop:
            self.handle_request()

When you execute the server_forever method, it blocks its thread. As no reference is kept to its parent instance, you have no means of setting self.stop = True. In fact no attempt to do this in the code is made, so the socket is likely to hang. This is also a problem if handle_request is blocking (which it is if you do not set a timeout).

You should note that the default implementation of serve_forever can be stopped with a server.shutdown so checking the state of the self.stop flag is redundant.

I would recommend updating your LaunchHTTPServer class to something like:

# as the only part of the code that needs to run as a thread is the serve_forever method
# there is no need to have this class as a thread
class LaunchHTTPServer (object):
    def __init__ ( self, sPort, CMRequestHandler ):
        self.notifyWindow     = None
        self.Port             = sPort
        self.CMRequestHandler = CMRequestHandler
        self.bExecute         = True
        self.server           = None
        self.server_thread    = None 

    def start ( self ):
        # Here you can use the default HTTPServer implementation, as the server is already stoppable
        self.server = BaseHTTPServer.HTTPServer(('',self.Port), self.CMRequestHandler )
        self.server_thread = Thread(target=self.server.serve_forever)
        self.server_thread.start()

    def stop( self ):
        try:
            self.server.shutdown()
            self.server.socket.close()
            self.server_thread.join()
            self.server,self.server_thread = None,None
         except Exception as error:
            pass # catch and raise which ever errors you desire here 


    def getExecute ( self ):
        return ( self.bExecute )

    def endThread ( self ):
        pass

With the above setup, it is now possible for you to catch Ctrl-C interrupts from the keyboard and ensure that the stop method of the instance is called to cleanly close the sockets and exit.

ebarr
  • 7,704
  • 1
  • 29
  • 40
  • Though you have provided a cleaner teardown of the HTTP Server, the same behavior exists. On the call to stop(), there was no exception. Either my understanding of subprocess.Popen() is incorrect, there is a bug, or this just isn't possible. Thank you for your input. – user3678436 May 27 '14 at 13:26
  • Sorry it wasn't of more use. Perhaps you could look at setting up a Python daemon rather than launching the process and exiting. – ebarr May 27 '14 at 13:42
  • Sorry this took so long to respond. Modified the code to start a daemon thread and all it did was execute the same subprocess.Popen() and it worked great. – user3678436 Jun 04 '14 at 03:12
  • +1 for following up on this (a good new-user question is a refreshing thing). – ebarr Jun 04 '14 at 03:17