Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python socket accept blocks - prevents app from quitting

Tags:

python

sockets

I've written a very simple python class which waits for connections on a socket. The intention is to stick this class into an existing app and asyncronously send data to connecting clients.

The problem is that when waiting on an socket.accept(), I cannot end my application by pressing ctrl-c. Neither can I detect when my class goes out of scope and notify it to end.

Ideally the application below should quit after the time.sleep(4) expires. As you can see below, I tried using select, but this also prevents the app from responding to ctrl-c. If I could detect that the variable 'a' has gone out of scope in the main method, I could set the quitting flag (and reduce the timeout on select to make it responsive).

Any ideas?

thanks


import sys
import socket
import threading
import time
import select

class Server( threading.Thread ):
    def __init__( self, i_port ):
        threading.Thread.__init__( self )
        self.quitting = False
        self.serversocket = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
        self.serversocket.bind( (socket.gethostname(), i_port ) )
        self.serversocket.listen(5)
        self.start()

    def run( self ):
        # Wait for connection
        while not self.quitting:
            rr,rw,err = select.select( [self.serversocket],[],[], 20 )
            if rr:
                (clientsocket, address) = self.serversocket.accept()
                clientsocket.close()

def main():
    a = Server( 6543 )
    time.sleep(4)

if __name__=='__main__':
  main()

like image 418
Brian O'Kennedy Avatar asked Jul 18 '09 17:07

Brian O'Kennedy


2 Answers

Add self.setDaemon(True) to the __init__ before self.start().

(In Python 2.6 and later, self.daemon = True is preferred).

The key idea is explained here:

The entire Python program exits when no alive non-daemon threads are left.

So, you need to make "daemons" of those threads who should not keep the whole process alive just by being alive themselves. The main thread is always non-daemon, by the way.

like image 171
Alex Martelli Avatar answered Nov 13 '22 02:11

Alex Martelli


I don't recommend the setDaemon feature for normal shutdown. It's sloppy; instead of having a clean shutdown path for threads, it simply kills the thread with no chance for cleanup. It's good to set it, so your program doesn't get stuck if the main thread exits unexpectedly, but it's not a good normal shutdown path except for quick hacks.

import sys, os, socket, threading, time, select

class Server(threading.Thread):
    def __init__(self, i_port):
        threading.Thread.__init__(self)
        self.setDaemon(True)
        self.quitting = False
        self.serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.serversocket.bind((socket.gethostname(), i_port))
        self.serversocket.listen(5)
        self.start()

    def shutdown(self):
        if self.quitting:
            return

        self.quitting = True
        self.join()

    def run(self):
        # Wait for connection
        while not self.quitting:
            rr,rw,err = select.select([self.serversocket],[],[], 1)
            print rr
            if rr:
                (clientsocket, address) = self.serversocket.accept()
                clientsocket.close()

        print "shutting down"
        self.serversocket.close()

def main():
    a = Server(6543)
    try:
        time.sleep(4)
    finally:
        a.shutdown()

if __name__=='__main__':
  main()

Note that this will delay for up to a second after calling shutdown(), which is poor behavior. This is normally easy to fix: create a wakeup pipe() that you can write to, and include it in the select; but although this is very basic, I couldn't find any way to do this in Python. (os.pipe() returns file descriptors, not file objects that we can write to.) I havn't dig deeper, since it's tangental to the question.

like image 42
Glenn Maynard Avatar answered Nov 13 '22 01:11

Glenn Maynard