Stop and start a flow without rebuilding anything

A stopped flow's nodes are built like any other flow's — being stopped
means having no subscriptions, schedules or webhooks, not being absent —
so a toggle only ever needed the lifecycle call and the gate that goes
with it. It was doing a whole-pipeline rebuild instead, which on a
populated installation is every node in every flow reconnecting.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01StpRc2C6au1WJ1EUU7fsfu
This commit is contained in:
2026-08-23 18:35:28 +02:00
co-authored by Claude Opus 5
parent cba8ca2c54
commit 0a018fb698
3 changed files with 80 additions and 3 deletions
@@ -297,3 +297,38 @@ def test_a_deleted_flow_leaves_its_consumers_reporting_a_missing_input(
assert engine.pipeline.get_node_by_id("a.meter") is None
asyncio.run(asyncio.wait_for(scenario(), timeout=10))
def test_stopping_one_flow_does_not_touch_the_others(
tmp_path: Path, lifecycle: list[str]
):
"""A toggle is a lifecycle call, not a rebuild.
A stopped flow's nodes are still built — being stopped means having no
subscriptions, not being absent — so nothing is compiled and no other
flow's node is asked to reconnect. That is where the seconds went.
"""
store = FlowStore(tmp_path / "flows")
for name in ("a", "b"):
store.write_flow(FlowDef(name=name, nodes=[NodeDef(id="io", type="lifecycle")]))
engine = FlowController(store)
async def scenario() -> None:
await engine.reload()
before = {name: engine.loaded[name].node for name in ("a.io", "b.io")}
lifecycle.clear()
await engine.set_enabled("a", False)
assert lifecycle == ["stop a.io"]
# Still built, still the same objects: only the lifecycle changed.
assert {name: engine.loaded[name].node for name in ("a.io", "b.io")} == before
assert engine.disabled == {"a"}
lifecycle.clear()
await engine.set_enabled("a", True)
assert lifecycle == ["start a.io"]
assert {name: engine.loaded[name].node for name in ("a.io", "b.io")} == before
asyncio.run(asyncio.wait_for(scenario(), timeout=10))