char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
c42f8c8
main
1use std:: convert:: Infallible ; 2use std:: io; 3use std:: rc:: Rc ; 4use std:: sync::{ Arc , Condvar , Mutex }; 5use std:: thread:: JoinHandle ; 6use std:: time::{ Duration , Instant , SystemTime , UNIX_EPOCH }; 7 8use avril_core::{ Extension , Op }; 9use futures:: channel:: oneshot; 10 11# [ derive ( Default )] 12struct State { 13pending : Option <( Instant , oneshot:: Sender <()>)>, 14stopped : bool , 15} 16 17struct Timers { 18origin : Instant , 19time_origin : f64 , 20shared : Arc <( Mutex < State >, Condvar )>, 21worker : Option < JoinHandle <()>>, 22} 23 24impl Timers { 25fn new () -> io:: Result < Self > { 26let origin =Instant :: now (); 27let time_origin =SystemTime :: now () 28. duration_since ( UNIX_EPOCH ) 29. unwrap_or_default () 30. as_secs_f64 () 31* 1000.0 ; 32let shared =Arc :: new (( Mutex :: new ( State :: default ()), Condvar :: new ())); 33let worker = shared. clone (); 34let worker = std:: thread:: Builder :: new () 35. name ( "avril:timers" . into ()) 36. spawn ( move ||{ 37let ( lock, wake) =&* worker; 38let mut state = lock. lock (). unwrap (); 39loop { 40if state. stopped { 41return ; 42} 43if let Some (( deadline, _)) =& state. pending { 44let now =Instant :: now (); 45if * deadline <= now{ 46let ( _, reply) = state. pending . take (). unwrap (); 47let _ = reply. send (()); 48} else { 49let delay =* deadline - now; 50 state = wake. wait_timeout ( state, delay). unwrap (). 0 ; 51} 52} else { 53 state = wake. wait ( state). unwrap (); 54} 55} 56}) ?; 57Ok ( Self { 58 origin, 59 time_origin, 60 shared, 61worker : Some ( worker), 62}) 63} 64 65fn wait ( & self , deadline : f64 ) -> io:: Result < oneshot:: Receiver <()>> { 66let offset =Duration :: try_from_secs_f64 ( deadline /1000.0 ). map_err ( io:: Error :: other) ?; 67let deadline =self 68. origin 69. checked_add ( offset) 70. ok_or_else ( || io:: Error :: other ( "Timer deadline is out of range" )) ?; 71let ( reply, result) = oneshot:: channel (); 72let mut state =self . shared . 0 . lock (). unwrap (); 73 state. pending . take (); 74if deadline <=Instant :: now () { 75let _ = reply. send (()); 76} else { 77 state. pending =Some (( deadline, reply)); 78} 79self . shared . 1 . notify_one (); 80Ok ( result) 81} 82 83fn cancel ( & self ) { 84self . shared . 0 . lock (). unwrap (). pending . take (); 85self . shared . 1 . notify_one (); 86} 87} 88 89impl Drop for Timers { 90fn drop ( & mut self ) { 91{ 92let mut state =self . shared . 0 . lock (). unwrap (); 93 state. stopped =true ; 94 state. pending . take (); 95self . shared . 1 . notify_one (); 96} 97if let Some ( worker) =self . worker . take () { 98let _ = worker. join (); 99} 100} 101} 102 103pub fn init () -> io:: Result < Extension > { 104let timers =Rc :: new ( Timers :: new () ?); 105let ( now, time_origin, wait, wait_unreferenced, cancel) =( 106 timers. clone (), 107 timers. clone (), 108 timers. clone (), 109 timers. clone (), 110 timers, 111); 112Ok ( Extension { 113name : "ext:timers" , 114dependencies : & [], 115ops : vec! [ 116Op :: new ( "op_timer_now" , move |(): () |{ 117Ok ::< _ , Infallible >( now. origin . elapsed (). as_secs_f64 () * 1000.0 ) 118}), 119Op :: new ( "op_timer_time_origin" , move |(): () |{ 120Ok ::< _ , Infallible >( time_origin. time_origin ) 121}), 122Op :: asynchronous ( "op_timer_wait" , move |deadline : f64 |{ 123let result = wait. wait ( deadline); 124async move { 125let _ = result?. await ; 126Ok ::< _ , io:: Error >(()) 127} 128}), 129Op :: asynchronous_unreferenced ( "op_timer_wait_unreferenced" , move |deadline : f64 |{ 130let result = wait_unreferenced. wait ( deadline); 131async move { 132let _ = result?. await ; 133Ok ::< _ , io:: Error >(()) 134} 135}), 136Op :: new ( "op_timer_cancel" , move |(): () |{ 137 cancel. cancel (); 138Ok ::< _ , Infallible >(()) 139}), 140], 141modules : & [ 142( 143"ext:timers/main.js" , 144include_str! ( concat! ( env! ( "OUT_DIR" ), "/main.js" )), 145), 146( 147"ext:timers/timers.js" , 148include_str! ( concat! ( env! ( "OUT_DIR" ), "/timers.js" )), 149), 150], 151entry_point : Some ( "ext:timers/main.js" ), 152}) 153} 154 155# [ cfg ( test )] 156mod tests{ 157use avril_core::{ Runtime , RuntimeOptions }; 158 159fn runtime () ->Runtime { 160Runtime :: new ( RuntimeOptions { 161extensions : vec! [ 162 avril_ext_web:: with_broker ( std:: sync:: Arc :: default ()), 163super :: init (). unwrap (), 164], 165 ..Default :: default () 166}) 167. unwrap () 168} 169 170# [ test ] 171fn callback_failure_does_not_disarm_remaining_timers () { 172let mut runtime =runtime (); 173 runtime 174. execute_script ::<()>( 175"timers" , 176r#" 177globalThis.calls = 0; 178setTimeout(() => {throw Error('timer failed');}); 179setTimeout(() => calls++); 180"# , 181) 182. unwrap (); 183assert! ( 184 runtime 185. run_event_loop () 186. unwrap_err () 187. to_string () 188. contains ( "timer failed" ) 189); 190 runtime. run_event_loop (). unwrap (); 191assert_eq! ( runtime. execute_script ::< u32 >( "calls" , "calls" ). unwrap (), 1 ); 192} 193 194# [ test ] 195fn microtask_failures_drain_the_queue_without_poisoning_later_turns () { 196let mut runtime =runtime (); 197let error = runtime 198. execute_script ::<()>( 199"microtasks" , 200r#" 201globalThis.calls = []; 202queueMicrotask(() => { 203calls.push('first'); 204queueMicrotask(() => calls.push('nested')); 205throw Error('first failure'); 206}); 207queueMicrotask(() => { 208calls.push('second'); 209throw Error('second failure'); 210}); 211Promise.resolve().then(() => calls.push('promise')); 212"# , 213) 214. unwrap_err () 215. to_string (); 216assert! ( error. contains ( "first failure" ), "{error}" ); 217assert_eq! ( 218 runtime 219. execute_script ::< Vec < String >>( "calls" , "calls" ) 220. unwrap (), 221[ "first" , "second" , "promise" , "nested" ] 222); 223 runtime. run_event_loop (). unwrap (); 224 runtime 225. execute_script ::<()>( 226"caught" , 227"queueMicrotask(() => {try {throw Error('caught');} catch {}});" , 228) 229. unwrap (); 230} 231 232# [ test ] 233fn microtasks_report_non_error_exceptions () { 234let mut runtime =runtime (); 235for valuein [ "undefined" , "null" , "42" , "'failure'" ] { 236let error = runtime 237. execute_script ::<()>( 238"throw" , 239& format! ( "queueMicrotask(() => {{throw {value};}});" ), 240) 241. unwrap_err () 242. to_string (); 243assert! ( error. contains ( value. trim_matches ( '\'' )), "{error}" ); 244 runtime. run_event_loop (). unwrap (); 245} 246} 247 248# [ test ] 249fn dropping_inside_an_async_embedder_cancels_pending_waits () { 250let executor = tokio:: runtime:: Builder :: new_current_thread () 251. build () 252. unwrap (); 253 executor. block_on ( async { 254let mut runtime =runtime (); 255 runtime 256. execute_script ::<()>( "pending" , "setTimeout(() => {}, 1_000_000);" ) 257. unwrap (); 258drop ( runtime); 259}); 260} 261}