@@ -35,6 +35,7 @@ use openworkers_core::{
3535} ;
3636use std:: collections:: HashMap ;
3737use std:: sync:: Arc ;
38+ use std:: sync:: Mutex ;
3839use std:: sync:: atomic:: { AtomicU64 , Ordering } ;
3940
4041use tracing:: Instrument ;
@@ -115,26 +116,36 @@ impl BindingConfigs {
115116/// Database pool type alias
116117pub type DbPool = sqlx:: Pool < sqlx:: Postgres > ;
117118
119+ /// Per-request state that changes between requests for context reuse.
120+ ///
121+ /// When warm isolates are enabled, the same `RunnerOperations` handle persists
122+ /// across requests. Only `log_tx` and `span` change per-request.
123+ pub struct RequestState {
124+ pub log_tx : Option < LogTx > ,
125+ pub span : tracing:: Span ,
126+ }
127+
118128/// Runner's implementation of OperationsHandler
119129///
120130/// Implements fetch via reqwest, bindings via config lookup, and logs via channel.
131+ ///
132+ /// For warm isolates: `request_state` is swappable via interior mutability so that
133+ /// the same ops handle (captured in the event loop) can serve multiple requests.
121134pub struct RunnerOperations {
122135 /// Stats for this worker
123136 pub stats : Arc < OperationsStats > ,
124137 /// User ID for logging/quotas
125138 pub user_id : Option < String > ,
126139 /// Worker ID for logging/quotas
127140 pub worker_id : Option < String > ,
128- /// Log sender (optional - if None, uses default stderr)
129- pub log_tx : Option < LogTx > ,
141+ /// Per-request state (log_tx + span), swappable between requests
142+ request_state : Mutex < RequestState > ,
130143 /// Binding configs for this worker
131144 pub bindings : BindingConfigs ,
132145 /// Database pool for KV operations
133146 pub db_pool : Option < DbPool > ,
134147 /// Binding limiters (rate limiting for fetch, KV, database, storage)
135148 pub limiters : BindingLimiters ,
136- /// Tracing span for async operations (propagates trace context)
137- pub span : tracing:: Span ,
138149}
139150
140151impl RunnerOperations {
@@ -143,11 +154,13 @@ impl RunnerOperations {
143154 stats : Arc :: new ( OperationsStats :: new ( ) ) ,
144155 user_id : None ,
145156 worker_id : None ,
146- log_tx : None ,
157+ request_state : Mutex :: new ( RequestState {
158+ log_tx : None ,
159+ span : tracing:: Span :: none ( ) ,
160+ } ) ,
147161 bindings : BindingConfigs :: new ( ) ,
148162 db_pool : None ,
149163 limiters : BindingLimiters :: default ( ) ,
150- span : tracing:: Span :: none ( ) ,
151164 }
152165 }
153166
@@ -158,8 +171,8 @@ impl RunnerOperations {
158171 }
159172
160173 /// Attach tracing span for context propagation
161- pub fn with_span ( mut self , span : tracing:: Span ) -> Self {
162- self . span = span;
174+ pub fn with_span ( self , span : tracing:: Span ) -> Self {
175+ self . request_state . lock ( ) . unwrap ( ) . span = span;
163176 self
164177 }
165178
@@ -173,8 +186,8 @@ impl RunnerOperations {
173186 self
174187 }
175188
176- pub fn with_log_tx ( mut self , log_tx : LogTx ) -> Self {
177- self . log_tx = Some ( log_tx) ;
189+ pub fn with_log_tx ( self , log_tx : LogTx ) -> Self {
190+ self . request_state . lock ( ) . unwrap ( ) . log_tx = Some ( log_tx) ;
178191 self
179192 }
180193
@@ -188,6 +201,20 @@ impl RunnerOperations {
188201 self
189202 }
190203
204+ /// Swap per-request state (log handler + tracing span) for warm context reuse.
205+ ///
206+ /// Called when an existing ops handle is reused for a new request.
207+ pub fn update_request ( & self , log_tx : LogTx , span : tracing:: Span ) {
208+ let mut state = self . request_state . lock ( ) . unwrap ( ) ;
209+ state. log_tx = Some ( log_tx) ;
210+ state. span = span;
211+ }
212+
213+ /// Get a clone of the current span
214+ fn span ( & self ) -> tracing:: Span {
215+ self . request_state . lock ( ) . unwrap ( ) . span . clone ( )
216+ }
217+
191218 /// Execute a binding fetch with the given config (shared by assets and storage).
192219 fn do_binding_fetch (
193220 & self ,
@@ -196,7 +223,7 @@ impl RunnerOperations {
196223 request : HttpRequest ,
197224 ) -> OpFuture < ' _ , Result < HttpResponse , String > > {
198225 let binding_name = binding. to_string ( ) ;
199- let span = self . span . clone ( ) ;
226+ let span = self . span ( ) ;
200227
201228 Box :: pin (
202229 async move {
@@ -261,6 +288,10 @@ impl Default for RunnerOperations {
261288}
262289
263290impl OperationsHandler for RunnerOperations {
291+ fn as_any ( & self ) -> & dyn std:: any:: Any {
292+ self
293+ }
294+
264295 /// Handle direct fetch: `fetch("https://example.com")`
265296 ///
266297 /// This is a pass-through fetch with no auth modification.
@@ -269,7 +300,7 @@ impl OperationsHandler for RunnerOperations {
269300 /// Special case: URLs matching `*.workers.rocks` or `*.workers.dev.localhost`
270301 /// are routed internally to avoid DNS lookup and external network hop.
271302 fn handle_fetch ( & self , request : HttpRequest ) -> OpFuture < ' _ , Result < HttpResponse , String > > {
272- let span = self . span . clone ( ) ;
303+ let span = self . span ( ) ;
273304 Box :: pin (
274305 async move {
275306 // Acquire fetch limiter permit (blocks if at concurrent limit, errors if total exceeded)
@@ -366,7 +397,7 @@ impl OperationsHandler for RunnerOperations {
366397 }
367398 } ;
368399
369- let span = self . span . clone ( ) ;
400+ let span = self . span ( ) ;
370401 Box :: pin (
371402 async move {
372403 // Acquire storage limiter permit
@@ -436,7 +467,7 @@ impl OperationsHandler for RunnerOperations {
436467 } ;
437468
438469 let namespace_id = config. id . clone ( ) ;
439- let span = self . span . clone ( ) ;
470+ let span = self . span ( ) ;
440471
441472 Box :: pin (
442473 async move {
@@ -489,7 +520,7 @@ impl OperationsHandler for RunnerOperations {
489520
490521 // Get shared pool for schema mode
491522 let shared_pool = self . db_pool . clone ( ) ;
492- let span = self . span . clone ( ) ;
523+ let span = self . span ( ) ;
493524
494525 Box :: pin (
495526 async move {
@@ -590,7 +621,7 @@ impl OperationsHandler for RunnerOperations {
590621 }
591622 } ;
592623
593- let span = self . span . clone ( ) ;
624+ let span = self . span ( ) ;
594625
595626 Box :: pin (
596627 async move {
@@ -641,11 +672,15 @@ impl OperationsHandler for RunnerOperations {
641672 self . stats . log_count . fetch_add ( 1 , Ordering :: Relaxed ) ;
642673
643674 // Send to log channel if available (for log collection/storage)
644- if let Some ( ref tx) = self . log_tx {
645- let _ = tx. send ( LogEvent {
646- level,
647- message : message. clone ( ) ,
648- } ) ;
675+ {
676+ let state = self . request_state . lock ( ) . unwrap ( ) ;
677+
678+ if let Some ( ref tx) = state. log_tx {
679+ let _ = tx. send ( LogEvent {
680+ level,
681+ message : message. clone ( ) ,
682+ } ) ;
683+ }
649684 }
650685
651686 // Also log via the log crate for debugging
@@ -670,6 +705,14 @@ mod tests {
670705 assert ! ( ops. bindings. kv. is_empty( ) ) ;
671706 }
672707
708+ #[ test]
709+ fn test_runner_operations_update_request ( ) {
710+ let ops = RunnerOperations :: new ( ) ;
711+ let ( tx, _rx) = std:: sync:: mpsc:: channel ( ) ;
712+ ops. update_request ( tx, tracing:: Span :: none ( ) ) ;
713+ assert ! ( ops. request_state. lock( ) . unwrap( ) . log_tx. is_some( ) ) ;
714+ }
715+
673716 #[ test]
674717 fn test_runner_operations_with_user ( ) {
675718 let ops = RunnerOperations :: new ( ) . with_user_id ( "user-123" . to_string ( ) ) ;
0 commit comments