You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 7cfe50b
Browse filesBrowse the repository at this point in the historyBrowse files
Copy file name to clipboardExpand all lines: CHANGELOG.md
+6Lines changed: 6 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -39,6 +39,12 @@ All notable changes to this project are documented here. The format loosely foll
39
39
-**`WillAsync` / `CountDownLatch` woke only one awaiter** — the single `Option<Waker>` slot dropped all but the last-registered waker, hanging concurrent awaiters; now stores `Vec<Waker>` and wakes all (draining before waking to stay reentrancy-safe). `WillAsync::start()` now also wakes wakers registered before `start()`, so poll-before-start no longer hangs.
40
40
-**`SubscriptionFunc` buffered every value forever (memory leak)** — under `for_futures`, `on_next` unconditionally pushed each delivered value into an internal `cached` stream, but that stream was open from construction (`LinkedListAsync::new()` starts `alive=true`). A callback-only subscription (the common `subscribe_fn` case) has no stream consumer, so the buffer grew without bound. The `cached` stream now starts **closed** and only begins buffering once `as_stream()` opens it — matching the documented open/close intent. Streaming (`subscribe_as_stream`) is unaffected. Regression-guarded by `test_common_subscription_func_no_buffer_until_stream_opened`.
41
41
-**`BlockingQueue::stop` doc/dead-code mismatch** — the method and module docs claimed it "drops the sender side" to close the queue, but the code did `let sender = …lock().unwrap(); drop(sender);` which dropped the `MutexGuard`, not the `Sender` (which lives behind `Arc<Mutex<..>>` and cannot be dropped there). Removed the misleading no-op and corrected both docs to state the real mechanism: `stop()` flips the `alive` flag (gating later `offer`/`take`), and a consumer already blocked in `take()` stays blocked until its own `timeout`. No behavior change (verified by `test_sync_blocking_queue_is_alive_and_stop`).
42
+
-**`HandlerThreadInner::post` accepted work after stop** — `Handler::stop` promised "stop accepting new jobs," but `post()` always enqueued into the inner `BlockingQueue`. A post after stop could leak forever into an un-drained queue or wake a worker blocked in `take()` and execute one more job after stop. `post()` now rejects only the stopped state (`started && !alive`), preserving pre-start queueing. Regression-guarded by `test_handler_inner_post_after_stop_is_rejected` and `test_handler_post_before_start_is_preserved`.
43
+
-**`ActorAsync::stop` left mailbox handles accepting sends** — stopping an actor cleared the actor alive flag but left the mailbox `BlockingQueue` alive, so `HandleAsync::send` after stop still queued messages that would never be processed. `stop()` now also stops the mailbox queue so later sends are rejected, while still not interrupting a thread already blocked in `take()` (shutdown redesign remains deferred). Regression-guarded by `test_actor_handle_send_after_stop_is_rejected`.
44
+
-**`CountDownLatch::wait` released only one blocking waiter** — `countdown()` used `Condvar::notify_one()`, so when the count reached zero only one waiting thread was guaranteed to wake. It now uses `notify_all()` at zero, matching Java `CountDownLatch` semantics and the existing async multi-waker behavior. Regression-guarded by `test_sync_countdownlatch_releases_all_waiters`.
45
+
-**Reentrant waker deadlock risks** — `WillAsync`, `CountDownLatch`, and `LinkedListAsync` now release state/waker locks before invoking `wake()`/`wake_all_wakers()`/`notify_all()`, so synchronous re-polling wakers cannot block on the same non-reentrant mutexes during wakeup. Verified by existing async stream/future tests.
46
+
-**`Cor::stop` vestigial no-op** — `stop()` ran `drop(self.op_ch_sender.lock().unwrap());`, which (like the old `BlockingQueue::stop`) dropped the `MutexGuard`, not the shared `Sender`, so it never closed the op channel. Removed the no-op and clarified that cooperative stop is delivered solely by the `alive` flag (a `yield_from` to a stopped `Cor` returns `None` because `receive()` early-returns). Behavior-preserving; also removes one lock held under `started_alive`. Verified by `test_cor_*` (pure + `test_runtime`).
47
+
-**`Publisher::delete_observer` closed streams for non-members and duplicates too early** — stream closure happened before confirming the subscription belonged to that publisher, so deleting a non-member could close another publisher's stream. With duplicate registrations, removing one occurrence closed the shared stream while another observer still remained. `delete_observer` now closes only after removing a matching observer, and only when no matching observer remains in that publisher. Regression-guarded by `test_publisher_delete_non_member_does_not_close_subscription_stream` and `test_publisher_delete_duplicate_keeps_stream_open_until_last_observer`.
0 commit comments