fix: register actors from imported modules in type checker - #2
Merged
Conversation
Three module registration functions (register_stdlib_hew_items, register_user_module, register_file_import_items) had catch-all match arms that silently ignored Item::Actor. This meant actors defined in imported modules were never registered — no type def, no receive fn signatures, no qualified aliases. Also extend check_spawn to resolve module-qualified actor names (spawn module.ActorName(args)) via Expr::FieldAccess. Adds 5 integration tests covering bare, glob, and named actor imports plus E2E test files for module-level actor definitions.
slepp
added a commit
that referenced
this pull request
Mar 31, 2026
… current-actor slot Two verified bugs in the WASM scheduler were causing incorrect behaviour: Bug #1 - self APIs see null/0 during WASM dispatch scheduler_wasm::activate_actor_wasm wrote its own private CURRENT_ACTOR static, while actor.rs self APIs (hew_actor_self, hew_actor_self_pid, hew_actor_self_stop) read CURRENT_ACTOR_WASM via set_current_actor - a different location. Fix: call crate::actor::set_current_actor() instead, which writes the canonical slot actor.rs reads on both wasm32 and native. Bug #2 - nested/reentrant activation overwrites and clears outer state ACTIVATING, CURRENT_ACTOR, PREV_ARENA, CURRENT_REPLY_CHANNEL were single-slot globals. Nested activation (hew_actor_ask -> hew_sched_run -> activate_actor_wasm) overwrote and then zeroed them, leaving the outer dispatch with null current actor and cleared state. Fix: save all per-activation globals at entry and restore on exit. The private CURRENT_ACTOR static is removed. Tests added: self_api_sees_current_actor_during_dispatch and nested_activation_preserves_outer_actor in scheduler_wasm::tests. All 903 hew-runtime unit tests pass; clippy clean.
slepp
added a commit
that referenced
this pull request
Mar 31, 2026
… current-actor slot (#430) Two verified bugs in the WASM scheduler were causing incorrect behaviour: Bug #1 - self APIs see null/0 during WASM dispatch scheduler_wasm::activate_actor_wasm wrote its own private CURRENT_ACTOR static, while actor.rs self APIs (hew_actor_self, hew_actor_self_pid, hew_actor_self_stop) read CURRENT_ACTOR_WASM via set_current_actor - a different location. Fix: call crate::actor::set_current_actor() instead, which writes the canonical slot actor.rs reads on both wasm32 and native. Bug #2 - nested/reentrant activation overwrites and clears outer state ACTIVATING, CURRENT_ACTOR, PREV_ARENA, CURRENT_REPLY_CHANNEL were single-slot globals. Nested activation (hew_actor_ask -> hew_sched_run -> activate_actor_wasm) overwrote and then zeroed them, leaving the outer dispatch with null current actor and cleared state. Fix: save all per-activation globals at entry and restore on exit. The private CURRENT_ACTOR static is removed. Tests added: self_api_sees_current_actor_during_dispatch and nested_activation_preserves_outer_actor in scheduler_wasm::tests. All 903 hew-runtime unit tests pass; clippy clean.
slepp
added a commit
that referenced
this pull request
Apr 11, 2026
The test is timing-sensitive on Windows CI: it polls hew_supervisor_get_child in the second crash iteration, but on a heavily-loaded runner the scheduler can take longer than the 5-second window to process the supervisor restart after crash #1. When that happens the inner poll exits via timeout and crash #2 is never injected, leaving the crash log at 1 entry instead of 2. This is the same class of jitter already covered by the connection_drop_wakes_pending_remote_ask retry. One retry absorbs a single transient hiccup without hiding real circuit-breaker regressions. Observed: run 24272197851, job 70879288506 (Windows) — 12.09s, before=0 after=1. Baseline on main: PASS 0.109s (run 24271597476, job 70877552018). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
slepp
added a commit
that referenced
this pull request
Apr 11, 2026
* runtime: fix two race-window bugs in hew_node_stop/node_inbound_router Fix 1 – spawn-window close (node_inbound_router): After INBOUND_ASK_ACTIVE is incremented, check shutdown_started before spawning a worker thread. If the flag is already set the counter is decremented and the function returns without spawning, so no new workers are created once node teardown has marked the connection manager as stopping. Fix 2 – per-conn_mgr drain (hew_node_stop): Replacing the old global-INBOUND_ASK_ACTIVE drain (which caused node_stop_wakes_pending_remote_ask to wait for workers on a different node in multi-node tests) with a per-conn_mgr AtomicUsize. Added field HewConnMgr.inbound_ask_active (Arc<AtomicUsize>) and accessor hew_connmgr_inbound_ask_active(). InboundAskGuard is now a tuple struct carrying an Arc clone of the per-manager counter; its Drop impl decrements the global first, then the per-manager counter (the drain watches per-manager, so global is always decremented before per-manager reaches zero – no false-idle window). hew_node_stop captures the Arc before hew_connmgr_free, then spins (≤ 5 s) on the per-manager counter after the free returns. Because hew_connmgr_free joins the reader threads first, no new router calls can happen after the free; the drain therefore terminates as soon as all already-running workers exit. Regression tests added: * inbound_router_no_spawn_after_shutdown_started – marks conn_mgr stopping, calls the router directly, asserts neither the global nor the per-manager counter changes. * node_stop_drains_inbound_ask_active – artificially inflates both counters, has a background thread decrement them after 60 ms, and asserts that hew_node_stop waited (elapsed ≥ 40 ms) for the drain. Updated two existing guard unit tests (inbound_ask_guard_decrements_on_drop, inbound_ask_guard_pair_decrements_twice) to use the new tuple-struct signature. * ci(nextest): add retry for circuit_breaker_trips_on_repeated_crashes The test is timing-sensitive on Windows CI: it polls hew_supervisor_get_child in the second crash iteration, but on a heavily-loaded runner the scheduler can take longer than the 5-second window to process the supervisor restart after crash #1. When that happens the inner poll exits via timeout and crash #2 is never injected, leaving the crash log at 1 entry instead of 2. This is the same class of jitter already covered by the connection_drop_wakes_pending_remote_ask retry. One retry absorbs a single transient hiccup without hiding real circuit-breaker regressions. Observed: run 24272197851, job 70879288506 (Windows) — 12.09s, before=0 after=1. Baseline on main: PASS 0.109s (run 24271597476, job 70877552018). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This was referenced Apr 20, 2026
slepp
added a commit
that referenced
this pull request
Jul 27, 2026
…t probes
The oracle was wrong, not the answer. `hew_tls_read_result` DOES transfer its
buffer; `result-retention = "transferred"` and the mint it licenses stand
unchanged. What was broken was the instrument.
MEASURED ON LINUX, not inferred from macOS. Ubuntu 26.04, glibc 2.43.
before: SIGABRT [1.522s] hew-std tls::tests::read_result_transfers_the_decrypted_buffer
tcache_thread_shutdown(): unaligned tcache chunk detected
after: PASS [0.406s] hew-std tls::tests::read_result_transfers_the_decrypted_buffer
THE CORRUPTING ACCESS, named. Valgrind on the same binary:
Invalid read of size 4
at core::sync::atomic::Atomic<u32>::fetch_sub
by hew_bytes_drop (bytes.rs:319)
by read_result_transfers_the_decrypted_buffer (tls.rs:1882) <- R3
Address 0x6821360 is 0 bytes inside a block of size 24 free'd
by hew_bytes_drop (bytes.rs:325)
by read_result_transfers_the_decrypted_buffer (tls.rs:1875) <- R2 counterfactual
Block was alloc'd at
by alloc_buf (bytes.rs:151) <- hew_bytes_from_static <- read_tls_vec
<- hew_tls_read <- hew_tls_read_result
`fetch_sub` is a read-modify-WRITE, so R3's release stores 0xFFFFFFFF over the
first four bytes of a chunk already on the tcache free list — the low half of
its `next` link. glibc walks that list at thread exit and finds the link
unaligned. macOS never noticed because libmalloc does not police a per-thread
free list whose link lives at offset 0 of the freed block.
WHY THE BLOCK WAS ALREADY FREE. Refcount trace of the R2 counterfactual,
instrumented on the same host at the real allocation:
alloc rc=1
probe #1 -> true rc=1
clone_ref rc=2
probe #2 -> false rc=1 <- the probe consumed the counterfactual's ref
tls.rs:1875 drop rc=0 <- FREED here, one release early
tls.rs:1882 drop <- the write above, into the freed chunk
`bytes_owner_is_sole` pushes into a COPY of the triple to keep the caller's
triple aimed at the original. On the shared reading `hew_bytes_push` reaches
`ensure_unique`, which forks a private buffer and RELEASES the pusher's
reference to the original — correct for an ordinary caller, which is moving its
value forward onto the fork, and wrong for a probe that deliberately kept a
second triple on the original. The probe dropped the fork but never put that
reference back, so the counterfactual's `clone_ref` was cancelled by the probe
rather than by the release written to balance it.
THE FIX. The probe is now owner-count neutral: on the fork path it retains the
original before releasing the fork, so both allocations leave with the count
they arrived with. Its premise is also asserted properly — `offset == 0` as
well as `len < MIN_CAPACITY`, since "a push cannot grow this" is a fact about
`offset + len` against capacity, not about `len` alone.
IT STILL DISCRIMINATES. Verified by mutation on Linux: with `read_tls_vec`
patched to retain the buffer it returns, the fixed probe fails as intended —
panicked at hew-std/src/tls.rs:1874:
the first result must be solely owned by the caller
and the counterfactual inside the passing test still reports NOT sole with a
second reference outstanding. The `result-retention` row keeps its teeth.
THE TRAP IS PINNED WHERE IT LIVES. `hew_bytes_push` and `ensure_unique` now
state that the copy-on-write path consumes the caller's reference, and
`bytes::tests::cow_push_consumes_the_pushers_reference_to_the_original` asserts
it against the refcount word so the next probe author reads it as a fact rather
than deriving it from the implementation.
Valgrind after: zero invalid accesses, zero definitely-lost. The four remaining
contexts are pre-existing rustls/ring uninitialised-padding reports, present
before this change. Full hew-std + hew-runtime on Linux: 3391/3391 pass.
slepp
added a commit
that referenced
this pull request
Jul 27, 2026
…t probes
The oracle was wrong, not the answer. `hew_tls_read_result` DOES transfer its
buffer; `result-retention = "transferred"` and the mint it licenses stand
unchanged. What was broken was the instrument.
MEASURED ON LINUX, not inferred from macOS. Ubuntu 26.04, glibc 2.43.
before: SIGABRT [1.522s] hew-std tls::tests::read_result_transfers_the_decrypted_buffer
tcache_thread_shutdown(): unaligned tcache chunk detected
after: PASS [0.406s] hew-std tls::tests::read_result_transfers_the_decrypted_buffer
THE CORRUPTING ACCESS, named. Valgrind on the same binary:
Invalid read of size 4
at core::sync::atomic::Atomic<u32>::fetch_sub
by hew_bytes_drop (bytes.rs:319)
by read_result_transfers_the_decrypted_buffer (tls.rs:1882) <- R3
Address 0x6821360 is 0 bytes inside a block of size 24 free'd
by hew_bytes_drop (bytes.rs:325)
by read_result_transfers_the_decrypted_buffer (tls.rs:1875) <- R2 counterfactual
Block was alloc'd at
by alloc_buf (bytes.rs:151) <- hew_bytes_from_static <- read_tls_vec
<- hew_tls_read <- hew_tls_read_result
`fetch_sub` is a read-modify-WRITE, so R3's release stores 0xFFFFFFFF over the
first four bytes of a chunk already on the tcache free list — the low half of
its `next` link. glibc walks that list at thread exit and finds the link
unaligned. macOS never noticed because libmalloc does not police a per-thread
free list whose link lives at offset 0 of the freed block.
WHY THE BLOCK WAS ALREADY FREE. Refcount trace of the R2 counterfactual,
instrumented on the same host at the real allocation:
alloc rc=1
probe #1 -> true rc=1
clone_ref rc=2
probe #2 -> false rc=1 <- the probe consumed the counterfactual's ref
tls.rs:1875 drop rc=0 <- FREED here, one release early
tls.rs:1882 drop <- the write above, into the freed chunk
`bytes_owner_is_sole` pushes into a COPY of the triple to keep the caller's
triple aimed at the original. On the shared reading `hew_bytes_push` reaches
`ensure_unique`, which forks a private buffer and RELEASES the pusher's
reference to the original — correct for an ordinary caller, which is moving its
value forward onto the fork, and wrong for a probe that deliberately kept a
second triple on the original. The probe dropped the fork but never put that
reference back, so the counterfactual's `clone_ref` was cancelled by the probe
rather than by the release written to balance it.
THE FIX. The probe is now owner-count neutral: on the fork path it retains the
original before releasing the fork, so both allocations leave with the count
they arrived with. Its premise is also asserted properly — `offset == 0` as
well as `len < MIN_CAPACITY`, since "a push cannot grow this" is a fact about
`offset + len` against capacity, not about `len` alone.
IT STILL DISCRIMINATES. Verified by mutation on Linux: with `read_tls_vec`
patched to retain the buffer it returns, the fixed probe fails as intended —
panicked at hew-std/src/tls.rs:1874:
the first result must be solely owned by the caller
and the counterfactual inside the passing test still reports NOT sole with a
second reference outstanding. The `result-retention` row keeps its teeth.
THE TRAP IS PINNED WHERE IT LIVES. `hew_bytes_push` and `ensure_unique` now
state that the copy-on-write path consumes the caller's reference, and
`bytes::tests::cow_push_consumes_the_pushers_reference_to_the_original` asserts
it against the refcount word so the next probe author reads it as a fact rather
than deriving it from the implementation.
Valgrind after: zero invalid accesses, zero definitely-lost. The four remaining
contexts are pre-existing rustls/ring uninitialised-padding reports, present
before this change. Full hew-std + hew-runtime on Linux: 3391/3391 pass.
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
register_stdlib_hew_items,register_user_module,register_file_import_items) silently ignoredItem::Actorvia catch-all_ => {}arms — actors defined in imported modules were never registeredItem::Actorhandling to all three registration paths: type def, receive fn signatures, qualified aliases, and import spec supportcheck_spawnto resolve module-qualified actor names (spawn module.ActorName(args)) viaExpr::FieldAccessTest plan
module_system_test.rscovering bare, glob, and named actor imports.hewfiles (counter_actor_lib.hew+module_actor.hew) that type-check successfullycargo build --workspace)cargo clippy -p hew-types --tests— no new warnings