Skip to content

fix(bavet): close stale-activity race across all nodes - #2567

Draft
triceo wants to merge 2 commits into
TimefoldAI:mainfrom
triceo:bavet
Draft

fix(bavet): close stale-activity race across all nodes#2567
triceo wants to merge 2 commits into
TimefoldAI:mainfrom
triceo:bavet

Conversation

@triceo

@triceo triceo commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

A downstream filtering node could read an out-tuple whose
state still reported active even though it was one flush away from being
retracted, because Bavet processes its node network in strict layers and a
shallow sibling can re-test a tuple before the layer that would retract it
has run. A filtering predicate dereferencing a stale shadow variable then
could crash with an NPE.

Each producing node now records the input tuple(s) its out-tuple was
derived from and staleness guards walk that chain instead of trusting bare tuple state.

…atten

A downstream filtering node could read a join/map/flatten out-tuple whose
state still reported active even though it was one flush away from being
retracted, because Bavet processes its node network in strict layers and a
shallow sibling can re-test a tuple before the layer that would retract it
has run. A filtering predicate dereferencing a stale shadow variable then
crashed with an NPE.

Each producing node now records the input tuple(s) its out-tuple was
derived from (map/flatten record one; joins record both left and right),
and staleness guards walk that chain instead of trusting bare tuple state.

groupBy/distinct output remains unfixed: a group tuple is an N:1 aggregate
with no single parent tuple whose activity implies the group's own, so
there is no correct link to record. The reproducing test is disabled with
a reason explaining this ceiling.
groupBy out-tuples could still report an active state to a downstream
filtering join even when about to be retracted, because a group is an
N:1 aggregate with no single parent tuple to walk (unlike joins,
ifExists, map, and flatten, already fixed this way).

Group now tracks its current contributors via TupleList, the same
zero-allocation intrusive linked list joins already use for
one-parent-many-children bookkeeping, replacing a bare parentCount int.
It answers Tuple's new TupleActivitySource hook (Tuple now extends this
interface, at no cost to the existing join/map/flatten mechanism) with
an OR across whichever contributors currently exist, short-circuiting
on the first still-active one so it stays cheap even for groups with
many contributors.

AbstractGroupNode now reserves its own store-slot indices from an
IntSupplier instead of receiving them pre-resolved, mirroring how
AbstractJoinNode already self-reserves from the tracker it's given -
so future group-node index needs won't require re-threading a growing
parameter list through the whole node-constructor chain again.
@sonarqubecloud

sonarqubecloud Bot commented Aug 4, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant