Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions changelog.d/8546-multi-app-fetch-scanner.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
### Fixed

- Preserve Fetch `Request`, `Headers`, and `FormData` registry roots for every
application thread in an embedded multi-app host. Previously a
process-global registration latch could install the Fetch scanner only for
the first Perry heap, so a second in-process Next application could reject
its HTTP handler and return 500. Add a two-application-thread regression test
for the per-thread scanner contract. (#8546)
13 changes: 13 additions & 0 deletions crates/perry-runtime/src/gc/roots.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,19 @@ pub(super) fn copy_only_root_scanner_counts() -> (usize, usize) {
(rust_scanners, ffi_scanners)
}

/// Return the number of named native-package mutable-root scanners registered
/// for the current Perry thread.
///
/// The registry is deliberately thread-local: an embedding host may run
/// multiple independent Perry heaps in one process, and each heap's collector
/// must see the scanners installed by its stdlib provider. This small
/// diagnostic surface lets provider tests verify that contract without running
/// a collection over fabricated heap pointers.
#[doc(hidden)]
pub fn gc_named_ffi_mutable_root_scanner_count() -> usize {
FFI_NAMED_MUTABLE_ROOT_SCANNERS.with(|scanners| scanners.borrow().len())
}

pub(super) fn registered_root_scanners_block_budgeted_gc() -> bool {
let has_copy_only = ROOT_SCANNERS.with(|scanners| !scanners.borrow().is_empty())
|| FFI_ROOT_SCANNERS.with(|scanners| !scanners.borrow().is_empty());
Expand Down
37 changes: 37 additions & 0 deletions crates/perry-stdlib/src/fetch/tests.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,42 @@
use super::*;

/// #8546: Coop hosts each in-process deployment on its own dedicated Perry
/// thread. The Fetch scanner registry is thread-local, so a process-global
/// registration latch makes the first Next application safe and leaves the
/// second application's Request / Headers roots invisible to its collector.
///
/// Run the two applications sequentially so this test deterministically fails
/// with the old `Once`: app 1 consumes the process-global latch, then app 2
/// starts with an empty thread-local scanner registry and cannot register.
#[test]
fn fetch_root_scanner_registers_for_each_application_thread() {
for application in 1..=2 {
let (before, after_first, after_second) = std::thread::spawn(|| {
let before = perry_runtime::gc::gc_named_ffi_mutable_root_scanner_count();
gc::ensure_gc_registered();
let after_first = perry_runtime::gc::gc_named_ffi_mutable_root_scanner_count();
gc::ensure_gc_registered();
let after_second = perry_runtime::gc::gc_named_ffi_mutable_root_scanner_count();
(before, after_first, after_second)
})
.join()
.expect("application thread panicked");

assert_eq!(
before, 0,
"application {application} must start with its own empty scanner registry"
);
assert_eq!(
after_first, 1,
"application {application} did not install the Fetch root scanner"
);
assert_eq!(
after_second, 1,
"application {application} registered the Fetch root scanner twice"
);
}
}

#[test]
fn fetch_handle_ids_use_high_small_handle_range() {
use perry_runtime::value::addr_class;
Expand Down
Loading