diff --git a/crates/cellule-ltx/src/capture/wal.rs b/crates/cellule-ltx/src/capture/wal.rs index 051b3ed9..7cbd8bd2 100644 --- a/crates/cellule-ltx/src/capture/wal.rs +++ b/crates/cellule-ltx/src/capture/wal.rs @@ -91,7 +91,7 @@ impl crate::environment::FileIo for TimedFileIo { fn add_elapsed(total: &AtomicU64, started: Instant, finished: Instant) { let elapsed = nanos(finished.saturating_duration_since(started)); - let _ = total.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current| { + let _ = total.try_update(Ordering::Relaxed, Ordering::Relaxed, |current| { Some(current.saturating_add(elapsed)) }); } diff --git a/crates/cellule-ltx/src/environment/host/budget.rs b/crates/cellule-ltx/src/environment/host/budget.rs index c3e6648d..8c034f27 100644 --- a/crates/cellule-ltx/src/environment/host/budget.rs +++ b/crates/cellule-ltx/src/environment/host/budget.rs @@ -113,7 +113,7 @@ impl DiskBudget { fn add(&self, bytes: u64) -> crate::Result<()> { self.inner .used - .fetch_update(Ordering::AcqRel, Ordering::Acquire, |used| { + .try_update(Ordering::AcqRel, Ordering::Acquire, |used| { used.checked_add(bytes) .filter(|next| *next <= self.inner.capacity) }) @@ -168,7 +168,7 @@ impl DiskBudget { fn remove(&self, bytes: u64) -> crate::Result<()> { self.inner .used - .fetch_update(Ordering::AcqRel, Ordering::Acquire, |used| { + .try_update(Ordering::AcqRel, Ordering::Acquire, |used| { used.checked_sub(bytes) }) .map(|_| ()) diff --git a/crates/cellule-runtime/src/cell/actor/admission.rs b/crates/cellule-runtime/src/cell/actor/admission.rs index 3d25c6a8..742c302c 100644 --- a/crates/cellule-runtime/src/cell/actor/admission.rs +++ b/crates/cellule-runtime/src/cell/actor/admission.rs @@ -17,7 +17,7 @@ pub(super) fn finish_shutdown(shutdown: &mut ShutdownState) { } pub(super) fn subtract_unpublished_bytes(total: &AtomicU64, bytes: u64) { - let _ = total.fetch_update(Ordering::AcqRel, Ordering::Acquire, |current| { + let _ = total.try_update(Ordering::AcqRel, Ordering::Acquire, |current| { Some(current.saturating_sub(bytes)) }); } diff --git a/crates/cellule-runtime/tests/runtime/lifecycle.rs b/crates/cellule-runtime/tests/runtime/lifecycle.rs index 2f98ddb5..e8f4c391 100644 --- a/crates/cellule-runtime/tests/runtime/lifecycle.rs +++ b/crates/cellule-runtime/tests/runtime/lifecycle.rs @@ -191,7 +191,7 @@ impl ObjectStore for PausingStore { ) -> object_store::Result { if self .transient_put_failures - .fetch_update(Ordering::AcqRel, Ordering::Acquire, |remaining| { + .try_update(Ordering::AcqRel, Ordering::Acquire, |remaining| { remaining.checked_sub(1) }) .is_ok() @@ -268,7 +268,7 @@ impl ObjectStore for PausingStore { if options.range.is_some() && self .transient_get_failures - .fetch_update(Ordering::AcqRel, Ordering::Acquire, |remaining| { + .try_update(Ordering::AcqRel, Ordering::Acquire, |remaining| { remaining.checked_sub(1) }) .is_ok() diff --git a/crates/cellule-runtime/tests/runtime/publication.rs b/crates/cellule-runtime/tests/runtime/publication.rs index 24ceeed8..5cabc1b9 100644 --- a/crates/cellule-runtime/tests/runtime/publication.rs +++ b/crates/cellule-runtime/tests/runtime/publication.rs @@ -119,7 +119,7 @@ impl ObjectStore for LostUpdateResponseStore { self.updates.fetch_add(1, Ordering::SeqCst); if self .remaining_failures - .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |remaining| { + .try_update(Ordering::SeqCst, Ordering::SeqCst, |remaining| { remaining.checked_sub(1) }) .is_ok() diff --git a/crates/cellule-store/src/read_admission/tests.rs b/crates/cellule-store/src/read_admission/tests.rs index dc88f4dd..2820094b 100644 --- a/crates/cellule-store/src/read_admission/tests.rs +++ b/crates/cellule-store/src/read_admission/tests.rs @@ -29,7 +29,7 @@ fn charge( amount: u64, ) -> Result<(), Box> { counter - .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |left| { + .try_update(Ordering::SeqCst, Ordering::SeqCst, |left| { left.checked_sub(amount) }) .map(|_| ()) diff --git a/crates/cellule-store/src/store.rs b/crates/cellule-store/src/store.rs index ec0a3509..24b891f4 100644 --- a/crates/cellule-store/src/store.rs +++ b/crates/cellule-store/src/store.rs @@ -3108,7 +3108,7 @@ mod tests { if !options.head && let ReadFault::Transient(remaining_failures) = &self.fault && remaining_failures - .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |remaining| { + .try_update(Ordering::Relaxed, Ordering::Relaxed, |remaining| { remaining.checked_sub(1) }) .is_ok() @@ -3264,7 +3264,7 @@ mod tests { self.aborts.fetch_add(1, Ordering::Relaxed); if self .abort_failures - .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |remaining| { + .try_update(Ordering::Relaxed, Ordering::Relaxed, |remaining| { remaining.checked_sub(1) }) .is_ok() diff --git a/crates/cellule-store/src/transport_read_admission.rs b/crates/cellule-store/src/transport_read_admission.rs index c0ac21a5..0c9b31e7 100644 --- a/crates/cellule-store/src/transport_read_admission.rs +++ b/crates/cellule-store/src/transport_read_admission.rs @@ -391,7 +391,7 @@ mod tests { async fn request(&self) -> Result<(), Box> { self.remaining - .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |remaining| { + .try_update(Ordering::SeqCst, Ordering::SeqCst, |remaining| { remaining.checked_sub(1) }) .map(|_| ()) diff --git a/scripts/check-web-rust-examples.py b/scripts/check-web-rust-examples.py index 11141e94..b22ed62b 100644 --- a/scripts/check-web-rust-examples.py +++ b/scripts/check-web-rust-examples.py @@ -18,45 +18,82 @@ ROOT = Path(__file__).resolve().parents[1] -def main() -> int: - documents = [ - path - for path in sorted((ROOT / "apps/web/content/authored").rglob("*.mdx")) - if "```rust" in path.read_text() - ] - if not documents: - print("error: no authored Rust examples found", file=sys.stderr) - return 1 +def build_libraries(packages: list[str], expected: set[str]) -> dict[str, Path] | None: + command = ["cargo", "build"] + for package in packages: + command.extend(["-p", package]) + command.extend(["--lib", "--locked", "--message-format=json"]) build = subprocess.run( - ["cargo", "build", "-p", "cellule-app", "--lib", "--locked", "--message-format=json"], + command, cwd=ROOT, stdout=subprocess.PIPE, text=True, ) if build.returncode: - return build.returncode + return None + libraries: dict[str, Path] = {} for line in build.stdout.splitlines(): artifact = json.loads(line) if artifact.get("reason") != "compiler-artifact": continue name = artifact["target"]["name"] - if name not in ("cellule_app", "cellule_runtime"): + if name not in expected: continue for filename in artifact["filenames"]: if filename.endswith(".rlib"): libraries[name] = Path(filename) - if set(libraries) != {"cellule_app", "cellule_runtime"}: - print("error: Cargo did not report both framework libraries", file=sys.stderr) - return 1 + + missing = expected - libraries.keys() + if missing: + print( + "error: Cargo did not report required example libraries: " + + ", ".join(sorted(missing)), + file=sys.stderr, + ) + return None + return libraries + + +def rustdoc_arguments(libraries: dict[str, Path]) -> list[str]: arguments = [] for name, file in libraries.items(): arguments.extend(["--extern", f"{name}={file}"]) for directory in sorted({file.parent for file in libraries.values()}): arguments.extend(["-L", f"dependency={directory}"]) + return arguments + + +def main() -> int: + documents = [ + path + for path in sorted((ROOT / "apps/web/content/authored").rglob("*.mdx")) + if "```rust" in path.read_text() + ] + if not documents: + print("error: no authored Rust examples found", file=sys.stderr) + return 1 + framework_libraries = build_libraries( + ["cellule-app", "cellule-types"], + {"cellule_app", "cellule_runtime", "cellule_types"}, + ) + if framework_libraries is None: + return 1 + store_libraries = build_libraries( + ["cellule-store"], + {"cellule_store", "cellule_types", "bytes", "object_store"}, + ) + if store_libraries is None: + return 1 failures = [] for document in documents: relative = document.relative_to(ROOT) + # Build this group separately so direct imports such as bytes::Bytes + # use the same feature-specific crate artifact as cellule-store. + libraries = ( + store_libraries if "cellule_store::" in document.read_text() else framework_libraries + ) + arguments = rustdoc_arguments(libraries) # rustdoc recognizes .md as Markdown; .mdx is otherwise parsed as Rust. # Copy unchanged text so fence line numbers still match the source. with tempfile.TemporaryDirectory(prefix="cellule-doc-examples-") as temporary: