Skip to content

Add: broadcast import-cache release when Worker.release_buffer() runs - #1769

Merged
ChaoWao merged 1 commit into
hw-native-sys:mainfrom
ChaoWao:import-release-broadcast
Aug 11, 2026
Merged

Add: broadcast import-cache release when Worker.release_buffer() runs#1769
ChaoWao merged 1 commit into
hw-native-sys:mainfrom
ChaoWao:import-release-broadcast

Conversation

@ChaoWao

@ChaoWao ChaoWao commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Worker.release_buffer() now broadcasts an import-cache release to every consumer of the identity — the deferred item from Fix: reject Buffer release while an in-flight run still references it #1751's design (§8 of the P1-B corrected design: "import mapping is released along with the handle's lifecycle, not dragged to Worker.close()"). Without this, a long-running chip/SUB child or nested Worker keeps a released Buffer's shm mapping resident for its entire process lifetime — a slow /dev/shm leak for any workload that creates/releases many buffers.
  • ImportRegistry.unregister(identity) (buffer.py) drops one cached mapping; idempotent when the endpoint never materialized it.
  • No new C++/bindings: reuses the existing generic broadcast_control_all / Worker._broadcast_py_control channel that _CTRL_PY_REGISTER et al. already use — it already reaches both NEXT_LEVEL (chip children and nested Workers, registered identically via add_next_level_worker) and SUB. New sub_cmd _CTRL_IMPORT_RELEASE rides that channel.
  • The digest-sized control slot carries a CanonicalIdentity's three meaningful fields packed field-by-field (_pack_identity_wire/_unpack_identity_wire), not the identity's own bytes — CanonicalIdentity's binding deliberately exposes no pack(), so this reconstructs the wire form the same way remote_l3_protocol.py already does for the cross-machine wire.
  • _child_worker_loop (nested NEXT_LEVEL Worker) forwards one more hop down via a new Worker._release_import_recursive(), which also drops the same-process self._chip_import_registry entry an L2 direct-chip Worker may hold.
  • Best-effort throughout, mirroring _broadcast_unregister: a slow or dead child must not block or fail release_buffer().

Testing

  • pyut: 1314 passed / 13 skipped / 0 failed
  • ruff check/format, pyright: clean
  • New unit tests for ImportRegistry.unregister() (present / absent / re-materialize-after-drop)
  • New integration test against a real forked chip child (device-free fake_chip_l3 harness) proving the wire round-trip works against a live process, not just mocks
  • Real a2a3 onboard run (test_l3_tensor_dispatch.py, 2 chips) confirms no regression in the shared mailbox control loop the new branches were added to

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ba7eef56-bf79-45c2-b3f6-9853f21fc8c6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

ImportRegistry can unregister imported buffers. Workers encode and broadcast import-release commands across child hierarchies. Successful owner-buffer closure now invalidates descendant import caches, with tests covering registry lifecycle and forked-chip release.

Changes

Buffer release propagation

Layer / File(s) Summary
Import registry unregistration
python/simpler/buffer.py, tests/ut/py/test_buffer.py
ImportRegistry.unregister removes cached identities, closes consumer-side shared-memory handles, and supports rematerialization after invalidation.
Release control protocol
python/simpler/worker.py
Workers encode canonical identities and handle _CTRL_IMPORT_RELEASE for SUB, chip, and nested Worker children.
Owner release and validation
python/simpler/worker.py, tests/ut/py/test_worker/test_release_buffer.py, tests/ut/py/test_remote_l3_lifecycle.py, tests/ut/py/test_worker/test_create_buffer.py
Worker.release_buffer broadcasts invalidation after successful closure and forwards it through descendants. Tests cover forked-chip release and required fixture state.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant OwnerWorker
  participant ChildWorker
  participant ImportRegistry
  OwnerWorker->>OwnerWorker: Close owner buffer
  OwnerWorker->>ChildWorker: Broadcast canonical identity
  ChildWorker->>ImportRegistry: Unregister imported identity
  ChildWorker->>ChildWorker: Forward release to descendants
Loading

Possibly related PRs

Poem

A rabbit saw buffers hop through the chain,
Then close their handles and vanish again.
The owner says, “Release!” with care,
Each child clears its cache in air.
Fresh buffers bloom where old ones ran. 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: broadcasting import-cache release when Worker.release_buffer() runs.
Description check ✅ Passed The description directly explains the import-cache release changes, control messaging, recursive cleanup, and testing.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@python/simpler/worker.py`:
- Line 9835: Make _release_import_recursive atomic with task admission:
serialize release through cache invalidation or mark the identity revoked before
releasing the admission lock, and reject subsequent submissions. Update
_submit_l2_locked to publish _chip_run_touched_identities before materializing
the mapping, ensuring release cannot miss an admitted run. Add a concurrent
release-versus-submit regression test covering L2 and L3+ behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c6d3a598-81b9-48ed-993c-110342846eec

📥 Commits

Reviewing files that changed from the base of the PR and between 0fc77f2 and edc30cf.

📒 Files selected for processing (6)
  • python/simpler/buffer.py
  • python/simpler/worker.py
  • tests/ut/py/test_buffer.py
  • tests/ut/py/test_remote_l3_lifecycle.py
  • tests/ut/py/test_worker/test_create_buffer.py
  • tests/ut/py/test_worker/test_release_buffer.py

Comment thread python/simpler/worker.py
@ChaoWao
ChaoWao force-pushed the import-release-broadcast branch from edc30cf to 927c1f1 Compare August 11, 2026 03:22
hw-native-sys#1751 deferred this explicitly: releasing a Buffer only unlinks its shm on
the owner side, but a consumer that once materialized it (a forked chip or
SUB child, or a nested NEXT_LEVEL Worker) keeps that mapping resident for its
entire process lifetime -- release never told it to drop the cache. The
authoritative design (.docs/worker-memory-model/p1b-corrected-design.md §8)
states the requirement directly: import mapping is supposed to be released
along with the handle's lifecycle, not dragged to Worker.close(). For a
long-running worker that creates/releases many buffers over its life, every
consumer's resident mapping is a slow leak of /dev/shm capacity that never
gets reclaimed until the consumer process itself exits.

ImportRegistry (buffer.py) gains unregister(identity): pop the cached
mapping if this endpoint made one, close its shm, no-op otherwise -- hw-native-sys#1747
deleted the previous unregister() as dead code with zero production callers;
this reintroduces one with a real caller.

The broadcast itself needs no new C++: the codebase already has a generic
cross-process control channel (WorkerManager::broadcast_control_all, driven
from Python via Worker._broadcast_py_control) that _CTRL_PY_REGISTER /
_CTRL_PY_UNREGISTER / _CTRL_PY_IMPORT_REGISTER already use, and it reaches
both WorkerType.NEXT_LEVEL (which covers chip children and nested Workers
uniformly -- both are registered through the same add_next_level_worker
call) and WorkerType.SUB. A new sub_cmd, _CTRL_IMPORT_RELEASE, rides that
existing channel; the digest-sized control slot carries a CanonicalIdentity's
three meaningful fields (owner_instance_id, buffer_id, generation) packed by
a new _pack_identity_wire/_unpack_identity_wire pair, not the identity's own
bytes -- CanonicalIdentity's binding deliberately exposes no pack() (a raw
byte dump once let a registry key on wire padding and split one backing in
two), so the wire form is reconstructed field-by-field here, the same way
remote_l3_protocol.py already encodes one for the cross-machine wire.

Receiving-side branches: _run_chip_main_loop and _sub_worker_loop each call
import_registry.unregister(identity) directly; _child_worker_loop (a nested
NEXT_LEVEL Worker) forwards one more hop down via the new
Worker._release_import_recursive(), which also drops the same-process
self._chip_import_registry entry an L2 direct-chip Worker may hold for its
own buffers. release_buffer() calls it once buffer.close() has actually
succeeded, so a failed close never tells a descendant to drop a mapping the
owner still considers live. The broadcast is best-effort throughout,
mirroring _broadcast_unregister: a child that never materialized the
identity has nothing to drop, and a slow or dead child must not block or
fail release_buffer() -- the Buffer is already closed on the owner side by
the time it runs.

_submit_l2_locked now publishes _chip_run_touched_identities BEFORE calling
_materialize_l2_args, not just before native dispatch: _materialize_l2_args
is what populates self._chip_import_registry, the very cache this PR's
broadcast now pops on release. Publishing only around dispatch (as hw-native-sys#1757
left it) still left a window where release_buffer() could see no in-flight
run while a submit already in progress had cached the mapping, pass its
check, and pop that mapping out from under a dispatch that had not reached
native execution yet -- self._chip_import_registry never existed as a
release_buffer() target before this PR, so this window is newly reachable,
not a pre-existing gap.

New tests: ImportRegistry.unregister() present/absent/re-materialize-after-
drop (test_buffer.py); a real-forked-chip-child integration test via the
device-free fake_chip_l3 harness proving the wire round-trip (sub_cmd
numbering, CanonicalIdentity packing) actually works against a live process,
not just mocks; a regression test blocking _materialize_l2_args mid-call and
confirming release_buffer() already rejects at that point, not only after
materialize returns (test_release_buffer.py) -- confirmed against the
pre-fix ordering first: release_buffer() did not raise, and the blocked
submit thread then hit FileNotFoundError reopening the shm release had
already unlinked out from under it. Two bare-Worker test helpers
(test_create_buffer.py, test_remote_l3_lifecycle.py) construct a Worker via
__new__ and manually set internals -- they now also set _chip_import_registry
and _worker so release_buffer() (which now touches both) keeps working
against them.

Verified: pyut 1315 passed / 13 skipped / 0 failed; ruff check/format and
pyright clean on every touched file; a real a2a3 onboard run
(test_l3_tensor_dispatch.py, 2 chips) confirms no regression in the shared
mailbox control loop the new sub_cmd branches were added to.
@ChaoWao
ChaoWao merged commit efe6804 into hw-native-sys:main Aug 11, 2026
19 checks passed
@ChaoWao
ChaoWao deleted the import-release-broadcast branch August 11, 2026 06:06
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