From: Ben Darnell Date: Tue, 12 Jul 2011 17:26:33 +0000 (-0700) Subject: Fix race condition in cross-thread IOLoop.add_callback X-Git-Tag: v2.1.0~91 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=2ee58f1f29c2cd3ecd3c3b143b180c9531ddeaf2;p=thirdparty%2Ftornado.git Fix race condition in cross-thread IOLoop.add_callback --- diff --git a/tornado/ioloop.py b/tornado/ioloop.py index 261c75d0f..6198de32e 100644 --- a/tornado/ioloop.py +++ b/tornado/ioloop.py @@ -26,12 +26,15 @@ In addition to I/O events, the `IOLoop` can also schedule time-based events. `IOLoop.add_timeout` is a non-blocking alternative to `time.sleep`. """ +from __future__ import with_statement + import errno import heapq import os import logging import select import thread +import threading import time import traceback @@ -107,6 +110,7 @@ class IOLoop(object): self._handlers = {} self._events = {} self._callbacks = [] + self._callback_lock = threading.Lock() self._timeouts = [] self._running = False self._stopped = False @@ -231,8 +235,9 @@ class IOLoop(object): # Prevent IO event starvation by delaying new callbacks # to the next iteration of the event loop. - callbacks = self._callbacks - self._callbacks = [] + with self._callback_lock: + callbacks = self._callbacks + self._callbacks = [] for callback in callbacks: self._run_callback(callback) @@ -359,9 +364,17 @@ class IOLoop(object): from that IOLoop's thread. add_callback() may be used to transfer control from other threads to the IOLoop's thread. """ - if not self._callbacks and thread.get_ident() != self._thread_ident: + with self._callback_lock: + list_empty = not self._callbacks + self._callbacks.append(stack_context.wrap(callback)) + if list_empty and thread.get_ident() != self._thread_ident: + # If we're in the IOLoop's thread, we know it's not currently + # polling. If we're not, and we added the first callback to an + # empty list, we may need to wake it up (it may wake up on its + # own, but an occasional extra wake is harmless). Waking + # up a polling IOLoop is relatively expensive, so we try to + # avoid it when we can. self._waker.wake() - self._callbacks.append(stack_context.wrap(callback)) def _run_callback(self, callback): try: