3

I'm having an issue that is really difficult to find an already made solution for it.

I'm running a new python thread that is then blocked by a socket connection (python-socketio) because it is waiting indefinitely for data. But I need to close this thread after 5 minutes. I've tried to setup a timer that would close the thread with sys.exit() but I found that this was closing the timer thread itself.

Here is the code so far :

class LiveMatches(commands.Cog):
    def __init__(self, client):
        self.client = client 

    def connect_to_scorebot(self, id):
        feed = MatchFeed(self.client)
        feed.main(id) # when this function is called, 
# it will block the newly created thread to receive the data with the socket

    def create_thread(self, id):
        # we create the thread and call launch_match that will connect to
        # the scorebot
        new_thread = threading.Thread(target=self.connect_to_scorebot, args=(id,))
        # start the thread
        new_thread.start()
Darkonaut
  • 20,186
  • 7
  • 54
  • 65
Funeoz
  • 131
  • 1
  • 1
    You can set `new_thread.daemon = True` and use `new_thread.join(timeout_in_seconds)`. – Olvin Roght Aug 30 '20 at 10:41
  • 1
    https://stackoverflow.com/questions/323972/is-there-any-way-to-kill-a-thread – Cyril Jouve Aug 30 '20 at 10:43
  • So after that timeout, the thread will close even if its task can't finish ? – Funeoz Aug 30 '20 at 10:43
  • @Funeoz, yes, it will. – Olvin Roght Aug 30 '20 at 10:44
  • @OlvinRoght It seems to block the main thread so it's not really what I want – Funeoz Aug 30 '20 at 11:04
  • @Funeoz, it's next problem you need to solve. You can use search box in the top of page and easily find dozens of solutions in answers under similar questions. – Olvin Roght Aug 30 '20 at 11:12
  • 1
    Can't you specify a timeout when reading from the socket? – Pynchia Aug 30 '20 at 14:26
  • @Olvin That's not the way `join()` works. `join` waits for a thread to exit. If given a timeout and the thread doesn't exit, `join` returns but the thread is still running. Daemon threads are terminated if still running when no more non-daemon threads exist. – Mark Tolonen Aug 30 '20 at 21:46
  • @MarkTolonen, point me where you found my explanation so you decided to correct me. – Olvin Roght Aug 30 '20 at 22:37
  • @Olvin the first comment in this question. – Mark Tolonen Aug 30 '20 at 22:43
  • @MarkTolonen and it somehow conflicts with what you've said? – Olvin Roght Aug 30 '20 at 22:43
  • @Olvin then Funeoz said "after the timeout the thread will close even if its task can't finish" and you said yes. But the thread keeps running, it won't exit because the join timed out. If the main thread then exits, *then* the daemon thread will exit. but the `join` can be a `time.sleep` and get the same result in that case. – Mark Tolonen Aug 30 '20 at 22:44

1 Answers1

1

A couple of options:

  1. You can set a timeout on the the socket used in the thread so that it returns from blocking after a period of time.
  2. Use select.select() with a timeout to poll a socket for data, periodically checking if the thread should exit.

Example of #1:

import threading
import socket
import select

# A server that echos only the first data received from a client
def server():
    s = socket.socket()
    s.bind(('',5000))
    s.listen()
    print('server: running')
    while True:
        c,a = s.accept()
        print('server: client connected')
        with c: # closes client socket when with block exits
            echoed = False
            while True:
                data = c.recv(1024)
                if not data: break
                if not echoed:
                    print('server: responding',data)
                    c.sendall(data)
                    echoed = True
        print('server: client disconnected')

def client():
    s = socket.socket()
    s.connect(('localhost',5000))
    with s: # closes client socket when with block exits
        try:
            s.settimeout(5) # 5-second timeout if no data received.
            print('client: send one')
            s.sendall(b'one')
            print('client: got',s.recv(1024))
            print('client: send two')
            s.sendall(b'two')
            print('client: got',s.recv(1024))  # this will timeout
        except socket.timeout:
            print('client: timed out')

# Start server thread.
# As a daemon, it will exit if main thread and client thread both exit.
threading.Thread(target=server,daemon=True).start()

t = threading.Thread(target=client)
t.start()
t.join() # wait for client thread to exit.
t = threading.Thread(target=client)
t.start()
t.join() # wait for client thread to exit.

Output:

server: running
client: send one
server: client connected
server: responding b'one'
client: got b'one'
client: send two
client: timed out
server: client disconnected
client: send one
server: client connected
server: responding b'one'
client: got b'one'
client: send two
client: timed out

Note server didn't print that the second client disconnected. Since it is a daemon thread, it was terminated when the main thread and client thread both exited, and didn't have time to recognize the client disconnected after timeout. If you want cleaner exit behavior, don't use daemon threads.

Mark Tolonen
  • 166,664
  • 26
  • 169
  • 251