Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python: How to interrupt raw_input() in other thread

I am writing a simple client-server program in python. In the client program, I am creating two threads (using Python's threading module), one for receiving, one for sending. The receiving thread continuously receives strings from the server side; while the sending thread continuously listens to the user input (using raw_input()) and send it to the server side. The two threads communicate using a Queue (which is natively synchronized, LIKE!).

The basic logic is like following:

Receiving thread:

global queue = Queue.Queue(0)

def run(self):
    while 1:
        receive a string from the server side
        if the string is QUIT signal:
            sys.exit()
        else:
            put it into the global queue

Sending thread:

def run(self):
    while 1:
        str = raw_input()
        send str to the server side
        fetch an element from the global queue
        deal with the element

As you can see, in the receiving thread, I have a if condition to test whether the server has sent a "QUIT signal" to the client. If it has, then I want the whole program to stop.

The problem here is that for most of its time, the sending thread is blocked by "raw_input()" and waiting for the user input. When it is blocked, calling "sys.exit()" from the other thread (receiving thread) will not terminate the sending thread immediately. The sending thread has to wait for the user to type something and hit the enter button.

Could anybody inspire me how to get around with this? I do not mind using alternatives of "raw_input()". Actually I do not even mind changing the whole structure.

-------------EDIT-------------

I am running this on a linux machine, and my Python version is 2.7.5

like image 488
seemuch Avatar asked Sep 22 '14 18:09

seemuch


2 Answers

You could just make the sending thread daemonic:

send_thread = SendThread()  # Assuming this inherits from threading.Thread
send_thread.daemon = True  # This must be called before you call start()

The Python interpreter won't be blocked from exiting if the only threads left running are daemons. So, if the only thread left is send_thread, your program will exit, even if you're blocked on raw_input.

Note that this will terminate the sending thread abruptly, no matter what its doing. This could be dangerous if it accesses external resources that need to be cleaned up properly or shouldn't be interrupted (like writing to a file, for example). If you're doing anything like that, protect it with a threading.Lock, and only call sys.exit() from the receiving thread if you can acquire that same Lock.

like image 181
dano Avatar answered Sep 28 '22 07:09

dano


The short answer is you can't. input() like a lot of such input commands is blocking and it's blocking whether everything about the thread has been killed. You can sometimes call sys.exit() and get it to work depending on the OS, but it's not going to be consistent. Sometimes you can kill the program by deferring out to the local OS. But, then you're not going to be widely cross platform.

What you might want to consider if you have this is to funnel the functionality through the sockets. Because unlike input() we can do timeouts, and threads and kill things rather easily. It also gives you the ability to do multiple connections and maybe accept connections more broadly.

import socket
import time
from threading import Thread


def process(command, connection):
    print("Command Entered: %s" % command)
    # Any responses are written to connection.
    connection.send(bytes('>', 'utf-8'))


class ConsoleSocket:
    def __init__(self):
        self.keep_running_the_listening_thread = True
        self.data_buffer = ''
        Thread(target=self.tcp_listen_handle).start()

    def stop(self):
        self.keep_running_the_listening_thread = False

    def handle_tcp_connection_in_another_thread(self, connection, addr):
        def handle():
            while self.keep_running_the_listening_thread:
                try:
                    data_from_socket = connection.recv(1024)
                    if len(data_from_socket) != 0:
                        self.data_buffer += data_from_socket.decode('utf-8')
                    else:
                        break
                    while '\n' in self.data_buffer:
                        pos = self.data_buffer.find('\n')
                        command = self.data_buffer[0:pos].strip('\r')
                        self.data_buffer = self.data_buffer[pos + 1:]
                        process(command, connection)
                except socket.timeout:
                    continue
                except socket.error:
                    if connection is not None:
                        connection.close()
                    break
        Thread(target=handle).start()
        connection.send(bytes('>', 'utf-8'))

    def tcp_listen_handle(self, port=23, connects=5, timeout=2):
        """This is running in its own thread."""
        sock = socket.socket()
        sock.settimeout(timeout)
        sock.bind(('', port))
        sock.listen(connects)  # We accept more than one connection.
        while self.keep_running_the_listening_thread:
            connection = None
            try:
                connection, addr = sock.accept()
                address, port = addr
                if address != '127.0.0.1':  # Only permit localhost.
                    connection.close()
                    continue
                # makes a thread deals with that stuff. We only do listening.
                connection.settimeout(timeout)
                self.handle_tcp_connection_in_another_thread(connection, addr)
            except socket.timeout:
                pass
            except OSError:
                # Some other error.
                if connection is not None:
                    connection.close()
        sock.close()


c = ConsoleSocket()


def killsocket():
    time.sleep(20)
    c.stop()


Thread(target=killsocket).start()

This launches a listener thread for the connections set on port 23 (telnet), and you connect and it passes that connection off to another thread. And it starts a killsocket thread that disables the various threads and lets them die peacefully (for demonstration purposes). You cannot however connect localhost within this code, because you'd need input() to know what to send to the server, which recreates the problem.

like image 36
Tatarize Avatar answered Sep 28 '22 07:09

Tatarize