Keep thread-affine strings on their own thread (fetch unix path, object URLs, worker shell argv)

Closed
DY
3mo ago
+125-72

What does this PR do?

WTF string refcounts are atomic, but an atomized StringImpl belongs to one thread's atom table and must not be destroyed from another. Three places let a bun_core::String that may be such an impl be dropped (or ref'd) off its thread:

  • fetch(url, { unix }): the socket path — Utf8Bytes that can hold a ref into the JS string — was stored in HTTPClient and bitwise-copied to the HTTP thread by ptr::read; two ManuallyDrop "forgets" on the redirect paths and a clear_data() reset kept it from being freed twice. It now has the same shape as hostname: the FetchTasklet owns the bytes on the JS thread and HTTPClient<'a> borrows &'a [u8]. The forgets and the reset are gone; no string type is left in bun_http for this field.
  • URL.createObjectURL: the registry is process-global, but an entry was a straight dupe() of the creating thread's Blob, including its global_this and its name. A worker that fetch()ed/imported/revoked that URL cloned and later dropped the creator's name, and got a Blob stamped with the creator's global. Entries are now stored with a null global and a to_thread_safe() name (one copy per createObjectURL of a named File), and resolve_and_dupe(path, global) stamps the resolving thread's global.
  • $0…$N in Bun.$ inside a worker took retain_wtf_impl on the parent thread's WorkerOptions.argv strings and held them for the interpreter's lifetime; it now copies them via the same worker_option_string helper process.argv uses.

How did you verify your code works?

New worker.test.ts case: a named File object URL created on the main thread is fetched, imported, .blob()'d and revoked from a worker (and the reverse), and the worker's Bun.$ reads its own argv. Debug+ASAN build driven through fetch over a unix socket, fetch.unix.test.ts, blob.test.ts, bunshell.test.ts, worker.test.ts, serve.test.ts.