Skip to content

[Next.js/dylib] tests/test_next_app_route_dylib.sh aborts on the first request: Rust provider-host + flat_namespace bind the stdlib image's __rust_dealloc to the System shim (mimalloc buffer freed by libsystem) #8205

Description

@proggeramlug

Symptom

tests/test_next_app_route_dylib.sh (the #8161 gate behind next-app-route.yml, which has never had a run yet) fails on cold start 1, first request, on clean main 3c95020f8:

PERRY_NEXT_APP_ROUTE_READY http://127.0.0.1:3180
tests/test_next_app_route_dylib.sh: line 273: 37574 Abort trap: 6   … "$host" "$runtime_library" "$stdlib_library" "$app"

Node oracle PASS, app-only dylib compiles, provider images link, ABI check passes (missing-symbols empty), host reaches READY, then the readiness curl aborts the process. Measured 2026-08-16 on the bench mini (macOS 26.5.1, LLVM 22.1.8, perry-dev, script defaults). The same commit, same compiler, same app source, served 500 verifier batches through routeModule.handle minutes later under tests/release/packages/next-app-route/fixture.sh (see #8163 / #8040 comments), so this is the fixture layout, not the compiler or the route.

Crash report (~/Library/Logs/DiagnosticReports/provider-host-2026-08-16-030353.ips)

libsystem_malloc  ___BUG_IN_CLIENT_OF_LIBMALLOC_POINTER_BEING_FREED_WAS_NOT_ALLOCATED
libperry_stdlib.dylib  <alloc::vec::IntoIter<i64> as Drop>::drop   (perry_ffi)
libperry_stdlib.dylib  js_node_http_server_process_pending
libperry_stdlib.dylib  js_stdlib_process_pending
libperry_runtime.dylib js_run_stdlib_pump
provider-host          provider_host::main

Mechanism (symbol evidence, nm on the kept images)

  • libperry_runtime.dylib defines the rustc allocator shim and it is mimalloc: __RNv…7___rustc14___rust_dealloc: b _mi_free (_mi_free is a local t in the runtime image).
  • libperry_stdlib.dylib imports __rust_alloc/__rust_dealloc/__rust_alloc_zeroed (U), as it should.
  • provider-host (built from provider-host.rs, a Rust binary) ALSO defines __RNv…7___rustc14___rust_dealloc — the System-allocator shim every Rust executable carries.
  • provider-linker.sh links the provider with -Wl,-flat_namespace -Wl,-interposable. In a flat namespace an undefined symbol binds to the first image that defines it, and the main executable is first — so the stdlib image's __rust_dealloc binds to the host's System shim while allocations that came out of the runtime image were made by mimalloc. The first cross-image Vec<i64> drop (js_node_http_server_process_pending draining the pending-request list) hands a mimalloc pointer to libsystem free() → abort.

tests/release/packages/next-app-route/ does not have this problem: its host is C (provider-host.c, no Rust shims) and its images are linked with -undefined dynamic_lookup (two-level namespace), so __rust_dealloc in the stdlib image resolves to the runtime's mimalloc shim.

Fix options (either is small; both are in tests/fixtures/next-app-route/)

  1. Make the host C (or link provider-host.rs with -C prefer-dynamic/no std shims — awkward); the release-tier provider-host.c is drop-in.
  2. Drop -flat_namespace -interposable from provider-linker.sh and let two-level namespace bind __rust_* to the runtime image (keep the exported-symbols list).

Either way the gate then needs a first green run before next-app-route.yml means anything; today its first scheduled run (03:17 UTC 2026-08-17) will be red for this reason. Longer term the two copies of the #8034 fixture should converge on one layout (the release one is the one with production evidence; the tests/fixtures/ one has the armed bypass guard — porting the guard over is a one-file copy, verified working in the runs above).

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugConfirmed defect or regression

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions