mirror of
https://github.com/zulip/zulip.git
synced 2025-11-16 11:52:01 +00:00
timeout: Try to kill the thread once the timeout expires
(imported from commit 25e19a3baa306fb440b27f492506ff231681bb4e)
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
import time
|
||||||
|
import ctypes
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
# Based on http://code.activestate.com/recipes/483752/
|
# Based on http://code.activestate.com/recipes/483752/
|
||||||
@@ -10,9 +12,14 @@ class TimeoutExpired(Exception):
|
|||||||
def timeout(timeout, func, *args, **kwargs):
|
def timeout(timeout, func, *args, **kwargs):
|
||||||
'''Call the function in a separate thread.
|
'''Call the function in a separate thread.
|
||||||
Return its return value, or raise an exception,
|
Return its return value, or raise an exception,
|
||||||
within 'timeout' seconds.
|
within approximately 'timeout' seconds.
|
||||||
|
|
||||||
The function may still be running in the background!
|
The function may receive a TimeoutExpired exception
|
||||||
|
anywhere in its code, which could have arbitrary
|
||||||
|
unsafe effects (resources not released, etc.).
|
||||||
|
It might also fail to receive the exception and
|
||||||
|
keep running in the background even though
|
||||||
|
timeout() has returned.
|
||||||
|
|
||||||
This may also fail to interrupt functions which are
|
This may also fail to interrupt functions which are
|
||||||
stuck in a long-running primitive interpreter
|
stuck in a long-running primitive interpreter
|
||||||
@@ -34,12 +41,38 @@ def timeout(timeout, func, *args, **kwargs):
|
|||||||
except BaseException, e:
|
except BaseException, e:
|
||||||
self.exn = e
|
self.exn = e
|
||||||
|
|
||||||
|
def raise_async_timeout(self):
|
||||||
|
# Called from another thread.
|
||||||
|
# Attempt to raise a TimeoutExpired in the thread represented by 'self'.
|
||||||
|
tid = ctypes.c_long(self.ident)
|
||||||
|
result = ctypes.pythonapi.PyThreadState_SetAsyncExc(
|
||||||
|
tid, ctypes.py_object(TimeoutExpired))
|
||||||
|
if result > 1:
|
||||||
|
# "if it returns a number greater than one, you're in trouble,
|
||||||
|
# and you should call it again with exc=NULL to revert the effect"
|
||||||
|
#
|
||||||
|
# I was unable to find the actual source of this quote, but it
|
||||||
|
# appears in the many projects across the Internet that have
|
||||||
|
# copy-pasted this recipe.
|
||||||
|
ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
|
||||||
|
|
||||||
thread = TimeoutThread()
|
thread = TimeoutThread()
|
||||||
thread.start()
|
thread.start()
|
||||||
thread.join(timeout)
|
thread.join(timeout)
|
||||||
|
|
||||||
if thread.isAlive():
|
if thread.isAlive():
|
||||||
|
# Gamely try to kill the thread, following the dodgy approach from
|
||||||
|
# http://stackoverflow.com/questions/323972/is-there-any-way-to-kill-a-thread-in-python
|
||||||
|
#
|
||||||
|
# We need to retry, because an async exception received while the
|
||||||
|
# thread is in a system call is simply ignored.
|
||||||
|
for i in xrange(10):
|
||||||
|
thread.raise_async_timeout()
|
||||||
|
time.sleep(0.1)
|
||||||
|
if not thread.isAlive():
|
||||||
|
break
|
||||||
raise TimeoutExpired
|
raise TimeoutExpired
|
||||||
|
|
||||||
if thread.exn:
|
if thread.exn:
|
||||||
raise thread.exn
|
raise thread.exn
|
||||||
return thread.result
|
return thread.result
|
||||||
|
|||||||
Reference in New Issue
Block a user