From 1de76d3943b10b9831b83019d7a5f8e3cc58d969 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Mon, 10 Aug 2026 04:00:07 +0200 Subject: [PATCH 1/3] fix(gc): release the prototype-registry latch when a prune drains it (#7737) The latch was one-way, so a single Object.setPrototypeOf anywhere in a process permanently disabled #7733's per-evacuated-object fast path. The set moves under the mutex so the clear cannot race an in-flight insert. Claude-Session: https://claude.ai/code/session_01Y1QZ5wUP9gRSwpiweT4Wix --- changelog.d/7739-prototype-latch-drain.md | 17 ++++ .../src/object/prototype_chain.rs | 90 ++++++++++++++++++- 2 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 changelog.d/7739-prototype-latch-drain.md diff --git a/changelog.d/7739-prototype-latch-drain.md b/changelog.d/7739-prototype-latch-drain.md new file mode 100644 index 0000000000..adf604df3e --- /dev/null +++ b/changelog.d/7739-prototype-latch-drain.md @@ -0,0 +1,17 @@ +### Fixed + +- **The prototype registry's fast-path latch was one-way, so a single `Object.setPrototypeOf` disabled it for the life of the process (#7737 item 1).** `OBJECT_PROTOTYPES_NONEMPTY` is set when a non-meta-capable owner records a prototype and was never cleared — **including by `prune_dead_object_prototype_owners` when its `retain()` drains the map back to empty**. + + That became load-bearing with #7733, which added a third reader: the evacuation move hook `object_static_prototype_owner_moved` consults the latch once per moved object to skip a process-global `Mutex` and a SipHash lookup (2.5 M of each on `retain.ts`). So one incidental re-prototyping early in a run — of a `RegExp`, say — silently forfeited that win for every subsequent evacuation, with no signal that it had happened. + + This is **#7510's finding recurring**: *"one immortal side-table entry nullified every `is_empty()` fast path"*, now with a third victim. + + **The clear could not simply be added, and the reason is the interesting part.** The latch was stored *outside* the mutex, deliberately before the insert, so that a reader observing it never misses a committed entry. Clearing under the lock against a set outside it loses entries: + + 1. writer stores `true`; + 2. pruner takes the lock, retains to empty, clears the latch; + 3. writer takes the lock and inserts. + + — leaving a non-empty map with the latch false, which every reader skips. The set therefore moves **under the same mutex**, still before the insert, which serialises (1) and (3) against (2) and makes the interleaving impossible. The publish property is unchanged: a reader that sees `true` takes the lock and so sees whatever the writer committed. No extra cost — that path acquired the lock on the next line anyway. + + The regression test's load-bearing assertion is the **last** one, that the latch comes back down; everything before it passes with the bug present. Verified by removing the clear: *"the registry is empty but the latch is still armed, so every evacuated object keeps paying the mutex + SipHash lookup for the rest of the process"*. diff --git a/crates/perry-runtime/src/object/prototype_chain.rs b/crates/perry-runtime/src/object/prototype_chain.rs index 575ea3b899..05767f9a8f 100644 --- a/crates/perry-runtime/src/object/prototype_chain.rs +++ b/crates/perry-runtime/src/object/prototype_chain.rs @@ -218,11 +218,22 @@ fn object_set_static_prototype_impl(obj_ptr: usize, proto_bits: u64, instance_ov } } let mut slot_addr = 0usize; - // Latch BEFORE the insert: a concurrent `object_static_prototype` that - // observed the latch after the insert-but-before-the-store window would - // skip the mutex and miss an already-recorded prototype. - OBJECT_PROTOTYPES_NONEMPTY.store(true, Ordering::Release); if let Ok(mut map) = get_object_prototypes().lock() { + // Latch BEFORE the insert, and UNDER THE LOCK (#7737). + // + // Before the insert, because a concurrent `object_static_prototype` + // that observed the latch in the insert-but-before-the-store window + // would skip the mutex and miss an already-recorded prototype. + // + // Under the lock, because the latch is now CLEARED when a prune + // empties the map. With the store outside, this interleaving loses an + // entry: writer stores `true`; pruner takes the lock, retains to + // empty, clears the latch; writer then takes the lock and inserts — + // leaving a non-empty map with the latch false, which every reader + // skips. Serialising both under the same mutex makes that impossible. + // The publish property is unchanged: a reader that sees `true` takes + // the lock and therefore sees whatever the writer committed. + OBJECT_PROTOTYPES_NONEMPTY.store(true, Ordering::Release); let slot = map.entry(obj_ptr).or_insert(0); *slot = proto_bits; slot_addr = slot as *mut u64 as usize; @@ -331,6 +342,20 @@ pub(crate) fn prune_dead_object_prototype_owners(is_dead_owner: &dyn Fn(usize) - } if let Ok(mut map) = get_object_prototypes().lock() { map.retain(|owner, _| !is_dead_owner(*owner)); + // #7737: release the latch when the registry drains. + // + // It used to be one-way. Since #7733 the evacuation move hook reads it + // once per moved object, so a SINGLE `Object.setPrototypeOf` against a + // non-meta-capable owner — anywhere in a process's lifetime, even one + // that later dies and is pruned right here — permanently disabled that + // fast path for the rest of the run. That is #7510's "one immortal + // side-table entry nullified every is_empty() fast path" recurring. + // + // Safe to clear here because the set is now under this same lock: no + // insert can be in flight past its latch store while we hold it. + if map.is_empty() { + OBJECT_PROTOTYPES_NONEMPTY.store(false, Ordering::Release); + } } } @@ -497,6 +522,11 @@ pub(crate) fn resolve_inherited_field_from_prototype( } } +#[cfg(test)] +pub(crate) fn test_prototype_registry_latch_armed() -> bool { + OBJECT_PROTOTYPES_NONEMPTY.load(Ordering::Acquire) +} + #[cfg(test)] mod tests { use super::*; @@ -536,3 +566,55 @@ mod tests { assert_eq!(resolution_stack_savepoint(), base_depth); } } + +#[cfg(test)] +mod latch_drain_tests_7737 { + use super::*; + + /// #7737: the registry's "non-empty" latch must be RELEASED when a prune + /// drains the map, not held for the life of the process. + /// + /// Since #7733 the evacuation move hook (`object_static_prototype_owner_moved`) + /// reads this latch once per moved object to skip a process-global mutex + /// and a SipHash lookup. While it was one-way, a single + /// `Object.setPrototypeOf` against a non-meta-capable owner — anywhere in + /// a process's lifetime, including one that dies and is pruned moments + /// later — permanently disabled that fast path for the rest of the run. + /// + /// That is #7510's finding recurring: "one immortal side-table entry + /// nullified every `is_empty()` fast path". The assertion that matters is + /// the LAST one — that the latch comes back down — because everything + /// before it passes with the bug present. + #[test] + fn a_drained_prototype_registry_releases_the_fast_path_latch() { + let _lock = crate::gc::global_side_table_test_lock(); + + // Start from a known state: drain whatever earlier tests recorded. + prune_dead_object_prototype_owners(&|_| true); + + let owner: usize = 0x5000_0000; + let proto_bits: u64 = 0x7FFC_0000_0000_0001; + if let Ok(mut map) = get_object_prototypes().lock() { + OBJECT_PROTOTYPES_NONEMPTY.store(true, Ordering::Release); + map.insert(owner, proto_bits); + } + assert!( + test_prototype_registry_latch_armed(), + "setup: recording an owner must arm the latch" + ); + + // The owner dies and is pruned — the registry is empty again. + prune_dead_object_prototype_owners(&|o| o == owner); + assert!( + get_object_prototypes().lock().map(|m| m.is_empty()).unwrap_or(false), + "setup: the prune must actually have emptied the map" + ); + + assert!( + !test_prototype_registry_latch_armed(), + "#7737: the registry is empty but the latch is still armed, so \ + every evacuated object keeps paying the mutex + SipHash lookup \ + for the rest of the process" + ); + } +} From 53662e9f8540f5304b9f6d3ec3860f66cfde3ffa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Mon, 10 Aug 2026 04:00:08 +0200 Subject: [PATCH 2/3] chore: bump version to 0.5.1432 Claude-Session: https://claude.ai/code/session_01Y1QZ5wUP9gRSwpiweT4Wix --- CLAUDE.md | 2 +- Cargo.lock | 152 ++++++++++++++++++++++++++--------------------------- Cargo.toml | 2 +- 3 files changed, 78 insertions(+), 78 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 3af159d924..30a57dd50e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,7 +8,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co Perry is a native TypeScript compiler written in Rust that compiles TypeScript source code directly to native executables. It uses SWC for TypeScript parsing and LLVM for code generation. -**Current Version:** 0.5.1431 +**Current Version:** 0.5.1432 ## TypeScript Parity Status diff --git a/Cargo.lock b/Cargo.lock index 4646d15d96..f1406f4a65 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5547,7 +5547,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "perry" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "anyhow", "base64", @@ -5607,14 +5607,14 @@ dependencies = [ [[package]] name = "perry-api-manifest" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "serde", ] [[package]] name = "perry-audio-miniaudio" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "cc", "libc", @@ -5622,7 +5622,7 @@ dependencies = [ [[package]] name = "perry-codegen" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "anyhow", "inkwell", @@ -5639,7 +5639,7 @@ dependencies = [ [[package]] name = "perry-codegen-arkts" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "anyhow", "perry-hir", @@ -5647,7 +5647,7 @@ dependencies = [ [[package]] name = "perry-codegen-glance" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "anyhow", "perry-hir", @@ -5655,7 +5655,7 @@ dependencies = [ [[package]] name = "perry-codegen-js" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "anyhow", "perry-dispatch", @@ -5664,7 +5664,7 @@ dependencies = [ [[package]] name = "perry-codegen-swiftui" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "anyhow", "perry-hir", @@ -5672,7 +5672,7 @@ dependencies = [ [[package]] name = "perry-codegen-wasm" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "anyhow", "base64", @@ -5684,7 +5684,7 @@ dependencies = [ [[package]] name = "perry-codegen-wear-tiles" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "anyhow", "perry-hir", @@ -5692,7 +5692,7 @@ dependencies = [ [[package]] name = "perry-container-compose" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "anyhow", "async-trait", @@ -5721,14 +5721,14 @@ dependencies = [ [[package]] name = "perry-container-e2e" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "anyhow", ] [[package]] name = "perry-diagnostics" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "serde", "serde_json", @@ -5736,7 +5736,7 @@ dependencies = [ [[package]] name = "perry-dispatch" -version = "0.5.1431" +version = "0.5.1432" [[package]] name = "perry-doc-fixture-my-bindings" @@ -5747,7 +5747,7 @@ dependencies = [ [[package]] name = "perry-doc-tests" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "anyhow", "clap", @@ -5762,7 +5762,7 @@ dependencies = [ [[package]] name = "perry-ext-ads" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "block2", "objc2", @@ -5772,7 +5772,7 @@ dependencies = [ [[package]] name = "perry-ext-argon2" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "argon2", "perry-ffi", @@ -5780,7 +5780,7 @@ dependencies = [ [[package]] name = "perry-ext-axios" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "perry-ffi", "reqwest", @@ -5789,7 +5789,7 @@ dependencies = [ [[package]] name = "perry-ext-bcrypt" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "bcrypt", "perry-ffi", @@ -5797,7 +5797,7 @@ dependencies = [ [[package]] name = "perry-ext-better-sqlite3" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "perry-ffi", "rusqlite", @@ -5805,7 +5805,7 @@ dependencies = [ [[package]] name = "perry-ext-cheerio" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "perry-ffi", "scraper", @@ -5813,7 +5813,7 @@ dependencies = [ [[package]] name = "perry-ext-commander" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "perry-ffi", "perry-runtime", @@ -5821,7 +5821,7 @@ dependencies = [ [[package]] name = "perry-ext-cron" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "chrono", "cron", @@ -5831,7 +5831,7 @@ dependencies = [ [[package]] name = "perry-ext-dayjs" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "chrono", "perry-ffi", @@ -5839,7 +5839,7 @@ dependencies = [ [[package]] name = "perry-ext-decimal" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "perry-ffi", "rust_decimal", @@ -5847,7 +5847,7 @@ dependencies = [ [[package]] name = "perry-ext-dotenv" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "perry-ffi", "serde_json", @@ -5855,7 +5855,7 @@ dependencies = [ [[package]] name = "perry-ext-ethers" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "perry-ffi", "rand 0.10.1", @@ -5863,7 +5863,7 @@ dependencies = [ [[package]] name = "perry-ext-events" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "perry-ffi", "perry-runtime", @@ -5871,14 +5871,14 @@ dependencies = [ [[package]] name = "perry-ext-exponential-backoff" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-fastify" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "bytes", "http-body-util", @@ -5896,7 +5896,7 @@ dependencies = [ [[package]] name = "perry-ext-fetch" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "bytes", "lazy_static", @@ -5909,7 +5909,7 @@ dependencies = [ [[package]] name = "perry-ext-http" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "bytes", "h2", @@ -5933,7 +5933,7 @@ dependencies = [ [[package]] name = "perry-ext-ioredis" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "lazy_static", "perry-ffi", @@ -5943,7 +5943,7 @@ dependencies = [ [[package]] name = "perry-ext-jsonwebtoken" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "base64", "jsonwebtoken", @@ -5954,7 +5954,7 @@ dependencies = [ [[package]] name = "perry-ext-lru-cache" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "lru", "perry-ffi", @@ -5963,7 +5963,7 @@ dependencies = [ [[package]] name = "perry-ext-moment" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "chrono", "perry-ffi", @@ -5971,7 +5971,7 @@ dependencies = [ [[package]] name = "perry-ext-mongodb" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "bson", "futures-util", @@ -5983,7 +5983,7 @@ dependencies = [ [[package]] name = "perry-ext-mysql2" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "chrono", "perry-ffi", @@ -5993,7 +5993,7 @@ dependencies = [ [[package]] name = "perry-ext-nanoid" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "nanoid", "perry-ffi", @@ -6002,7 +6002,7 @@ dependencies = [ [[package]] name = "perry-ext-net" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "bytes", "perry-ffi", @@ -6015,7 +6015,7 @@ dependencies = [ [[package]] name = "perry-ext-node-forge" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "const-oid 0.9.6", "der 0.7.10", @@ -6034,7 +6034,7 @@ dependencies = [ [[package]] name = "perry-ext-nodemailer" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "lettre", "perry-ffi", @@ -6044,7 +6044,7 @@ dependencies = [ [[package]] name = "perry-ext-pdf" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "perry-ffi", "printpdf", @@ -6052,7 +6052,7 @@ dependencies = [ [[package]] name = "perry-ext-pg" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "perry-ffi", "sqlx", @@ -6061,7 +6061,7 @@ dependencies = [ [[package]] name = "perry-ext-ratelimit" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "governor", "perry-ffi", @@ -6069,7 +6069,7 @@ dependencies = [ [[package]] name = "perry-ext-sharp" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "fast_image_resize", "image", @@ -6079,14 +6079,14 @@ dependencies = [ [[package]] name = "perry-ext-slugify" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-streams" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "lazy_static", "perry-ffi", @@ -6095,7 +6095,7 @@ dependencies = [ [[package]] name = "perry-ext-undici" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "perry-ffi", "perry-runtime", @@ -6104,7 +6104,7 @@ dependencies = [ [[package]] name = "perry-ext-uuid" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "perry-ffi", "uuid", @@ -6112,7 +6112,7 @@ dependencies = [ [[package]] name = "perry-ext-validator" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "perry-ffi", "regex", @@ -6122,7 +6122,7 @@ dependencies = [ [[package]] name = "perry-ext-ws" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "futures-util", "lazy_static", @@ -6135,7 +6135,7 @@ dependencies = [ [[package]] name = "perry-ext-zlib" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "brotli", "flate2", @@ -6145,7 +6145,7 @@ dependencies = [ [[package]] name = "perry-ffi" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "dashmap", "once_cell", @@ -6154,7 +6154,7 @@ dependencies = [ [[package]] name = "perry-hir" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "anyhow", "perry-api-manifest", @@ -6172,7 +6172,7 @@ dependencies = [ [[package]] name = "perry-parser" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "anyhow", "perry-diagnostics", @@ -6184,7 +6184,7 @@ dependencies = [ [[package]] name = "perry-runtime" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "anyhow", "base64", @@ -6226,14 +6226,14 @@ dependencies = [ [[package]] name = "perry-runtime-static" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "perry-runtime", ] [[package]] name = "perry-stdlib" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "aes 0.8.4", "aes 0.9.1", @@ -6328,14 +6328,14 @@ dependencies = [ [[package]] name = "perry-stdlib-static" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "perry-stdlib", ] [[package]] name = "perry-transform" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "anyhow", "perry-hir", @@ -6344,14 +6344,14 @@ dependencies = [ [[package]] name = "perry-ui" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "perry-ui-model", ] [[package]] name = "perry-ui-android" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "base64", "itoa", @@ -6368,7 +6368,7 @@ dependencies = [ [[package]] name = "perry-ui-geisterhand" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "rand 0.10.1", "serde", @@ -6378,7 +6378,7 @@ dependencies = [ [[package]] name = "perry-ui-gtk4" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "base64", "cairo-rs 0.22.0", @@ -6401,7 +6401,7 @@ dependencies = [ [[package]] name = "perry-ui-ios" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "base64", "block2", @@ -6417,7 +6417,7 @@ dependencies = [ [[package]] name = "perry-ui-macos" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "base64", "block2", @@ -6432,7 +6432,7 @@ dependencies = [ [[package]] name = "perry-ui-model" -version = "0.5.1431" +version = "0.5.1432" [[package]] name = "perry-ui-test" @@ -6443,11 +6443,11 @@ dependencies = [ [[package]] name = "perry-ui-testkit" -version = "0.5.1431" +version = "0.5.1432" [[package]] name = "perry-ui-tvos" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "base64", "block2", @@ -6463,7 +6463,7 @@ dependencies = [ [[package]] name = "perry-ui-visionos" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "base64", "block2", @@ -6479,7 +6479,7 @@ dependencies = [ [[package]] name = "perry-ui-watchos" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "block2", "libc", @@ -6492,7 +6492,7 @@ dependencies = [ [[package]] name = "perry-ui-windows" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "base64", "libc", @@ -6509,14 +6509,14 @@ dependencies = [ [[package]] name = "perry-ui-windows-winui" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "perry-ui-windows", ] [[package]] name = "perry-updater" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "anyhow", "base64", @@ -6532,7 +6532,7 @@ dependencies = [ [[package]] name = "perry-wasm-host" -version = "0.5.1431" +version = "0.5.1432" dependencies = [ "wasmi", ] diff --git a/Cargo.toml b/Cargo.toml index 6d4ff3a68d..319169ecdb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -315,7 +315,7 @@ codegen-units = 16 codegen-units = 16 [workspace.package] -version = "0.5.1431" +version = "0.5.1432" edition = "2021" license = "MIT" repository = "https://github.com/PerryTS/perry" From c6c8ee38517b03758d5a664aef9cba6ee60a1a7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Mon, 10 Aug 2026 04:05:05 +0200 Subject: [PATCH 3/3] style: cargo fmt Claude-Session: https://claude.ai/code/session_01Y1QZ5wUP9gRSwpiweT4Wix --- crates/perry-runtime/src/object/prototype_chain.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/perry-runtime/src/object/prototype_chain.rs b/crates/perry-runtime/src/object/prototype_chain.rs index 05767f9a8f..c4d4dee4b7 100644 --- a/crates/perry-runtime/src/object/prototype_chain.rs +++ b/crates/perry-runtime/src/object/prototype_chain.rs @@ -606,7 +606,10 @@ mod latch_drain_tests_7737 { // The owner dies and is pruned — the registry is empty again. prune_dead_object_prototype_owners(&|o| o == owner); assert!( - get_object_prototypes().lock().map(|m| m.is_empty()).unwrap_or(false), + get_object_prototypes() + .lock() + .map(|m| m.is_empty()) + .unwrap_or(false), "setup: the prune must actually have emptied the map" );