Repository: denoland/deno
Pull request: fix(runtime): reduce memory retention after web worker termination

## Summary

Addresses #26058 — Web Workers use significantly more RSS than Chrome, and terminating them doesn't release the memory back to the OS.

Two targeted changes:

- **Call `malloc_trim(0)` on Linux after each worker thread exits.** When a worker's V8 isolate and tokio runtime are dropped, glibc's allocator holds onto the fragmented heap pages rather than returning them to the OS. This explicitly asks glibc to release them. Follows the same pattern already used in the SIGUSR2 memory trim handler (`runtime/worker.rs:81`).

- **Remove the delayed termination hack entirely.** The 2-second timer that spawned threads/tasks to force-terminate workers is no longer needed — the upstream V8 issue that required it has been fixed. Workers now terminate cooperatively via the termination signal and event loop wakeup, which also eliminates the ~100 lingering OS threads during rapid worker churn.

### What this doesn't fix
- The ~7-8MB per-isolate overhead from V8's lack of shared read-only heap (upstream V8 issue)
- macOS/Windows RSS behavior (`malloc_trim` is Linux-only)

Changed files:
filename: runtime/ops/worker_host.rs
patch: @@ -269,7 +269,20 @@ fn op_create_worker(
       .await
     };
 
-    create_and_run_current_thread(fut)
+    let _ = create_and_run_current_thread(fut);
+
+    // After the worker's tokio runtime and JsRuntime/V8 isolate have been
+    // dropped, ask the system allocator to release freed memory back to the
+    // OS. Without this, glibc in particular holds onto the fragmented heap
+    // pages, causing RSS to remain high after many workers are created and
+    // destroyed (https://github.com/denoland/deno/issues/26058).
+    #[cfg(target_os = "linux")]
+    {
+      // SAFETY: calling libc function with no preconditions.
+      unsafe {
+        libc::malloc_trim(0);
+      }
+    }
   })?;
 
   // Receive WebWorkerHandle from newly created worker

filename: runtime/web_worker.rs
patch: @@ -269,9 +269,7 @@ pub struct SendableWebWorkerHandle {
   port: MessagePort,
   receiver: mpsc::Receiver<WorkerControlEvent>,
   termination_signal: Arc<AtomicBool>,
-  has_terminated: Arc<AtomicBool>,
   terminate_waker: Arc<AtomicWaker>,
-  isolate_handle: v8::IsolateHandle,
 }
 
 impl From<SendableWebWorkerHandle> for WebWorkerHandle {
@@ -280,9 +278,7 @@ impl From<SendableWebWorkerHandle> for WebWorkerHandle {
       receiver: Rc::new(RefCell::new(handle.receiver)),
       port: Rc::new(handle.port),
       termination_signal: handle.termination_signal,
-      has_terminated: handle.has_terminated,
       terminate_waker: handle.terminate_waker,
-      isolate_handle: handle.isolate_handle,
     }
   }
 }
@@ -299,9 +295,7 @@ pub struct WebWorkerHandle {
   pub port: Rc<MessagePort>,
   receiver: Rc<RefCell<mpsc::Receiver<WorkerControlEvent>>>,
   termination_signal: Arc<AtomicBool>,
-  has_terminated: Arc<AtomicBool>,
   terminate_waker: Arc<AtomicWaker>,
-  isolate_handle: v8::IsolateHandle,
 }
 
 impl WebWorkerHandle {
@@ -315,36 +309,16 @@ impl WebWorkerHandle {
 
   /// Terminate the worker
   /// This function will set the termination signal, close the message channel,
-  /// and schedule to terminate the isolate after two seconds.
+  /// and wake the worker's event loop so it can terminate.
   pub fn terminate(self) {
-    use std::thread::sleep;
-    use std::thread::spawn;
-    use std::time::Duration;
-
     let schedule_termination =
       !self.termination_signal.swap(true, Ordering::SeqCst);
 
     self.port.disentangle();
 
-    if schedule_termination && !self.has_terminated.load(Ordering::SeqCst) {
+    if schedule_termination {
       // Wake up the worker's event loop so it can terminate.
       self.terminate_waker.wake();
-
-      let has_terminated = self.has_terminated.clone();
-
-      // Schedule to terminate the isolate's execution.
-      spawn(move || {
-        sleep(Duration::from_secs(2));
-
-        // A worker's isolate can only be terminated once, so we need a guard
-        // here.
-        let already_terminated = has_terminated.swap(true, Ordering::SeqCst);
-
-        if !already_terminated {
-          // Stop javascript execution
-          self.isolate_handle.terminate_execution();
-        }
-      });
     }
   }
 }
@@ -363,9 +337,9 @@ fn create_handles(
     name,
     port: Rc::new(parent_port),
     termination_signal: termination_signal.clone(),
-    has_terminated: has_terminated.clone(),
+    has_terminated,
     terminate_waker: terminate_waker.clone(),
-    isolate_handle: isolate_handle.clone(),
+    isolate_handle,
     cancel: CancelHandle::new_rc(),
     sender: ctrl_tx,
     worker_type,
@@ -374,9 +348,7 @@ fn create_handles(
     receiver: ctrl_rx,
     port: worker_port,
     termination_signal,
-    has_terminated,
     terminate_waker,
-    isolate_handle,
   };
   (internal_handle, external_handle)
 }