@@ -30,6 +30,7 @@ pub enum LeadershipRole {
3030pub struct WorkerConfig {
3131 pub db_name : String ,
3232 pub follower_timeout_ms : f64 ,
33+ pub query_timeout_ms : f64 ,
3334}
3435
3536pub fn worker_config_from_global ( ) -> Result < WorkerConfig , JsValue > {
@@ -60,9 +61,22 @@ pub fn worker_config_from_global() -> Result<WorkerConfig, JsValue> {
6061 5000.0
6162 }
6263
64+ fn get_query_timeout_from_global ( ) -> f64 {
65+ let global = js_sys:: global ( ) ;
66+ let val = Reflect :: get ( & global, & JsValue :: from_str ( "__SQLITE_QUERY_TIMEOUT_MS" ) )
67+ . unwrap_or ( JsValue :: UNDEFINED ) ;
68+ if let Some ( n) = val. as_f64 ( ) {
69+ if n. is_finite ( ) && n >= 0.0 {
70+ return n;
71+ }
72+ }
73+ 30000.0
74+ }
75+
6376 Ok ( WorkerConfig {
6477 db_name : get_db_name_from_global ( ) ?,
6578 follower_timeout_ms : get_follower_timeout_from_global ( ) ,
79+ query_timeout_ms : get_query_timeout_from_global ( ) ,
6680 } )
6781}
6882
@@ -113,6 +127,7 @@ pub struct CoordinatorState {
113127 pub leader_ready : Rc < RefCell < bool > > ,
114128 pub ready_signaled : Rc < RefCell < bool > > ,
115129 pub follower_timeout_ms : f64 ,
130+ pub query_timeout_ms : f64 ,
116131 pub channel : BroadcastChannel ,
117132 pub db_worker_ready : Rc < RefCell < bool > > ,
118133 pub db_worker : Rc < RefCell < Option < Worker > > > ,
@@ -144,6 +159,7 @@ impl CoordinatorState {
144159 leader_ready : Rc :: new ( RefCell :: new ( false ) ) ,
145160 ready_signaled : Rc :: new ( RefCell :: new ( false ) ) ,
146161 follower_timeout_ms : config. follower_timeout_ms ,
162+ query_timeout_ms : config. query_timeout_ms ,
147163 channel : create_broadcast_channel ( & config. db_name ) ?,
148164 db_worker_ready : Rc :: new ( RefCell :: new ( false ) ) ,
149165 db_worker : Rc :: new ( RefCell :: new ( None ) ) ,
@@ -296,9 +312,10 @@ impl CoordinatorState {
296312 . as_string ( )
297313 . ok_or_else ( || JsValue :: from_str ( "Embedded worker source is missing" ) ) ?;
298314 let preamble = format ! (
299- "self.__SQLITE_DB_ONLY = true;\n self.__SQLITE_DB_NAME = {};\n self.__SQLITE_FOLLOWER_TIMEOUT_MS = {};\n " ,
315+ "self.__SQLITE_DB_ONLY = true;\n self.__SQLITE_DB_NAME = {};\n self.__SQLITE_FOLLOWER_TIMEOUT_MS = {};\n self.__SQLITE_QUERY_TIMEOUT_MS = {}; \ n " ,
300316 db_name_encoded,
301317 self . follower_timeout_ms,
318+ self . query_timeout_ms,
302319 ) ;
303320
304321 let parts = js_sys:: Array :: new ( ) ;
@@ -403,7 +420,7 @@ impl CoordinatorState {
403420 . borrow_mut ( )
404421 . insert ( query_id. clone ( ) , request_id) ;
405422 let pending = Rc :: clone ( & self . follower_pending ) ;
406- let timeout = self . follower_timeout_ms ;
423+ let timeout = self . query_timeout_ms ;
407424 let timeout_query_id = query_id. clone ( ) ;
408425 spawn_local ( async move {
409426 sleep_ms ( timeout. ceil ( ) as i32 ) . await ;
@@ -943,10 +960,34 @@ mod tests {
943960 ) ;
944961 }
945962
963+ #[ wasm_bindgen_test]
964+ fn worker_config_reads_custom_timeouts ( ) {
965+ set_global_str ( "__SQLITE_DB_NAME" , "testdb-timeouts" ) ;
966+ set_global_num ( "__SQLITE_FOLLOWER_TIMEOUT_MS" , 1234.0 ) ;
967+ set_global_num ( "__SQLITE_QUERY_TIMEOUT_MS" , 4321.0 ) ;
968+
969+ let cfg = worker_config_from_global ( ) . expect ( "config" ) ;
970+ assert_eq ! ( cfg. follower_timeout_ms, 1234.0 ) ;
971+ assert_eq ! ( cfg. query_timeout_ms, 4321.0 ) ;
972+ }
973+
974+ #[ wasm_bindgen_test]
975+ fn worker_config_defaults_query_timeout ( ) {
976+ set_global_str ( "__SQLITE_DB_NAME" , "testdb-timeouts-default" ) ;
977+ let _ = Reflect :: delete_property (
978+ & js_sys:: global ( ) ,
979+ & JsValue :: from_str ( "__SQLITE_QUERY_TIMEOUT_MS" ) ,
980+ ) ;
981+
982+ let cfg = worker_config_from_global ( ) . expect ( "config" ) ;
983+ assert_eq ! ( cfg. query_timeout_ms, 30000.0 ) ;
984+ }
985+
946986 #[ wasm_bindgen_test( async ) ]
947987 async fn coordinator_broadcasts_leader_and_ready ( ) {
948988 set_global_str ( "__SQLITE_DB_NAME" , "testdb-coordinator" ) ;
949989 set_global_num ( "__SQLITE_FOLLOWER_TIMEOUT_MS" , 100.0 ) ;
990+ set_global_num ( "__SQLITE_QUERY_TIMEOUT_MS" , 100.0 ) ;
950991 set_global_str (
951992 "__SQLITE_EMBEDDED_WORKER" ,
952993 "self.postMessage({type:'worker-ready'}); self.onmessage = ev => { const d = ev.data || {}; if (d.type === 'execute-query') { self.postMessage({type:'query-result', requestId:d.requestId, result:'{\" ok\" :true}', error:null}); } };" ,
@@ -988,6 +1029,7 @@ mod tests {
9881029 async fn leader_ping_responds_based_on_db_readiness ( ) {
9891030 set_global_str ( "__SQLITE_DB_NAME" , "testdb-ping" ) ;
9901031 set_global_num ( "__SQLITE_FOLLOWER_TIMEOUT_MS" , 50.0 ) ;
1032+ set_global_num ( "__SQLITE_QUERY_TIMEOUT_MS" , 50.0 ) ;
9911033 set_global_str ( "__SQLITE_EMBEDDED_WORKER" , "" ) ;
9921034
9931035 let cfg = worker_config_from_global ( ) . expect ( "config" ) ;
@@ -1039,6 +1081,7 @@ mod tests {
10391081 async fn lock_request_failure_keeps_follower_role ( ) {
10401082 set_global_str ( "__SQLITE_DB_NAME" , "testdb-lock-failure" ) ;
10411083 set_global_num ( "__SQLITE_FOLLOWER_TIMEOUT_MS" , 50.0 ) ;
1084+ set_global_num ( "__SQLITE_QUERY_TIMEOUT_MS" , 50.0 ) ;
10421085 set_global_str ( "__SQLITE_EMBEDDED_WORKER" , "" ) ;
10431086
10441087 // Stub navigator.locks.request to throw so acquisition fails.
@@ -1081,6 +1124,7 @@ mod tests {
10811124 async fn db_worker_failure_resets_and_reports ( ) {
10821125 set_global_str ( "__SQLITE_DB_NAME" , "testdb-db-failure" ) ;
10831126 set_global_num ( "__SQLITE_FOLLOWER_TIMEOUT_MS" , 50.0 ) ;
1127+ set_global_num ( "__SQLITE_QUERY_TIMEOUT_MS" , 50.0 ) ;
10841128 set_global_str ( "__SQLITE_EMBEDDED_WORKER" , "" ) ;
10851129
10861130 let cfg = worker_config_from_global ( ) . expect ( "config" ) ;
@@ -1176,6 +1220,7 @@ mod tests {
11761220 WorkerConfig {
11771221 db_name : "testdb-fake" . to_string ( ) ,
11781222 follower_timeout_ms : 10.0 ,
1223+ query_timeout_ms : 10.0 ,
11791224 } ,
11801225 hooks,
11811226 ) ;
0 commit comments