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 c95d0ca8f5
commit 56396732a4
3 changed files with 80 additions and 3 deletions
+41 -2
View File
@@ -426,9 +426,48 @@ class FlowController:
entry.last_error_ts = None
async def set_enabled(self, flow: str, enabled: bool) -> None:
"""Stop or start one flow. Rebuilding is what applies it."""
"""Stop or start one flow, without building anything.
Its nodes are built whether or not it runs — being stopped means
having no subscriptions, schedules or webhooks, not being absent — so
all this has to do is the lifecycle call and the gate that goes with
it. Nothing is compiled and no other flow is touched.
"""
await run_in_threadpool(self.store.write_enabled, flow, enabled)
await self.reload()
if self.pipeline is None:
# Nothing built yet, so there are no nodes to start or stop.
await self.reload()
return
await self._acquire(FLOW_REBUILD_WAIT)
try:
if enabled:
# The gate first: a subscription that fires the instant it
# starts must not be turned away by a flag on its way out.
self.disabled = self.disabled - {flow}
self.pipeline.set_disabled(self.disabled)
# Lifts any quarantine, which is what a rebuild used to do by
# throwing the whole supervisor away.
await self.supervisor.cancel_flow(flow)
await self._activate(flow)
else:
self.disabled = self.disabled | {flow}
self.pipeline.set_disabled(self.disabled)
await self._teardown(flow)
finally:
self._lock.release()
self._publish(
{
"type": "pipeline_rebuilt",
"issues": [issue.model_dump() for issue in self.issues],
"nodes": [status.model_dump() for status in self.node_statuses()],
# Every flow's, not this one's: the canvas replaces its paused
# set from this, so a narrowed list would clear the markers of
# flows this never touched.
"paused": self.paused_flows(),
}
)
async def _acquire(self, wait: float | None) -> None:
"""Take the rebuild lock, or say the engine is busy.