From 573518bb2497247dc715cf7645ba875b41e6e18a Mon Sep 17 00:00:00 2001 From: Jonas Haag Date: Thu, 20 Aug 2015 12:20:32 +0200 Subject: [PATCH] Fix #77: Don't abort running requests on ^C The graceful shutdown is achieved by stopping the accept and signal watchers. This way, we're not accepting any more clients and the main loop ends as soon as all currently processed requests have been worked off. This patch also ports the libev API calls from libev v3 to v4. --- bjoern/server.c | 37 ++++++++++++++++++++++--------- tests/interrupt-during-request.py | 34 ++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 10 deletions(-) create mode 100644 tests/interrupt-during-request.py diff --git a/bjoern/server.c b/bjoern/server.c index 520748fc..0228d398 100644 --- a/bjoern/server.c +++ b/bjoern/server.c @@ -17,7 +17,6 @@ #include "wsgi.h" #include "server.h" -#define SERVER_INFO(loop) ((ServerInfo*)ev_userdata(loop)) #define READ_BUFFER_SIZE 64*1024 #define Py_XCLEAR(obj) do { if(obj) { Py_DECREF(obj); obj = NULL; } } while(0) #define GIL_LOCK(n) PyGILState_STATE _gilstate_##n = PyGILState_Ensure() @@ -36,6 +35,11 @@ enum write_state { aborted, }; +typedef struct { + ServerInfo* server_info; + ev_io accept_watcher; +} ThreadInfo; + typedef void ev_io_callback(struct ev_loop*, ev_io*, const int); #if WANT_SIGINT_HANDLING @@ -56,11 +60,13 @@ static bool handle_nonzero_errno(Request*); void server_run(ServerInfo* server_info) { struct ev_loop* mainloop = ev_loop_new(0); - ev_set_userdata(mainloop, server_info); - ev_io accept_watcher; - ev_io_init(&accept_watcher, ev_io_on_request, server_info->sockfd, EV_READ); - ev_io_start(mainloop, &accept_watcher); + ThreadInfo thread_info; + thread_info.server_info = server_info; + ev_set_userdata(mainloop, &thread_info); + + ev_io_init(&thread_info.accept_watcher, ev_io_on_request, server_info->sockfd, EV_READ); + ev_io_start(mainloop, &thread_info.accept_watcher); #if WANT_SIGINT_HANDLING ev_signal signal_watcher; @@ -70,19 +76,30 @@ void server_run(ServerInfo* server_info) /* This is the program main loop */ Py_BEGIN_ALLOW_THREADS - ev_loop(mainloop, 0); - ev_default_destroy(); + ev_run(mainloop, 0); + ev_loop_destroy(mainloop); Py_END_ALLOW_THREADS } #if WANT_SIGINT_HANDLING +static void +pyerr_set_interrupt(struct ev_loop* mainloop, struct ev_cleanup* watcher, const int events) +{ + PyErr_SetInterrupt(); + free(watcher); +} + static void ev_signal_on_sigint(struct ev_loop* mainloop, ev_signal* watcher, const int events) { /* Clean up and shut down this thread. * (Shuts down the Python interpreter if this is the main thread) */ - ev_unloop(mainloop, EVUNLOOP_ALL); - PyErr_SetInterrupt(); + ev_cleanup* cleanup_watcher = malloc(sizeof(ev_cleanup)); + ev_cleanup_init(cleanup_watcher, pyerr_set_interrupt); + ev_cleanup_start(mainloop, cleanup_watcher); + + ev_io_stop(mainloop, &((ThreadInfo*)ev_userdata(mainloop))->accept_watcher); + ev_signal_stop(mainloop, watcher); } #endif @@ -107,7 +124,7 @@ ev_io_on_request(struct ev_loop* mainloop, ev_io* watcher, const int events) } Request* request = Request_new( - SERVER_INFO(mainloop), + ((ThreadInfo*)ev_userdata(mainloop))->server_info, client_fd, inet_ntoa(sockaddr.sin_addr) ); diff --git a/tests/interrupt-during-request.py b/tests/interrupt-during-request.py new file mode 100644 index 00000000..22d2038a --- /dev/null +++ b/tests/interrupt-during-request.py @@ -0,0 +1,34 @@ +import time +import threading +import bjoern +import os +import signal +import httplib + +HOST = ('127.0.0.1', 9000) + +request_completed = False + +def application(environ, start_response): + start_response('200 ok', []) + yield "chunk1" + os.kill(os.getpid(), signal.SIGINT) + yield "chunk2" + yield "chunk3" + global request_completed + request_completed = True + + +def requester(): + conn = httplib.HTTPConnection(*HOST) + conn.request("GET", "/") + conn.getresponse() + + +threading.Thread(target=requester).start() +try: + bjoern.run(application, *HOST) +except KeyboardInterrupt: + assert request_completed +else: + raise RuntimeError("Didn't receive KeyboardInterrupt")