0

I have a python2 program which uses socket and SocketServer. it basically consists of some clients, that can connect with each other.Each Client also has their own Thread which is implemented using threading.Thread and run in daemon mode. Now my problem is that when i call sys.exit() the program doesn't exit.I also (try to) close all open sockets before sys.exit() call.I also use socket.makefile() in the program, however I call close on all of the opened files right after I read from them.What could be the potential causes if sys.exit() not exiting?

Here's the code, the application is supposed to represent a graph of nodes, each node will contain an Edge to another node if we call initiateConnection on the first node with the address of the second one.Since the Edges are undirected I want both sides of the socket to be handled by the same RequestHandler.See notes above Client.initiateConnection.

import socket
import SocketServer
import select
import sys
import threading

class Client(SocketServer.ThreadingTCPServer):

    """Attributes:
    neighbours: connections to neighbours
    n_addresses: addresses of Neighbours
    requests: Number of Requests Sent
    processed_requests:{}
    id: this client's id
    """

    def __init__(self, server_address, RequestHandlerClass, id):
        self.neighbours = []
        self.n_addresses = []
        self.requests = 0
        self.processed_requests = {}
        self.id = id
        SocketServer.ThreadingTCPServer.__init__(self, server_address, RequestHandlerClass)

    def initiateConnection(self, address):
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect(address)
        self.neighbours.append(s)
        self.n_addresses.append(s.getpeername())
        self.addConnection(s, address)
        # check if the given port, and the connected one match
        if address[1] != s.getpeername()[1]:
            raise Exception(address, s.getpeername())

    """ this method is based on the _handle_request_no_block method on the base server
    we will add the connection created by this node i.e: this node is the client for that
    particular connection, to the list of connections handled by this server, i.e: view it as a server connection
    """
    #This method is supposed to add the request first instantiated by
    # the Client instance itself, to the requests the ThreadingTCPServer manages (creates a seperate thread for it and etc.)
    # Maybe there's something else I have to do, so that the ThreadingTCPServer closes this connection properly?
    def addConnection(self, request, address):
        if self.verify_request(request, address):
            try:
                self.process_request(request, address)
            except:
                self.handle_error(request, address)
                self.shutdown_request(request)

    def addNeighbour(self, s):
        self.neighbours.append(s)
        self.n_addresses.append(s.getpeername())

    def server_close(self):
        print("{}: closing neighbours".format(self.id))
        for c in self.neighbours:
            print("{}: closing {}".format(c.getpeername()))
            c.close()
        print("{}: closed all neighbours".format(self.id))
        SocketServer.ThreadingTCPServer.server_close(self)


class RequestHandler(SocketServer.StreamRequestHandler):

    GET_NEIGHBOURS="1"
    EOR="EOR"
    # the below function means someone is trying to initiate
    # connection, since it will be handled in it's own thread
    # we will initiate a loop and wait for a request.
    def handle(self):
        self.server.addNeighbour(self.request)
        while True:
            lines = []
            select.select([self.rfile], [], [])
            print("{}: got request from {}".format(self.server.id, self.request.getpeername()))
            while True:
                line = self.rfile.readline().strip()
                print("line read:\n{}".format(line))
                if line == RequestHandler.EOR:
                    break
                elif not bool(line):
                    continue
                lines.append(line)
            print("{}: all request lines read:\n{}".format(self.server.id, lines))
            # we have a complete request now process it
            if lines[1] == self.GET_NEIGHBOURS:
                print("{}: request was of type    get_neighbours".format(self.server.id))
                data = self.processNeighbours(lines)
                self.request.sendall("{}\n".format(data))
                print("{}: data sent to {}".format(self.server.id, self.request.getpeername()))

class UnknownRequestCode(Exception): pass

if __name__ == '__main__':

    def output(s):
        print(s)
    def getLine():
        return sys.stdin.readline()

    address = ('', 0)
    clients = {}
    addresses = {}
    while True:
        print("commands:\nclose: exit\nnew: make a new client, will prompt for a client id\nshow: show a clients neighbours, will prompt for a client's id\nconnect: will connect a client to another one, will prompt for the ids")
        i = getLine().strip()
        if i == "close":
            for c in clients.values():
                c.shutdown()
            print("everything shut down")
            sys.exit()
        if i == "new":
            i = getLine(). strip()
            client = Client(address, RequestHandler, i)
            clients[i] = client
            a = client.server_address
            addresses[i] = a
            output(a)
            t = threading.Thread(target=client.serve_forever)
            t.daemon = True
            t.start()
        elif i == "show":
            i = getLine().strip()
            c = clients[i]
            o = c.startSearch()
            #output(o)       
        elif i == "connect":
            i = getLine().strip()
            c = clients[i]
            i = getLine().strip()
            a = addresses[i]
            print("{}".format(a))
            c.initiateConnection(a)

UPDATE: I have identified the (rather obvious) problem, the handle method never exits the loop since the nodes are always supposed to get connected, and wait for each other's request.But I can not check program termination since it gets stuck before the select.select call.How can I fix that?

user2268997
  • 1,263
  • 2
  • 14
  • 35
  • Do you have a try statement surrounding the call? You do realize that a system exit is basically an intentionally thrown error? – Malik Brahimi Mar 26 '15 at 11:20
  • @MalikBrahimi No there is not, and yes I do. – user2268997 Mar 26 '15 at 14:56
  • @PaulRooney I will post some code shortly the problem is when I start to connect two Nodes i.e: `Client.initiateConnection` if I don't call that, it will exit, even if multiple `Client`s are instantiated – user2268997 Mar 26 '15 at 14:58
  • @PaulRooney it's not actually the `initiateConnection` that's causing the problem, it's the request handler's handler method. – user2268997 Mar 26 '15 at 15:36

2 Answers2

0

a python program ends when all threads exit. i think your thread functions do not return which means the program will wait for them to return even if you call exit().

if that is the case (and i really think it is), then a simple solution is to have a global 'running' flag = True:

running = True

and inside each thread, you create a loop that checks this flag before running:

#thread function
def thread_function ():
    #loop checks the flag
    while running:
        pass    #write loop code here
    #if running turns to false, then you will be here
    pass    #write clean up code here

hope it helps

roh
  • 11
  • 3
  • I thought it would be, but the ones I'm creating cause no problem, and the others which are created by the SocketServer.ThreadingTCPServer are terminated when call `server.shutdown`. I've added some code. – user2268997 Mar 26 '15 at 14:55
  • Turns out you were right, please take a look at the `RequestHandlers`'s handle method and the note added at the end. – user2268997 Mar 26 '15 at 15:35
  • in the handle method, you are using select without timeout: `select.select([self.rfile], [], [])` this means "wait forever until the file is ready for reading". what happens if the file is never ready for reading? you function will block forever. you should use a timeout like this: `select.select([self.rfile], [], [], 0.5)` like this, your function will block for half a second then returns with empty lists unless the file is ready for reading. that way, you can process other things (like check flags) continuesly. I did not look at everything in the code. Not sure if other things are ok. – roh Mar 27 '15 at 07:03
0

Since the RequestHandler.handle runs in an infinite loop, and waits for request from the other side and vice versa, we must terminate it when the main process exits.The key was in SocketServer.ThreadingMixin.daemon_threads which must be set to true, so that the created threads that will eventually run the handle method will be terminated upon exit.

user2268997
  • 1,263
  • 2
  • 14
  • 35