Python Timeout Context Manager With Threads
Solution 1:
If the code guarded by the context manager is loop-based, consider handling this the way people handle thread killing. Killing another thread is generally unsafe, so the standard approach is to have the controlling thread set a flag that's visible to the worker thread. The worker thread periodically checks that flag and cleanly shuts itself down. Here's how you can do something analogous with timeouts:
classtimeout(object):
def__init__(self, seconds):
self.seconds = seconds
def__enter__(self):
self.die_after = time.time() + self.seconds
return self
def__exit__(self, type, value, traceback):
pass @propertydeftimed_out(self):
return time.time() > self.die_after
Here's a single-threaded usage example:
with timeout(1) as t:
whileTrue: # this will take a long time without a timeout# periodically check for timeoutsif t.timed_out:
break# or raise an exception# do some "useful" workprint"."
time.sleep(0.2)
and a multithreaded one:
import thread
defprint_for_n_secs(string, seconds):
with timeout(seconds) as t:
whileTrue:
if t.timed_out:
break# or raise an exceptionprint string,
time.sleep(0.5)
for i in xrange(5):
thread.start_new_thread(print_for_n_secs,
('thread%d' % (i,), 2))
time.sleep(0.25)
This approach is more intrusive than using signals but it works for arbitrary threads.
Solution 2:
I cannot see a way of doing what you are proposing with a context manager, you cannot yield
the flow from one thread to another.
What I would do is wrap your function with an interrutable thread with the timeout. Here is a recipe for that.
You will have an extra thread and the syntax won't be as nice but it would work.
Solution 3:
I know it's late but I'm only just reading this, but what about creating your own signaller/context manager? I'm new to python would love feedback from experienced devs this implementation.
This is based off of the answer from "Mr Fooz"
classTimeoutSignaller(Thread):
def__init__(self, limit, handler):
Thread.__init__(self)
self.limit = limit
self.running = True
self.handler = handler
assertcallable(handler), "Timeout Handler needs to be a method"defrun(self):
timeout_limit = datetime.datetime.now() + datetime.timedelta(seconds=self.limit)
while self.running:
if datetime.datetime.now() >= timeout_limit:
self.handler()
self.stop_run()
breakdefstop_run(self):
self.running = FalseclassProcessContextManager:
def__init__(self, process, seconds=0, minutes=0, hours=0):
self.seconds = (hours * 3600) + (minutes * 60) + seconds
self.process = process
self.signal = TimeoutSignaller(self.seconds, self.signal_handler)
def__enter__(self):
self.signal.start()
return self.process
def__exit__(self, exc_type, exc_val, exc_tb):
self.signal.stop_run()
defsignal_handler(self):
# Make process terminate however you like# using self.process referenceraise TimeoutError("Process took too long to execute")
Use case:
withProcessContextManager(my_proc) as p:
# do stuff e.g.
p.execute()
Solution 4:
Timeouts for system calls are done with signals. Most blocking system calls return with EINTR when a signal happens, so you can use alarm to implement timeouts.
Here's a context manager that works with most system calls, causing IOError to be raised from a blocking system call if it takes too long.
import signal, errno
from contextlib import contextmanager
import fcntl
@contextmanagerdeftimeout(seconds):
deftimeout_handler(signum, frame):
pass
original_handler = signal.signal(signal.SIGALRM, timeout_handler)
try:
signal.alarm(seconds)
yieldfinally:
signal.alarm(0)
signal.signal(signal.SIGALRM, original_handler)
with timeout(1):
f = open("test.lck", "w")
try:
fcntl.flock(f.fileno(), fcntl.LOCK_EX)
except IOError, e:
if e.errno != errno.EINTR:
raise e
print"Lock timed out"
Post a Comment for "Python Timeout Context Manager With Threads"