@@ -8,12 +8,13 @@ use crossbeam_channel::{Receiver, Sender};
88
99use crate :: host_call:: CallIdRouter ;
1010use crate :: ipc_binary:: BinaryFrame ;
11+ use crate :: snapshot:: SnapshotCache ;
1112#[ cfg( not( test) ) ]
1213use crate :: host_call:: { BridgeCallContext , ChannelFrameSender } ;
1314#[ cfg( not( test) ) ]
1415use crate :: ipc_binary:: { self , ExecutionErrorBin } ;
1516#[ cfg( not( test) ) ]
16- use crate :: { bridge, execution, isolate, stream} ;
17+ use crate :: { bridge, execution, isolate, snapshot , stream} ;
1718
1819/// Commands sent to a session thread
1920pub enum SessionCommand {
@@ -54,19 +55,27 @@ pub struct SessionManager {
5455 ipc_tx : IpcSender ,
5556 /// Call_id → session_id routing table for BridgeResponse dispatch
5657 call_id_router : CallIdRouter ,
58+ /// Shared snapshot cache for fast isolate creation from pre-compiled bridge code
59+ snapshot_cache : Arc < SnapshotCache > ,
5760}
5861
5962impl SessionManager {
60- pub fn new ( max_concurrency : usize , ipc_tx : IpcSender , call_id_router : CallIdRouter ) -> Self {
63+ pub fn new ( max_concurrency : usize , ipc_tx : IpcSender , call_id_router : CallIdRouter , snapshot_cache : Arc < SnapshotCache > ) -> Self {
6164 SessionManager {
6265 sessions : HashMap :: new ( ) ,
6366 max_concurrency,
6467 slot_control : Arc :: new ( ( Mutex :: new ( 0 ) , Condvar :: new ( ) ) ) ,
6568 ipc_tx,
6669 call_id_router,
70+ snapshot_cache,
6771 }
6872 }
6973
74+ /// Get the snapshot cache for pre-warming from WarmSnapshot messages.
75+ pub fn snapshot_cache ( & self ) -> & Arc < SnapshotCache > {
76+ & self . snapshot_cache
77+ }
78+
7079 /// Create a new session bound to the given connection.
7180 /// Spawns a dedicated thread with a V8 isolate. If max concurrency is
7281 /// reached, the session thread will block until a slot becomes available.
@@ -86,6 +95,7 @@ impl SessionManager {
8695 let max = self . max_concurrency ;
8796 let ipc_tx = self . ipc_tx . clone ( ) ;
8897 let router = Arc :: clone ( & self . call_id_router ) ;
98+ let snap_cache = Arc :: clone ( & self . snapshot_cache ) ;
8999
90100 let name_prefix = if session_id. len ( ) > 8 {
91101 & session_id[ ..8 ]
@@ -95,7 +105,7 @@ impl SessionManager {
95105 let join_handle = thread:: Builder :: new ( )
96106 . name ( format ! ( "session-{}" , name_prefix) )
97107 . spawn ( move || {
98- session_thread ( heap_limit_mb, cpu_time_limit_ms, rx, slot_control, max, ipc_tx, router) ;
108+ session_thread ( heap_limit_mb, cpu_time_limit_ms, rx, slot_control, max, ipc_tx, router, snap_cache ) ;
99109 } )
100110 . map_err ( |e| format ! ( "failed to spawn session thread: {}" , e) ) ?;
101111
@@ -221,7 +231,8 @@ fn send_message(ipc_tx: &IpcSender, frame: &BinaryFrame, frame_buf: &mut Vec<u8>
221231 }
222232}
223233
224- /// Session thread: acquires a concurrency slot, creates a V8 isolate, and
234+ /// Session thread: acquires a concurrency slot, defers V8 isolate creation
235+ /// to first Execute (when bridge code is known for snapshot lookup), and
225236/// processes commands until shutdown.
226237fn session_thread (
227238 #[ cfg_attr( test, allow( unused_variables) ) ] heap_limit_mb : Option < u32 > ,
@@ -231,6 +242,7 @@ fn session_thread(
231242 max_concurrency : usize ,
232243 #[ cfg_attr( test, allow( unused_variables) ) ] ipc_tx : IpcSender ,
233244 #[ cfg_attr( test, allow( unused_variables) ) ] call_id_router : CallIdRouter ,
245+ #[ cfg_attr( test, allow( unused_variables) ) ] snapshot_cache : Arc < SnapshotCache > ,
234246) {
235247 // Acquire concurrency slot (blocks if at capacity)
236248 {
@@ -242,17 +254,13 @@ fn session_thread(
242254 * count += 1 ;
243255 }
244256
245- // Create V8 isolate and context
246- // In test mode, skip V8 to avoid inter-test SIGSEGV (V8 lifecycle tested in isolate::tests)
257+ // Isolate creation is deferred to first Execute (when bridge code is known
258+ // for snapshot cache lookup). This avoids creating an isolate that may never
259+ // be used and enables snapshot-based fast creation.
260+ #[ cfg( not( test) ) ]
261+ let mut v8_isolate: Option < v8:: OwnedIsolate > = None ;
247262 #[ cfg( not( test) ) ]
248- let ( mut v8_isolate, _v8_context) = {
249- isolate:: init_v8_platform ( ) ;
250- let mut iso = isolate:: create_isolate ( heap_limit_mb) ;
251- // Disable WASM compilation before any code execution
252- execution:: disable_wasm ( & mut iso) ;
253- let ctx = isolate:: create_context ( & mut iso) ;
254- ( iso, ctx)
255- } ;
263+ let mut _v8_context: Option < v8:: Global < v8:: Context > > = None ;
256264
257265 #[ cfg( not( test) ) ]
258266 let pending = bridge:: PendingPromises :: new ( ) ;
@@ -306,12 +314,37 @@ fn session_thread(
306314 bridge_code
307315 } ;
308316
317+ // Deferred isolate creation: create on first Execute using snapshot cache
318+ if v8_isolate. is_none ( ) {
319+ isolate:: init_v8_platform ( ) ;
320+ let mut iso = if !effective_bridge_code. is_empty ( ) {
321+ match snapshot_cache. get_or_create ( & effective_bridge_code) {
322+ Ok ( blob) => {
323+ snapshot:: create_isolate_from_snapshot ( ( * blob) . clone ( ) , heap_limit_mb)
324+ }
325+ Err ( e) => {
326+ eprintln ! ( "snapshot creation failed, falling back to fresh isolate: {}" , e) ;
327+ isolate:: create_isolate ( heap_limit_mb)
328+ }
329+ }
330+ } else {
331+ isolate:: create_isolate ( heap_limit_mb)
332+ } ;
333+ // Must re-apply WASM disable after every restore (not captured in snapshot)
334+ execution:: disable_wasm ( & mut iso) ;
335+ let ctx = isolate:: create_context ( & mut iso) ;
336+ _v8_context = Some ( ctx) ;
337+ v8_isolate = Some ( iso) ;
338+ }
339+
340+ let iso = v8_isolate. as_mut ( ) . unwrap ( ) ;
341+
309342 // Create a fresh V8 context per execution (clean global scope)
310- let exec_context = isolate:: create_context ( & mut v8_isolate ) ;
343+ let exec_context = isolate:: create_context ( iso ) ;
311344
312345 // Inject globals from last InjectGlobals payload into the fresh context
313346 if let Some ( ref payload) = last_globals_payload {
314- let scope = & mut v8:: HandleScope :: new ( & mut v8_isolate ) ;
347+ let scope = & mut v8:: HandleScope :: new ( iso ) ;
315348 let ctx = v8:: Local :: new ( scope, & exec_context) ;
316349 let scope = & mut v8:: ContextScope :: new ( scope, ctx) ;
317350 execution:: inject_globals_from_payload ( scope, payload) ;
@@ -341,7 +374,7 @@ fn session_thread(
341374 let _sync_store;
342375 let _async_store;
343376 {
344- let scope = & mut v8:: HandleScope :: new ( & mut v8_isolate ) ;
377+ let scope = & mut v8:: HandleScope :: new ( iso ) ;
345378 let ctx = v8:: Local :: new ( scope, & exec_context) ;
346379 let scope = & mut v8:: ContextScope :: new ( scope, ctx) ;
347380
@@ -364,7 +397,7 @@ fn session_thread(
364397 // Start timeout guard before execution
365398 let mut timeout_guard = match ( cpu_time_limit_ms, maybe_abort_tx) {
366399 ( Some ( ms) , Some ( abort_tx) ) => {
367- let handle = v8_isolate . thread_safe_handle ( ) ;
400+ let handle = iso . thread_safe_handle ( ) ;
368401 Some ( crate :: timeout:: TimeoutGuard :: new ( ms, handle, abort_tx) )
369402 }
370403 _ => None ,
@@ -373,14 +406,14 @@ fn session_thread(
373406 // Execute code (fresh context per execution)
374407 let file_path_opt = if file_path. is_empty ( ) { None } else { Some ( file_path. as_str ( ) ) } ;
375408 let ( code, exports, error) = if mode == 0 {
376- let scope = & mut v8:: HandleScope :: new ( & mut v8_isolate ) ;
409+ let scope = & mut v8:: HandleScope :: new ( iso ) ;
377410 let ctx = v8:: Local :: new ( scope, & exec_context) ;
378411 let scope = & mut v8:: ContextScope :: new ( scope, ctx) ;
379412 let ( c, e) =
380413 execution:: execute_script ( scope, & effective_bridge_code, & user_code, & mut bridge_cache) ;
381414 ( c, None , e)
382415 } else {
383- let scope = & mut v8:: HandleScope :: new ( & mut v8_isolate ) ;
416+ let scope = & mut v8:: HandleScope :: new ( iso ) ;
384417 let ctx = v8:: Local :: new ( scope, & exec_context) ;
385418 let scope = & mut v8:: ContextScope :: new ( scope, ctx) ;
386419 execution:: execute_module (
@@ -395,7 +428,7 @@ fn session_thread(
395428
396429 // Run event loop if there are pending async promises
397430 let terminated = if pending. len ( ) > 0 {
398- let scope = & mut v8:: HandleScope :: new ( & mut v8_isolate ) ;
431+ let scope = & mut v8:: HandleScope :: new ( iso ) ;
399432 let ctx = v8:: Local :: new ( scope, & exec_context) ;
400433 let scope = & mut v8:: ContextScope :: new ( scope, ctx) ;
401434 !run_event_loop ( scope, & rx, & pending, maybe_abort_rx. as_ref ( ) )
@@ -464,8 +497,8 @@ fn session_thread(
464497 // Drop V8 resources (only present in non-test mode)
465498 #[ cfg( not( test) ) ]
466499 {
467- drop ( _v8_context) ;
468- drop ( v8_isolate) ;
500+ drop ( _v8_context. take ( ) ) ;
501+ drop ( v8_isolate. take ( ) ) ;
469502 }
470503
471504 // Release concurrency slot
@@ -686,7 +719,8 @@ mod tests {
686719 fn test_manager ( max : usize ) -> SessionManager {
687720 let ( tx, _rx) = crossbeam_channel:: unbounded ( ) ;
688721 let router: CallIdRouter = Arc :: new ( Mutex :: new ( HashMap :: new ( ) ) ) ;
689- SessionManager :: new ( max, tx, router)
722+ let snap_cache = Arc :: new ( SnapshotCache :: new ( 4 ) ) ;
723+ SessionManager :: new ( max, tx, router, snap_cache)
690724 }
691725
692726 #[ test]
0 commit comments