I am starting a daemon thread from a context manager that should send a heartbeat every second, but since it is running in a thread it will not terminate the context manager if an exception occurs. How do I raise an exception in the context manager when the heartbeats stops?
from contextlib import contextmanager
from threading import Thread, Event
from time import sleep
@contextmanager
def plc():
stop_event = Event()
try:
# Send heartbeat every second
hb_t = Thread(target=heartbeat_task,
args=(stop_event,),
daemon=True)
hb_t.start()
yield
except Exception:
raise
finally:
stop_event.set()
hb_t.join()
print("Heartbeat stopped")
def heartbeat_task(stop_event):
value = False
while not stop_event.is_set():
value = not value
print("Heartbeat: " + str(value))
sleep(1)
def main():
with plc():
while True:
print("Program running")
sleep(5)
if __name__ == '__main__':
main()
I have a hard time finding examples of this.
Thanks for helping out!
Update
I have modified the code to be more closely aligned with the code you posted. But:
Your code as presented has an inconsistency:
heartbeat_taskis passed an event that if set will cause the function to return. But it is only set when the context manager in functionmaincreated withwith plc():exits, which is never. If you are hoping that any exception thrown byheartbeat_taskwill force the context manager to exit and then caught in functionplc, then what is the point of it callingstop_event.set()if by definition we only arrive here ifheartbeat_taskis no longer running due to an exception?So either you want
heartbeat_taskto run indefinitely until it raises an exception (in which case there is no point in having a "stop" event) or you want to be able to stopheartbeat_taskwhen some condition exists, but there is no code for doing that. I will assume for demo purposes thatmainwill be given access to thestop_eventevent and will set it under some circumstance. Otherwise, it runs until it detects thatheartbeat_taskis no longer running presumably because it raised an exception (it is executing an infinite loop, so how else could it terminate if the stop event has not been set?). What remains is why you need to be using a context manager at all. I will present an alternative later on.If you use a multithreading pool (we only need one thread in the pool), it become simple for the main thread to catch exceptions thrown by a task submitted to the pool: When
multiprocessing.pool.ThreadPool.apply_asyncis called amultiprocessing.pool.AsyncResultinstance is returned that represents a future completion. When methodgetis called on this instance you either get the return value from the worker function (heartbeat_task) or any exception thrown by the worker function is re-raised. But we can also use methodwaitto wait for either the completion of the submitted task or an elapsed time. We can then test whether after waiting 5 seconds whether the submitted task actually finished (due to an exception or return) with methodready. If the task is still running, then we can tell it to stop. In this demo I force the task to raise an exception after approximately 7 seconds:Prints:
Alternative to Using a Context Manager