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
Harden shared_thread_pool singleton and fix rustdoc links in default build
Replace static mut + transmute in shared_thread_pool() with AtomicPtr
published via Once/Release-Acquire (edition-2024 safe, MSRV unchanged).
Fix broken intra-doc links that only resolved under for_futures, add a
pure cargo doc gate to CI, and correct BlockingQueue/Cor/README docs to
match the code.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Copy file name to clipboardExpand all lines: CHANGELOG.md
+8-1Lines changed: 8 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -17,6 +17,8 @@ All notable changes to this project are documented here. The format loosely foll
17
17
- README CI status badge (renders live once the workflow runs on the default branch); README now states edition 2021 / MSRV 1.56.
18
18
-[docs/PROJECT_NOTES.md](docs/PROJECT_NOTES.md) — project origins (three-era history), cross-language family (`fpGo`/`fpEs`), and a catalog of less-known/"leaked" public APIs (`observe_on`/`subscribe_on` scheduler routing + its no-op trap, `subscribe_blocking_queue` push→pull bridge, `do_m_pattern!`, `map_insert!`, coroutine macros) with scenarios and anti-patterns. README gained a "Less-known / advanced capabilities" section pointing to it.
19
19
- Two runnable examples for previously undemonstrated "leaked" capabilities: `examples/scheduler.rs` (`MonadIO::observe_on`/`subscribe_on` thread-hopping, with the `subscribe_on`-alone no-op trap proven by asserting the delivering `ThreadId`) and `examples/publisher_queue.rs` (`Publisher::as_blocking_queue` push→pull bridge). `examples/cor.rs` gained comments documenting the sync/async `yield_from` deadlock invariant.
20
+
-**Cargo.toml** — `[package.metadata.docs.rs]` with `features = ["for_futures"]` so docs.rs documents the async/Rx surface (`Future`/`Stream` impls, `*_as_future`, `MonadIO::to_future`, `Publisher` stream helpers) instead of only the default `pure` API.
21
+
- **docs/PROJECT_NOTES.md** — validated and corrected against git/API ground truth via a parallel multi-lens review (evidence auditor, provenance historian, leaked-API hunter, scenarios/usability, strategic synthesis). Corrected the commit count (204→206); tagged every provenance attribution as **commit-cited** (Cor←Go `28f8178`, `WillAsync`←JavaFuture `24060c2`, `Reduce`←`dtolnay/reduce` `0a53ac6`, Handler/`RawFunc`←Stack Overflow `1f15ba1`/`93a36f8`, GoF Observer←`eliovir/rust-examples` source comment since `9932452`, Akka-style ask `d173df7`) vs **naming-only [INFERENCE]** (Haskell, Java `java.util.concurrent`, Android `Handler` loop, RxJava schedulers); disambiguated the "leaked" homonym (undocumented-feature `b2e17e5` vs resource-leak `6d801ad`/`40ef803`); split the cross-language family into observed parity intent (`maybe.rs:10`) vs inferred sibling APIs; documented five additional easy-to-miss public APIs (`monadio::of`/`From`, `Publisher::map` side-effect, `Cor::yield_from` cross-type, `LinkedListAsync` `Stream`, `RawFunc`/`RawReceiver`); added a standalone `BlockingQueue` scenario (with the `unbounded`/non-interruptible anti-pattern) and corrected the Publisher scenario (stream style is doctest/test-only).
20
22
21
23
### Changed
22
24
@@ -30,9 +32,11 @@ All notable changes to this project are documented here. The format loosely foll
30
32
- Removed legacy `.travis.yml`.
31
33
-**Cargo.toml** — adopted **edition 2021** and pinned **MSRV `rust-version = "1.56"`**; removed the dead commented `tokio` dependency and feature line.
32
34
-**CI** — added `rustfmt --check` and `rustdoc -D warnings` gates, an MSRV (1.56) job, and a feature-combination build matrix; `clippy.sh` now fails on warnings (`-D warnings`); `publish.sh` refuses a dirty working tree (dropped `--allow-dirty`).
35
+
-**CI** — the `doc` job now also builds `cargo doc --no-deps` (default `pure`, matching docs.rs) under `-D warnings`, not just `--features=test_runtime`, so broken intra-doc links in the default build fail CI.
33
36
-**src/fp.rs** — reimplemented `compose!`, `pipe!`, and `compose_two` from first principles, removing a CC BY-SA-licensed StackOverflow snippet (semantics unchanged).
34
37
-**src/lib.rs** — crate-level docs now list the full 8-module `pure` stack (previously mis-stated as `fp` + `maybe` only).
35
38
- Deleted seven vestigial `thread::sleep` synchronization hacks from tests (each was already gated by a latch/flag/waker/queue); mechanical clippy idiom cleanup across `actor`, `common`, `maybe`, `publisher`.
39
+
-**src/common.rs** — hardened the `shared_thread_pool()` lazy singleton (`for_futures`): replaced `static mut` + `mem::transmute::<Box<_>, *const _>` with an `AtomicPtr` published by a `Release` store inside `Once::call_once` and read by an `Acquire` load. This removes the `Box`→pointer `transmute` and the `static mut` (a hard error under edition 2024), narrowing the `unsafe` block to the single unavoidable leaked-pointer deref. Behavior, public API, and MSRV (1.56) are unchanged (`AtomicPtr`/`Box::into_raw` predate it); guarded by `test_common_shared_thread_pool_concurrent_init_is_singleton`.
36
40
37
41
### Fixed
38
42
@@ -43,8 +47,11 @@ All notable changes to this project are documented here. The format loosely foll
43
47
-**`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
48
-**`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
49
-**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`).
50
+
-**`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`. The module-level doc still claimed cooperative stop "closes channels"; corrected it to match (the shared op channel is not closed). Verified by `test_cor_*` (pure + `test_runtime`).
47
51
-**`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`.
52
+
-**Broken rustdoc intra-doc links in the default (`pure`) build** — `LinkedListAsync`'s `Stream` mention, and `sync` module/`WillAsync`/`CountDownLatch`/`BlockingQueue` links to `Future` and `poll_result_as_future`/`take_result_as_future`, referenced `for_futures`-only items and so failed to resolve when that feature was off. Because docs.rs builds default features and the CI `doc` job only checked `--features=test_runtime`, this shipped undetected: `cargo doc` (and docs.rs) rendered eight broken links. Links to always-available items now use a fully-qualified path (`std::future::Future`); links to `for_futures`-only items are plain code spans. `RUSTDOCFLAGS='-D warnings' cargo doc --no-deps` (pure) now passes.
53
+
-**README claimed `cor_yield_from!` documents the sync-deadlock invariant, but it didn't** — the README ("Known behavior") stated that `cor_start!`, `set_async`, **and**`cor_yield_from!` document the sync↔sync `yield_from` deadlock in `src/cor.rs`, yet only the first two carried the note. Since `cor_yield_from!` is the operation that actually deadlocks and the macro users call, added the invariant to its rustdoc (pointing at `cor_start!` / `set_async`). The `cor_start!` macro doc-links use the explicit `crate::cor_start` path so they resolve regardless of textual position relative to the macro definition.
54
+
-**`BlockingQueue` struct doc contradicted the code (claimed "bounded")** — the rustdoc summary read "Thread-safe **bounded** channel wrapper (Java `BlockingQueue`-like)", but the queue is backed by `mpsc::channel()` (unbounded) and its own `put` note says "there's no maximum size", and `docs/PROJECT_NOTES.md` correctly calls it unbounded. Corrected the summary to state it is **unbounded** and that, unlike a typical bounded Java `BlockingQueue`, `put`/`offer` never block on capacity. Doc-only; verified by `RUSTDOCFLAGS='-D warnings' cargo doc` (pure/for_futures/test_runtime) and the `sync` test suite.
Copy file name to clipboardExpand all lines: README.md
+5-1Lines changed: 5 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -51,7 +51,11 @@ Some public APIs are easy to miss. See [docs/PROJECT_NOTES.md](docs/PROJECT_NOTE
51
51
-**Push→pull bridge** — `Publisher::as_blocking_queue` / `subscribe_blocking_queue` forward published values into a `BlockingQueue` for deterministic pulling; `Publisher::subscribe_on` delivers off-thread.
52
52
-**Sync↔async bridge** — `BlockingQueue::{take,poll}_result_as_future` (`for_futures`) `.await` a blocking queue.
53
53
-**Pattern do-notation** — `do_m_pattern!` extends `do_m!` with typed `let`, reassignment, `exec`, and `ret`.
0 commit comments