diff --git a/cranelift/runtime/src/process.rs b/cranelift/runtime/src/process.rs index b9d90b07..06ea73bf 100644 --- a/cranelift/runtime/src/process.rs +++ b/cranelift/runtime/src/process.rs @@ -146,8 +146,9 @@ fn pith_store_process_handle(mut child: Child) -> i64 { } fn pith_strdup_string(text: &str) -> *mut i8 { - let owned = format!("{}\0", text); - unsafe { crate::pith_strdup(owned.as_ptr() as *const i8) } + // the shared helper allocates from the known length without probing the + // rust buffer for a header it cannot have (see runtime_core). + crate::runtime_core::pith_strdup_string(text) } unsafe fn pith_build_command( diff --git a/cranelift/runtime/src/runtime_core.rs b/cranelift/runtime/src/runtime_core.rs index 2121a2bd..415c66c9 100644 --- a/cranelift/runtime/src/runtime_core.rs +++ b/cranelift/runtime/src/runtime_core.rs @@ -4,8 +4,16 @@ use crate::string; use std::alloc::{alloc, Layout}; pub(crate) fn pith_strdup_string(text: &str) -> *mut i8 { - let owned = format!("{}\0", text); - unsafe { pith_strdup(owned.as_ptr() as *const i8) } + // the length is already known, so allocate the headered cstring directly. + // routing through pith_strdup would probe 16 bytes in front of the rust + // string's buffer looking for a header no rust allocation has — a read of + // memory this process does not own whenever the buffer starts its heap + // block. pith_alloc_cstring writes the nul terminator itself. + unsafe { + let result = pith_alloc_cstring(text.len()); + std::ptr::copy_nonoverlapping(text.as_ptr() as *const i8, result, text.len()); + result + } } #[no_mangle] diff --git a/tests/cases/test_tls_concurrent_resumption.pith b/tests/cases/test_tls_concurrent_resumption.pith index bbf5e749..ba50ed14 100644 --- a/tests/cases/test_tls_concurrent_resumption.pith +++ b/tests/cases/test_tls_concurrent_resumption.pith @@ -14,6 +14,7 @@ # resumed. a torn store shows up as a failed dial, a wrong echo, or a hang. import std.net.tls as tls +import std.time as time CERT_PATH := "tests/live/fixtures/localhost.crt" KEY_PATH := "tests/live/fixtures/localhost.key" @@ -60,11 +61,26 @@ fn dial_once(cfg: tls.Config, tag: String) -> Bool: return false return got_r.ok == "echo:" + tag +# a dial that fails transiently — a starved accept loop on a loaded box, a +# refused connect while the backlog drains — is retried a few times with a +# short backoff. the property under test is the session stores staying +# coherent under concurrent handshakes, and a retried dial exercises them +# exactly as well; without the retry, one transient socket failure on a busy +# ci runner breaks the exact-count output for reasons unrelated to the lock. +fn dial_with_retry(cfg: tls.Config, tag: String) -> Bool: + mut attempt := 0 + while attempt < 5: + if dial_once(cfg, tag): + return true + attempt = attempt + 1 + time.delay(20 * attempt) + return false + fn worker(id: Int, cfg: tls.Config, done: Channel[Int]): mut ok := 0 mut i := 0 while i < DIALS_PER_WORKER: - if dial_once(cfg, "w" + id.to_string() + "-" + i.to_string()): + if dial_with_retry(cfg, "w" + id.to_string() + "-" + i.to_string()): ok = ok + 1 i = i + 1 done.send(ok)