I have an application that relies on signals for timeouts for a number of blocking actions.
For example:
def wait_timeout(signum, frame):
raise Exception("timeout")
signal.signal(signal.SIGALRM, wait_timeout)
signal.setitimer(signal.ITIMER_REAL, 5)
try:
while true:
print("zzz")
sleep(1)
except Exception as e:
# timeout
print("Time's up")
Now I've implemented multithreading using the same approach, but with all threads I get ValueError: signal only works in main thread
.
I assume the approach for timeouts with signals is not applicable in threads.
Unfortunately I can't use something like this:
timeout = 5
start = time.time()
while true:
print("zzz")
sleep(1)
if time.time() <= start+timeout:
print("Time's up)
break
As the operations in the while loop may be blocking and possibly last forever, therefore the loop may never reach the if clause.
Q: How may I implement a timeout, like I used to do it with signals, in threads?
edit: I've come across this blog post , showing a similar solution to setTimeout()
in JavaScript in python. I assume this may be a possible solution, but I'm really not sure how this may be used.
edit2: I start the threads in the main as follows:
p = Popen(["tool", "--param", arg], stdin=PIPE, stdout=PIPE, stderr=STDOUT)
t = Thread(target=process_thread, daemon=True, args=(p,arg1,arg2))
t.start()
The process_thread
function processes the stdout of the tool
, by doing the following:
for line in p.stdout:
# process line of the processes stdout
This process may take forever, eg once the tool
doesn't produce any output. I only want the output of the tool
for, let's say 5 seconds, so the for loop needs to be broken, after a specific timeout.
That's what I used signals for, but apparently they don't work in threads.
edit3: I've created a more elaborate and accurate example on how I intend to use the singals in threads. See the gist here
What you are looking for is a Watchdog .
def watchdog(queue):
while True:
watch = queue.get()
time.sleep(watch.seconds)
try:
watch = queue.get_nowait()
# No except, got queue message,
# do noting wait for next watch
except queue.Empty:
os.kill(watch.pid, signal.SIGKILL)
def workload_thread(queue):
pid = os.getpid()
queue.put({'pid':pid, 'seconds':5})
# do your work
# Test Watchdog
# time.sleep(6)
queue.put({'pid':pid, 'done':True})
Note: Code not tested, may have Syntax errors!
This implements a class Terminator
, which sends a signal.SIG...
after a given timeout=5
to Threads Popen process
. Multiple different pid
are possible.
class Terminator(object):
class WObj():
def __init__(self, process, timeout=0, sig=signal.SIGABRT):
self.process = process
self.timeout = timeout
self.sig = sig
def __init__(self):
self.__queue = queue.Queue()
self.__t = Thread(target=self.__sigterm_thread, args=(self.__queue,))
self.__t.start()
time.sleep(0.1)
def __sigterm_thread(self, q):
w = {}
t = 0
while True:
time.sleep(0.1);
t += 1
try:
p = q.get_nowait()
if p.process == 0 and p.sig == signal.SIGTERM:
# Terminate sigterm_thread
return 1
if p.process.pid not in w:
if p.timeout > 0 and p.sig != signal.SIGABRT:
w[p.process.pid] = p
else:
if p.sig == signal.SIGABRT:
del (w[p.process.pid])
else:
w[p.process.pid].timeout = p.timeout
except queue.Empty:
pass
if t == 10:
for key in list(w.keys()):
p = w[key]
p.timeout -= 1
if p.timeout == 0:
""" A None value indicates that the process hasn't terminated yet. """
if p.process.poll() == None:
p.process.send_signal(p.sig)
del (w[p.process.pid])
t = 0
# end if t == 10
# end while True
def signal(self, process, timeout=0, sig=signal.SIGABRT):
self.__queue.put(self.WObj(process, timeout, sig))
time.sleep(0.1)
def close(self, process):
self.__queue.put(self.WObj(process, 0, signal.SIGABRT))
time.sleep(0.1)
def terminate(self):
while not self.__queue.empty():
trash = self.__queue.get()
if self.__t.is_alive():
self.__queue.put(self.WObj(0, 0, signal.SIGTERM))
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.__del__()
def __del__(self):
self.terminate()
This is the workload for instance:
def workload(n, sigterm):
print('Start workload(%s)' % n)
arg = str(n)
p = Popen(["tool", "--param", arg], stdin=PIPE, stdout=PIPE, stderr=STDOUT)
sigterm.signal(p, timeout=4, sig=signal.SIGTERM)
while True:
for line in p.stdout:
# process line of the processes stdout
print(line.strip())
time.sleep(1)
if p.poll() != None:
break
sigterm.close(p)
time.sleep(0.1)
print('Exit workload(%s)' % n)
if __name__ == '__main__':
with Terminator() as sigterm:
p1 = Thread(target=workload, args=(1, sigterm)); p1.start(); time.sleep(0.1)
p2 = Thread(target=workload, args=(2, sigterm)); p2.start(); time.sleep(0.1)
p3 = Thread(target=workload, args=(3, sigterm)); p3.start(); time.sleep(0.1)
p1.join(); p2.join(); p3.join()
time.sleep(0.5)
print('EXIT __main__')
Tested with Python:3.4.2 and Python:2.7.9
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.