fix: force heater/stirrer safe on SIGTERM, not just SIGINT
systemctl stop/restart send SIGTERM, whose default disposition kills the process immediately - skipping the shutdown finally: block that cancels tasks and forces both actors safe via their activate(False). Route SIGTERM through the same KeyboardInterrupt path SIGINT already uses, and add stirrer.activate(False) alongside the existing heater.close() as a belt-and-suspenders fallback. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TvgC7oy9MxaA4ZQxXqkNdS
This commit is contained in:
+16
-4
@@ -3,6 +3,7 @@ import asyncio
|
|||||||
import functools
|
import functools
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import signal
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
@@ -202,23 +203,34 @@ if __name__ == '__main__':
|
|||||||
threading.Thread(target=http_server.serve_forever, daemon=True).start()
|
threading.Thread(target=http_server.serve_forever, daemon=True).start()
|
||||||
print("Serving {} on http://0.0.0.0:{}".format(web_dir, args.http_port))
|
print("Serving {} on http://0.0.0.0:{}".format(web_dir, args.http_port))
|
||||||
|
|
||||||
|
# systemd's `stop`/`restart` send SIGTERM, whose default disposition is to
|
||||||
|
# kill the process immediately - skipping the finally: block below
|
||||||
|
# entirely, leaving the heater/stirrer at whatever they were last set to.
|
||||||
|
# Routing it through the same KeyboardInterrupt path Ctrl-C (SIGINT)
|
||||||
|
# already uses gets it the same graceful, actor-safing shutdown.
|
||||||
|
signal.signal(signal.SIGTERM, signal.default_int_handler)
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
try:
|
try:
|
||||||
loop.run_forever()
|
loop.run_forever()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("\nShutting down...")
|
print("\nShutting down...")
|
||||||
finally:
|
finally:
|
||||||
# Cancel all tasks so their finally blocks (e.g. HeaterTask's
|
# Cancel all tasks so their finally blocks (e.g. HeaterTask's/
|
||||||
# `with device.open()`) get a chance to run before we close the port.
|
# StirrerTask's `with device.open()`) get a chance to run before we
|
||||||
|
# close the port - this is what actually forces the heater to 0 W
|
||||||
|
# (AHeater.activate(False)) and the stirrer to a stopped state
|
||||||
|
# (AStirrer.activate(False)) on any exit that reaches here.
|
||||||
pending = asyncio.all_tasks(loop)
|
pending = asyncio.all_tasks(loop)
|
||||||
for task in pending:
|
for task in pending:
|
||||||
task.cancel()
|
task.cancel()
|
||||||
if pending:
|
if pending:
|
||||||
loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True))
|
loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True))
|
||||||
server_log_task.write()
|
server_log_task.write()
|
||||||
# Belt-and-suspenders: explicitly close the heater connection even if
|
# Belt-and-suspenders: explicitly deactivate both actors even if the
|
||||||
# the task cleanup above failed to reach HeaterHendi.activate(False).
|
# task cleanup above failed to reach their own activate(False).
|
||||||
heater.close()
|
heater.close()
|
||||||
|
stirrer.activate(False)
|
||||||
http_server.shutdown()
|
http_server.shutdown()
|
||||||
loop.close()
|
loop.close()
|
||||||
print("Server stopped.")
|
print("Server stopped.")
|
||||||
|
|||||||
Reference in New Issue
Block a user