Commit Graph
16 Commits
Author SHA1 Message Date
dtourolleandClaude Opus 4.8 bceccc3406 fix: deliver EOF sentinel only when the ring is freshly empty
🚦 CI / changes (pull_request) Successful in 19s
🚦 CI / docker (pull_request) Has been skipped
🚦 CI / test (pull_request) Successful in 8m1s
🚦 CI / tsan (pull_request) Failing after 2m22s
🚦 CI / docs (pull_request) Has been skipped
Channel<T>::pop() surfaced the out-of-band sentinel from its empty branch
using the tail_ snapshot taken at the top of the loop. Under contention the
producer can push more values *and* the sentinel in the window between that
snapshot and take_sentinel(), so pop() could return the sentinel while real
values still sat in the ring — the sentinel jumping ahead of values pushed
before it. No value was lost (a consumer that keeps draining still receives
them, and approx_size() keeps counting them so a PoolNode reschedules), but a
consumer treating the sentinel as a hard "last message" barrier would act on
EOF early.

Re-confirm emptiness against a fresh tail_ load before taking the sentinel.
Costs one acquire-load on the empty-ring path only; never runs in steady
state. The spin and post-spin takes already reload tail_ on the line above
them; try_pop_now() already reads tail_ fresh in the same branch — both were
correct and are unchanged.

The two sentinel stress cases now assert the strict "sentinel is last, after
every value" ordering (previously relaxed to avoid the flake this fixes).
Verified TSan-clean (2606 assertions, no data races) over repeated runs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 19:35:31 +02:00
dtourolle c4538f03ca python: fix nanobind Network reference leak via GC type slots
The Python Network holds each PyNode's callable, forming an
uncollectable instance -> callable -> globals() -> instance cycle that
tripped nanobind's leak check at interpreter shutdown.

Implement tp_traverse/tp_clear type slots on the Network binding so
Python's cyclic collector can see through the C++-held callables and
break the cycle. PyNode exposes its callable; PyNetwork visits and
clears them. Wired into both binding sites (auto_bind and the legacy
register_py_network).
2026-07-04 14:43:47 +02:00
dtourolleandClaude Opus 4.8 19f5a2b0ae Deliver EOF sentinels out-of-band to prevent teardown deadlock
🚦 CI / changes (push) Successful in 18s
🚦 CI / docker (push) Has been skipped
🚦 CI / docs (push) Has been cancelled
🚦 CI / test (push) Has been cancelled
Channel::push() drops values on overflow (the intended backpressure
policy for data), and PoolNode swallows the resulting ChannelOverflowError.
For a control sentinel like EOF this is fatal: a single dropped EOF under
backpressure wedges every downstream pop() forever, so the pipeline never
tears down.

Deliver sentinels out-of-band instead. Channel::push_sentinel() stores the
token in a dedicated slot that does not consume ring capacity, so it can
never overflow and — crucially — never blocks the caller. That non-blocking
property is essential: each KPN node has a single worker thread, so a
*blocking* push would park that thread and stop it draining its own input,
cascading into a hold-and-wait deadlock under backpressure. The consumer's
pop()/try_pop_now() drain the ring first, then deliver the sentinel, so it
always arrives after every value pushed before it.

approx_size() (which node readiness checks call) counts a pending sentinel
as consumable work, so a channel carrying only a sentinel still schedules
its consumer's next fire — without this the token would sit undelivered and
the pipeline would still deadlock at teardown.

PoolNode/PoolObjectNode route values carrying an eof flag (direct .eof or
nested .source.eof) through push_sentinel via a SFINAE-safe is_sentinel_value
trait; all other values keep the existing lossy throwing push. The trait
compiles to false for types without an eof convention, so this is a no-op
for pipelines that don't use one.

Verified end-to-end: scene_analyze now reaches EOF, flushes its output, and
exits cleanly instead of hanging.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 00:30:40 +02:00
dtourolle 7cb92a4091 Build docs with a pre-configured docker
🧪 Test / test (push) Successful in 4m41s
2026-06-20 08:55:48 +02:00
dtourolle 6f384dc4b5 Added callbacks for node errors and fifo overflow
📚 Docs / deploy (push) Failing after 7s
🧪 Test / test (push) Has been cancelled
Add new doc system which should/might deploy to pages.
2026-06-19 22:26:39 +02:00
dtourolleandClaude Opus 4.8 79916f1da1 Set node names in make_network for user and fanout nodes
🧪 Test / test (push) Successful in 6m22s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 15:20:50 +02:00
dtourolle f6bcaa15b0 Performance improvements, better readme and complete python bindings
🧪 Test / test (push) Failing after 28m30s
2026-05-12 21:23:33 +02:00
dtourolle c39db82763 Add a per node error handler possibility
🧪 Test / test (push) Successful in 6m7s
2026-05-10 19:51:23 +02:00
dtourolle 1e9ba5ee66 Add a unified observability interface for applications with multiple networks
🧪 Test / test (push) Successful in 6m9s
2026-05-10 19:08:40 +02:00
dtourolle 278c122e8f Add shared reasource tag to allow coordination of usage
🧪 Test / test (push) Successful in 6m8s
2026-05-09 15:22:27 +02:00
dtourolle 9acc42b2e9 Fix fanout naming for debug graph
🧪 Test / test (push) Failing after 2m38s
2026-05-09 09:54:40 +02:00
dtourolle da8f4d9926 Add debug graph to static network 2026-05-09 08:59:19 +02:00
dtourolle 011b5eb35f fix web debuginterface 2026-05-09 08:40:06 +02:00
dtourolle 9ce581b5ce Fixed bug when generating identical fanouts 2026-05-09 08:36:51 +02:00
dtourolle 2bca2a7554 Add static network
🧪 Test / test (push) Successful in 6m4s
2026-05-08 20:00:15 +02:00
dtourolle 5e77dc836b First attempt 2026-05-08 17:48:16 +02:00