3

I've recently started learning Python (long time Java programmer here) and currently in the process of writing some simple server programs. The problem is, for a seemingly similar piece of code, the Java counterpart properly responds to the SIGINT signal (Ctrl+C) whereas the Python one doesn't. This is seen when a separate thread is used to spawn the server. Code is as follows:

// Java code

package pkg;

import java.io.*;
import java.net.*;

public class ServerTest {

    public static void main(final String[] args) throws Exception {
        final Thread t = new Server();
        t.start();
    }

}

class Server extends Thread {

    @Override
    public void run() {
        try {
            final ServerSocket sock = new ServerSocket(12345);
            while(true) {
                final Socket clientSock = sock.accept();
                clientSock.close();
            }
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

}

and Python code:

# Python code
import threading, sys, socket, signal

def startserver():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(('', 12345))
    s.listen(1)
    while True:
        csock, caddr = s.accept()
        csock.sendall('Get off my lawn kids...\n')
        csock.close()

if __name__ == '__main__':
    try:
        t = threading.Thread(target=startserver)
        t.start()
    except:
        sys.exit(1)

In both the above code snippets, I create a simple server which listens to TCP requests on the given port. When Ctrl+C is pressed in case of the Java code, the JVM exits whereas in the case of the Python code, all I get is a ^C at the shell. The only way I can stop the server is press Ctrl+Z and then manually kill the process.

So I come up with a plan; why not have a sighandler which listens for Ctrl+Z and quits the application? Cool, so I come up with:

import threading, sys, socket, signal

def startserver():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(('', 12345))
    s.listen(1)
    while True:
        csock, caddr = s.accept()
        csock.sendall('Get off my lawn kids...\n')
        csock.close()

def ctrlz_handler(*args, **kwargs):
    sys.exit(1)

if __name__ == '__main__':
    try:
        signal.signal(signal.SIGTSTP, ctrlz_handler)
        t = threading.Thread(target=startserver)
        t.start()
    except:
        sys.exit(1)

But now, it seems as I've made the situation even worse! Now pressing Ctrl+C at the shell emits '^C' and pressing Ctrl+Z at the shell emits '^Z'.

So my question is, why this weird behaviour? I'd probably have multiple server processes running as separate threads in the same process so any clean way of killing the server when the process receives SIGINT? BTW using Python 2.6.4 on Ubuntu.

TIA,
sasuke

Mwiza
  • 7,780
  • 3
  • 46
  • 42
sasuke
  • 6,589
  • 5
  • 36
  • 35
  • 3
    Um, Ctrl+C works fine in Python for me, correctly generating a `KeyboardInterrupt` exception. Perhaps it has more to do with the socket `accept()` implementation? What happens if you open a connection after hitting Ctrl+C (so that execution is definitively passed back to Python)? -- Also, you might consider looking into something like Twisted for writing network servers in Python. – Amber Nov 03 '10 at 19:39
  • 3
    OT: You don't really want to wrap everything in `try` blocks, then catch every single exception (this includes things like `SystemExit` (`sys.exit()`), `NameError` (mistyped an identifier) and `KeyboardInterrupt` (Ctrl+C) btw) with `except` and silently swallow those. Just omit them. Only catch what you can handle. If an exception is not caught anywhere, execution will get aborted and you'll get a stacktrace anyway! –  Nov 03 '10 at 19:48
  • @Amber: If you'll notice, the `accept()` is not in the main thread but in a new thread which I explicitly spawn. Plus if you execute the code I guess you'll observe the same behaviour. Regarding Twisted; I've never done any low level network programming (C is too hardcore for me) hence the quest to make something raw involving sockets in Python. Still thanks for the suggestion. – sasuke Nov 03 '10 at 20:06
  • @delnan: Oh, how I miss good old Java... ;-) But still, thanks for the "pythonic" guidance. :-) – sasuke Nov 03 '10 at 20:07
  • @sasuke, oh, I didn't even notice the thread. Um, perhaps your issues then are stemming from the fact that your main thread of script execution has already finished? Since `.start()` doesn't block, your actual script is already done, it's just the separate thread still running. Perhaps consider adding a `t.join()` after the `t.start()` so that you script will wait for the thread you spawned to finish before winding up its own execution. – Amber Nov 03 '10 at 20:11
  • @Amber: AFAIK, `join()` is basically required for 2 cases: you need to hold off the VM termination till your *daemon* threads have completed executing or if you want to execute a given piece of code only *after* the thread you just spawned completes its work. Since none of those points apply in my case, I'm OK with the main thread completing its execution since my server thread still lives (it's not a daemon thread). But to answer your question, no, adding `t.join()` doesn't help either. – sasuke Nov 03 '10 at 20:18

2 Answers2

5

Several issues:

  • Don't catch all exceptions and silently exit. That's the worst possible thing you can do.

  • Unless you really know what you're doing, never catch SIGTSTP. It's not a signal meant for applications to intercept, and if you're catching it, chances are you're doing something wrong.

  • KeyboardInterrupt is only sent to the initial thread of the program, and never to other threads. This is done for a couple reasons. Generally, you want to deal with ^C in a single place, and not in a random, arbitrary thread that happens to receive the exception. Also, it helps guarantee that, in normal operation, most threads never receive asynchronous exceptions; this is useful since (in all languages, including Java) they're very hard to deal with reliably.

  • You're never going to see the KeyboardInterrupt here, because your main thread exits immediately after starting the secondary thread. There's no main thread to receive the exception, and it's simply discarded.

The fix is two-fold: keep the main thread around, so the KeyboardInterrupt has somewhere to go, and only catch KeyboardInterrupt, not all exceptions.


import threading, sys, socket, signal, time

def startserver():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(('', 12345))
    s.listen(1)
    while True:
        csock, caddr = s.accept()
        csock.sendall('Get off my lawn kids...\n')
        csock.close()

if __name__ == '__main__':
    try:
        t = threading.Thread(target=startserver)
        t.start()

        # Wait forever, so we can receive KeyboardInterrupt.
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        print "^C received"
        sys.exit(1)

    # We never get here.
    raise RuntimeError, "not reached"
Glenn Maynard
  • 55,829
  • 10
  • 121
  • 131
  • Great answer. I'd probably use t.join() instead of time.sleep() to block the main thread though, it's semantically cleaner even though the behaviour would be the same. – Gintautas Miliauskas Nov 03 '10 at 20:33
  • It works! I'm baffled as to what is the difference between manually keeping the `Thread` alive and calling `join()` on it? Also, from your answer, it seems that the JVM has a different way of handling signals since even though the main thread completes its execution, the CTRL+C is picked up properly and terminates the server. – sasuke Nov 03 '10 at 20:48
  • 1
    @Gintautas: Yeah. @sasuke: In Python, Signals are only delivered to the main thread. I don't know anything about Java's signal behavior. – Glenn Maynard Nov 03 '10 at 21:01
1
  1. the thread your create is not daemonic, so it doesn't exit when the parent thread exits;

  2. the main exits right after it starts the child thread, python process waits for the child thread to be terminated;

  3. offtop: do not forget to close the sockets;

  4. you should implement some stop mechanism for the child thread. A simple workround: check some stopped flag in the while loop that accepts client connections (use a module-level, or extend the threading.Thread class to encapsulate it), set that flag to True and kick the server socket (by connecting) on Ctrl+C and/or Ctrl+Z or without them; wait in the main block for while t.isAlive(): time.sleep(1). Here is a sample code:


import threading, sys, socket, signal, time

class Server(threading.Thread): def init(self): threading.Thread.init(self) self._stop = False self._port =12345

def stop(self): self.__stop = True self.kick()

def kick(self): try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('', self.__port)) s.close() except IOError, e: print "Failed to kick the server:", e

def run(self): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(('', self._port)) s.listen(1) try: while True: csock, caddr = s.accept() if self._stop: print "Stopped, breaking" break print "client connected from", caddr try: csock.sendall('Get off my lawn kids...\n') finally: csock.close() finally: s.close()

if name == 'main': t = Server() # if it is failed to stop the thread properly, it will exit t.setDaemon(True) t.start() try: while t.isAlive(): time.sleep(1) except: # Ctrl+C brings us here print "Maybe, you have interrupted this thing?" t.stop() # t.join() may be called here, or another synchronization should appear to # make sure the server stopped correctly

I don't know why it show stop,kick,run methods as module-level and puts more than one newlines, sorry

khachik
  • 28,112
  • 9
  • 59
  • 94