`receive_text` raises `WebSocketDisconnect` once the peer has gone and `RuntimeError` on every call after that. The loop swallowed both — the exception was checked only to decide whether the frame held a media subscription — and immediately made another read, which raised again. A closed tab therefore left a coroutine reading a dead socket at full tilt: around 15,000 turns a second, one orphaned `Queue.get()` per turn parked on the bus queue's waiter list. The event loop stalled behind it, `/health` answered 503 on its loop-lag check, and Docker's probe plus Traefik took the API out of the pool mid-request — which is what `make verify` was dying on, with `TypeError: Failed to fetch` from the browser. The spin ended only when an event finally reached the send path and the socket raised there, so it lasted from milliseconds to minutes depending on what the bus happened to be carrying. Four abandoned sockets logged 2,518 `Task was destroyed but it is pending!` lines and up to 2.6s of loop lag; twenty now log none and cost 1.0ms. A failed read is the disconnect, so it ends the stream. The getter created beside it is cancelled rather than abandoned: nothing reads it after the turn that made it, and left behind it would also take an event the live reader wanted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CL9zvnnvcp1mvA8o7impxk
Fluksio
Fluksio is a node-based automation software that brings trust and reliability to your flow. It just works and looks good. Get started by running
pip install fluksio
fluksio serve
and you're ready to go.
For data science
You can turn your existing data science project into a flow by decorating your functions with @node ...
# myresearch/train.py
import fluksio
from fluksio import Port, node
@node(
requires=["dataset", Port("lr", "float")],
provides=[Port("loss", "float", stream=True), Port("weights", "artifact")],
device="gpu", device_policy="prefer",
)
def fit(dataset, lr, epochs=25):
for epoch in range(epochs):
loss = step(...)
yield {"loss": loss} # published as it happens, kept as a series
return {"weights": fluksio.save_artifact("weights.pt")}
... and passing them to a Flow:
# myresearch/pipeline.py
from fluksio import Flow, Port
from myresearch.data import prepare
from myresearch.evaluate import evaluate
from myresearch.train import fit
train = Flow("train", nodes=[prepare, fit, evaluate],
inputs=[Port("lr", "float", initial=0.01)], outputs=["score"])
Fluksio will automatically infer the order of nodes based on the inputs and outputs you defined. When everything is set, you can launch your first run as follows:
fluksio run train --lr 0.05 --wait
Checkout our documentation for more infos.
Some other features
- Flows: typed messages between nodes, wired by name, edited on a canvas or declared in code. Every change is a commit in a git repository you own.
- Runs: an experiment and a CI-style job are the same entity. Parameters, seed, result, per-node timings, artifacts and the commit it ran at.
- Dashboards: charts and controls bound to the same messages the flows carry, with no separate metrics pipeline.
- Remote workers:
pip install fluksio-workeron the GPU box; it dials out over one websocket, so nothing there has to be reachable.
Fluksio can also be used for facility automation. Visit us on fluksio.com or go straight to our documentation.
License
Copyright (C) 2026 Melvin Strobl - GNU Affero General Public License v3.0 or later. Running a modified version over a network obliges you to offer its users the corresponding source (AGPL §13).