1

I wrote a multithreaded web-socket client class so that the user's (main) thread does not blocks on the run_forever() method call. The code seems to work fine, except in the end, when I am stopping the thread, it does not close the web-socket cleanly and my process does not exit. I have to do a kill -9 each time to get rid of it. I tried calling the thread's join() method to make sure the main thread waits for the the child to complete its execution, but that did not help.

The code looks like below. Can you please help me make the exit/stopping of the thread graceful?

import thread
import threading
import time
import websocket

class WebSocketClient(threading.Thread):

    def __init__(self, url):
        self.url = url
        threading.Thread.__init__(self)

    def run(self):

        # Running the run_forever() in a seperate thread.
        #websocket.enableTrace(True)
        self.ws = websocket.WebSocketApp(self.url,
                                         on_message = self.on_message,
                                         on_error = self.on_error,
                                         on_close = self.on_close)
        self.ws.on_open = self.on_open
        self.ws.run_forever()

    def send(self, data):

        # Wait till websocket is connected.
        while not self.ws.sock.connected:
            time.sleep(0.25)

        print 'Sending data...', data
        self.ws.send("Hello %s" % data)

    def stop(self):
        print 'Stopping the websocket...'
        self.ws.keep_running = False

    def on_message(self, ws, message):
        print 'Received data...', message

    def on_error(self, ws, error):
        print 'Received error...'
        print error

    def on_close(self, ws):
        print 'Closed the connection...'

    def on_open(self, ws):
        print 'Opened the connection...'

if __name__ == "__main__":

    wsCli = WebSocketClient("ws://localhost:8888/ws")
    wsCli.start()
    wsCli.send('Hello')
    time.sleep(.1)
    wsCli.send('World')
    time.sleep(1)
    wsCli.stop()
    #wsCli.join()
    print 'After closing client...'
martineau
  • 119,623
  • 25
  • 170
  • 301
AnilJ
  • 1,951
  • 2
  • 33
  • 60
  • 1
    Try setting `wsCli.daemon = True` before calling `wsCli.start()`. – martineau Nov 16 '16 at 04:59
  • Also, the question [_Threaded, non-blocking websocket client_](http://stackoverflow.com/questions/29145442/threaded-non-blocking-websocket-client) might be of value to you. – martineau Nov 16 '16 at 05:07
  • It worked. What is the effect of making it daemon? – AnilJ Nov 16 '16 at 05:50
  • When I call wsCli.join(), my client program does not exit and I have to do a kill -9 for the process. – AnilJ Nov 16 '16 at 06:41
  • See [this answer](http://stackoverflow.com/a/38805873/355230) for an explanation of the `daemon` attribute. It shouldn't be surprising that the program doesn't exit after you `join()` a thread set to run forever (regardless of its `daemon` attribute setting). – martineau Nov 16 '16 at 15:59
  • Coding quibble: The documentation on [Thread Objects](https://docs.python.org/2/library/threading.html#thread-objects) says "If the subclass overrides the constructor, it must make sure to invoke the base class constructor (`Thread.__init__()`) **before** doing anything else to the thread." [emphasis mine] – martineau Nov 16 '16 at 16:08
  • @martineau, I agree with your commnet, but please note that I calling the wsCli.stop(), where I am setting the flag self.ws.keep_running = False. This should break the run_forever() loop and hence the thread. The join method is called after I am stopping the thread. This should not cause the process to still run, correct? – AnilJ Nov 16 '16 at 18:53
  • I'm not familiar with the websocket-client module. Are you sure that just setting `self.ws.keep_running = False` makes the `self.ws.run_forever()` call in the `run()` method return? – martineau Nov 16 '16 at 19:19
  • Surprisingly, even if I do not set this flag, program exits normally. Not sure what is the effect of setting thread as daemon on the run_forever() method. I got the example of this flag from here: https://github.com/liris/websocket-client/blob/master/websocket/_app.py and here http://www.programcreek.com/python/example/87177/websocket.keep_running – AnilJ Nov 16 '16 at 20:55
  • 1
    I peeked at the `run_forever()` source code for `_app.py`. It contains a loop that normally doesn't return while the `WebSocket` it creates is connected. Setting the `keep_running` flag to `False` should make it break out of the loop, as will any exception occurring. Instead of setting the flag, I suggest you call `self.ws.close()` which does that _and_ cleanly closes the sock connection. Note that you should probably still set the `daemon` thread attribute to `True` as I suggested earlier, just in case something unexpected happens and your class's `close()` method doesn't get called. – martineau Nov 17 '16 at 23:34
  • Yes, I am doing both... setting flag to false as well as calling the ws.close(). – AnilJ Nov 17 '16 at 23:38
  • 1
    You don't need to set `self.ws.keep_running` to `False`, it's the first thing `self.ws.close()` does. Anyway, is your problem now solved? – martineau Nov 17 '16 at 23:44
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/128402/discussion-between-anilj-and-martineau). – AnilJ Nov 17 '16 at 23:46

1 Answers1

8

To summarize the solution discovered through our extended discussion in the comments (and give you a chance to accept and up-vote my answer. ;-)

To fix the problem you need to call self.ws.close() in the WebSocketClient class' stop() method instead of merely setting self.ws.keep_running = False. That way the web-socket will be cleanly closed.

I also suggested that you set the thread's daemon attribute to True so the thread will automatically be stopped when the main thread terminates for any reason — in case something completely unexpected happens.

martineau
  • 119,623
  • 25
  • 170
  • 301