Source code for ezmsg.dashboard.backend.execution_worker
"""Disposable validation process or owned graph runner process."""
from __future__ import annotations
import asyncio
import json
import signal
import sys
import threading
from pathlib import Path
from .execution_document import construct_graph
from .external_sources import external_sources
[docs]
def write_status(path: Path, **payload):
temporary = path.with_suffix(".tmp")
temporary.write_text(json.dumps(payload))
temporary.replace(path)
[docs]
def check_main_threads(component):
if getattr(component, "__main__", None):
raise ValueError("Main-thread Units require a dedicated execution policy")
for child in getattr(component, "components", {}).values():
check_main_threads(child)
[docs]
def main():
mode, request_path, status_path = sys.argv[1:]
status_path = Path(status_path)
runner = None
stopped = threading.Event()
signal.signal(signal.SIGTERM, lambda *_: stopped.set())
signal.signal(signal.SIGINT, lambda *_: stopped.set())
try:
request = json.loads(Path(request_path).read_text())
from ezmsg.core.backend import ExecutionContext, GraphRunner
from ezmsg.core.netprotocol import Address
sources = (
asyncio.run(external_sources(request["graph_address"], request["root_name"]))
if request["document"].get("external_inputs")
else []
)
components, connections = construct_graph(request["document"], sources)
if mode == "validate":
context = ExecutionContext.setup(components, root_name=request["root_name"], connections=connections)
if context is None:
raise ValueError("The graph contains no executable Units or relays")
# Collection internals must follow the same main-thread policy as Units.
for component in components.values():
check_main_threads(component)
write_status(status_path, state="valid")
return
runner = GraphRunner(
components=components,
connections=connections,
root_name=request["root_name"],
graph_address=Address.from_string(request["graph_address"]),
auto_start=False,
)
runner.start()
write_status(status_path, state="running")
while not stopped.wait(0.1):
if status_path.with_suffix(".stop").exists():
break
if any(not process.is_alive() for process in runner.processes):
raise RuntimeError("A managed process exited; the processing group has stopped")
runner.stop()
runner = None
write_status(status_path, state="stopped")
except BaseException as exc:
write_status(status_path, state="failed", error=f"{type(exc).__name__}: {exc}")
finally:
if runner is not None and runner.running:
runner.stop()
if __name__ == "__main__":
main()