Skip to content

Hold the hash witness's axes by weak reference - #18

Merged
cboulay merged 1 commit into
devfrom
cboulay/witness-weakref
Oct 1, 2026
Merged

cboulay merged 1 commit into
devfrom
cboulay/witness-weakref

Conversation

@cboulay

@cboulay cboulay commented Oct 1, 2026 •

Copy link
Copy Markdown
Member

Fixes #17.

Problem

_build_witness kept the axis objects of the message that set the hash, for an identity fast path (incoming is kept). The witness is only rebuilt when the hash changes, so in a steady stream it held the first message's axes for the transformer's lifetime. For a message from another process, those axes' data are zero-copy views into the channel's shared memory. When the publisher grew its SHM, the subscriber's channel couldn't close the old segment and died for good (ezmsg-org/ezmsg#272).

Change

The witness holds weakref.ref(axis) instead:

  • The fast path still hits when a producer reuses its axes.
  • Nothing is pinned. A dead reference answers None and the check falls through to the value comparison (fingerprint, or gain/offset), which is what already happens on every message across processes.
  • A dead reference is never mistaken for a dimension that lost its axis.
  • Axis types that can't be weakly referenced are compared by value every time rather than held strongly.

Copying the kept axes (the issue's option 3) wouldn't keep the fast path: a copy is a new object, so is never matches. Dropping the shortcut entirely (option 1) works too, but costs ~160 ns per transformer per in-process message.

Cost

_message_hash witness hit, (10, 256) message with a 256-label channel axis, best of 5:

before after
in-process, reused axis 280 ns 294 ns
cross-process, new axis per message 443 ns 469 ns

Tests

TestTheWitnessHoldsNothing in tests/test_hash_witness.py:

  • the first message's axes are released, for both the specialised (time, ch) validator and the generic one (both fail without this change);
  • after the axis dies, equal content still hits and agrees with a full recompute;
  • after the axis dies, a message whose dimension lost its axis is not masked.

The issue's repro now reports every segment closable while the witness is set.

One failure in the full suite, which also fails on dev: test_producer_handles_missing_graph_server_gracefully finds a GraphServer if one is already listening on the default port.

Related: ezmsg-org/ezmsg#272 (channel-side fix, draft #273), ezmsg-org/ezmsg#274 (fingerprint computed at pickling).

The witness kept the axis objects of the message that set the hash, for
an identity fast path. Across a process boundary their data are zero-copy
views into the channel's shared memory, so the first message's segment
stayed exported for the transformer's lifetime, and a publisher growing
its SHM killed the subscriber's channel (ezmsg-org/ezmsg#272).

The witness now holds weakref.ref(axis): the fast path still hits when a
producer reuses its axes, nothing is pinned, and a dead reference falls
through to the value comparison. A dead reference is never mistaken for a
dimension that lost its axis. Axis types that cannot be weakly referenced
are compared by value every time rather than held strongly.
@cboulay
cboulay merged commit 683b79f into dev Oct 1, 2026
14 checks passed
@cboulay
cboulay deleted the cboulay/witness-weakref branch October 1, 2026 15:58
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.

Hash witness retains the first message's axes, pinning cross-process SHM (causes ezmsg#272)

1 participant