1use alloc::{
2 ffi::CString,
3 sync::{Arc, Weak},
4 vec::Vec,
5};
6use core::{ptr::NonNull, result::Result as StdResult, task::Poll};
7#[cfg(feature = "std")]
8use std::println;
9
10#[cfg(feature = "parallel")]
11use std::sync::mpsc::{self, Sender};
12
13use async_lock::Mutex;
14
15use super::{
16 opaque::Opaque, raw::RawRuntime, schedular::SchedularPoll, spawner::DriveFuture,
17 InterruptHandler, MemoryUsage, PromiseHook, RejectionTracker,
18};
19use crate::allocator::Allocator;
20#[cfg(feature = "loader")]
21use crate::loader::{Loader, Resolver};
22#[cfg(feature = "parallel")]
23use crate::util::{AssertSendFuture, AssertSyncFuture};
24use crate::{
25 context::AsyncContext, qjs, result::AsyncJobException, util::ManualPoll, Ctx, Exception, Result,
26};
27
28#[derive(Debug)]
29pub(crate) struct InnerRuntime {
30 pub runtime: RawRuntime,
31}
32
33#[cfg(feature = "parallel")]
34unsafe impl Send for InnerRuntime {}
35
36#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "futures")))]
40#[derive(Clone)]
41pub struct AsyncWeakRuntime {
42 inner: Weak<Mutex<InnerRuntime>>,
43 #[cfg(feature = "parallel")]
44 pending_free: Sender<NonNull<qjs::JSContext>>,
45}
46
47impl AsyncWeakRuntime {
48 pub fn try_ref(&self) -> Option<AsyncRuntime> {
49 self.inner.upgrade().map(|inner| AsyncRuntime {
50 inner,
51 #[cfg(feature = "parallel")]
52 pending_free: self.pending_free.clone(),
53 })
54 }
55}
56
57#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "futures")))]
59#[derive(Clone)]
60pub struct AsyncRuntime {
61 pub(crate) inner: Arc<Mutex<InnerRuntime>>,
63 #[cfg(feature = "parallel")]
64 pub(crate) pending_free: Sender<NonNull<qjs::JSContext>>,
65}
66
67#[cfg(feature = "parallel")]
70unsafe impl Send for AsyncRuntime {}
71#[cfg(feature = "parallel")]
72unsafe impl Send for AsyncWeakRuntime {}
73
74#[cfg(feature = "parallel")]
78unsafe impl Sync for AsyncRuntime {}
79#[cfg(feature = "parallel")]
80unsafe impl Sync for AsyncWeakRuntime {}
81
82impl AsyncRuntime {
83 #[allow(clippy::arc_with_non_send_sync)]
91 pub fn new() -> Result<Self> {
92 let opaque = Opaque::with_spawner();
93
94 #[cfg(feature = "parallel")]
95 let (pending_free, pending_free_recv) = mpsc::channel();
96 let runtime = unsafe {
97 RawRuntime::new(
98 opaque,
99 #[cfg(feature = "parallel")]
100 pending_free_recv,
101 )
102 }?;
103
104 Ok(Self {
105 inner: Arc::new(Mutex::new(InnerRuntime { runtime })),
106 #[cfg(feature = "parallel")]
107 pending_free,
108 })
109 }
110
111 #[allow(clippy::arc_with_non_send_sync)]
116 pub fn new_with_alloc<A>(allocator: A) -> Result<Self>
117 where
118 A: Allocator + 'static,
119 {
120 let opaque = Opaque::with_spawner();
121
122 #[cfg(feature = "parallel")]
123 let (pending_free, pending_free_recv) = mpsc::channel();
124 let runtime = unsafe {
125 RawRuntime::new_with_allocator(
126 opaque,
127 allocator,
128 #[cfg(feature = "parallel")]
129 pending_free_recv,
130 )
131 }?;
132
133 Ok(Self {
134 inner: Arc::new(Mutex::new(InnerRuntime { runtime })),
135 #[cfg(feature = "parallel")]
136 pending_free,
137 })
138 }
139
140 pub fn weak(&self) -> AsyncWeakRuntime {
142 AsyncWeakRuntime {
143 inner: Arc::downgrade(&self.inner),
144 #[cfg(feature = "parallel")]
145 pending_free: self.pending_free.clone(),
146 }
147 }
148
149 #[inline]
151 pub async fn set_host_promise_rejection_tracker(&self, tracker: Option<RejectionTracker>) {
152 unsafe {
153 self.inner
154 .lock()
155 .await
156 .runtime
157 .set_host_promise_rejection_tracker(tracker);
158 }
159 }
160
161 #[inline]
163 pub async fn set_promise_hook(&self, tracker: Option<PromiseHook>) {
164 unsafe {
165 self.inner.lock().await.runtime.set_promise_hook(tracker);
166 }
167 }
168
169 #[inline]
173 pub async fn set_interrupt_handler(&self, handler: Option<InterruptHandler>) {
174 unsafe {
175 self.inner
176 .lock()
177 .await
178 .runtime
179 .set_interrupt_handler(handler);
180 }
181 }
182
183 #[cfg(feature = "loader")]
185 #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "loader")))]
186 pub async fn set_loader<R, L>(&self, resolver: R, loader: L)
187 where
188 R: Resolver + 'static,
189 L: Loader + 'static,
190 {
191 unsafe {
192 self.inner.lock().await.runtime.set_loader(resolver, loader);
193 }
194 }
195
196 pub async fn set_info<S: Into<Vec<u8>>>(&self, info: S) -> Result<()> {
198 let string = CString::new(info)?;
199 unsafe {
200 self.inner.lock().await.runtime.set_info(string);
201 }
202 Ok(())
203 }
204
205 pub async fn set_memory_limit(&self, limit: usize) {
212 unsafe {
213 self.inner.lock().await.runtime.set_memory_limit(limit);
214 }
215 }
216
217 pub async fn set_max_stack_size(&self, limit: usize) {
221 unsafe {
222 self.inner.lock().await.runtime.set_max_stack_size(limit);
223 }
224 }
225
226 pub async fn set_gc_threshold(&self, threshold: usize) {
228 unsafe {
229 self.inner.lock().await.runtime.set_gc_threshold(threshold);
230 }
231 }
232
233 pub async fn run_gc(&self) {
240 unsafe {
241 let mut lock = self.inner.lock().await;
242 lock.runtime.drain_pending_free();
243 lock.runtime.run_gc();
244 }
245 }
246
247 pub async fn memory_usage(&self) -> MemoryUsage {
249 unsafe { self.inner.lock().await.runtime.memory_usage() }
250 }
251
252 #[inline]
256 pub async fn is_job_pending(&self) -> bool {
257 let lock = self.inner.lock().await;
258
259 lock.runtime.is_job_pending() || !lock.runtime.get_opaque().spawner_is_empty()
260 }
261
262 #[inline]
266 pub async fn execute_pending_job(&self) -> StdResult<bool, AsyncJobException> {
267 let mut lock = self.inner.lock().await;
268 lock.runtime.update_stack_top();
269 lock.runtime.drain_pending_free();
270
271 let f = ManualPoll::new(|cx| {
272 let job_res = lock.runtime.execute_pending_job().map_err(|e| {
273 let ptr = NonNull::new(e)
274 .expect("executing pending job returned a null context on error");
275 unsafe { qjs::JS_DupContext(ptr.as_ptr()) };
278 AsyncJobException(unsafe { AsyncContext::from_raw(ptr, self.clone()) })
279 })?;
280
281 if job_res {
282 return Poll::Ready(Ok(true));
283 }
284
285 match lock.runtime.get_opaque().poll(cx) {
286 SchedularPoll::ShouldYield => Poll::Pending,
287 SchedularPoll::Empty => Poll::Ready(Ok(false)),
288 SchedularPoll::Pending => Poll::Ready(Ok(false)),
289 SchedularPoll::PendingProgress => Poll::Ready(Ok(true)),
290 }
291 });
292
293 #[cfg(feature = "parallel")]
294 let f = unsafe { AssertSendFuture::assert(AssertSyncFuture::assert(f)) };
295
296 f.await
297 }
298
299 #[inline]
301 pub async fn idle(&self) {
302 let mut lock = self.inner.lock().await;
303 lock.runtime.update_stack_top();
304 lock.runtime.drain_pending_free();
305
306 let f = ManualPoll::new(|cx| {
307 loop {
308 let pending = lock.runtime.execute_pending_job().map_err(|e| {
309 let ptr = NonNull::new(e)
310 .expect("executing pending job returned a null context on error");
311 unsafe { qjs::JS_DupContext(ptr.as_ptr()) };
314 AsyncJobException(unsafe { AsyncContext::from_raw(ptr, self.clone()) })
315 });
316 match pending {
317 Err(e) => {
318 let ctx = unsafe { Ctx::from_ptr(e.0 .0.ctx().as_ptr()) };
320 let err = ctx.catch();
321 if let Some(_x) = err.clone().into_object().and_then(Exception::from_object)
322 {
323 #[cfg(feature = "std")]
325 println!("error executing job: {}", _x);
326 } else {
327 #[cfg(feature = "std")]
328 println!("error executing job: {:?}", err);
329 }
330 }
331 Ok(true) => continue,
332 Ok(false) => {}
333 }
334
335 match lock.runtime.get_opaque().poll(cx) {
336 SchedularPoll::ShouldYield => return Poll::Pending,
337 SchedularPoll::Empty => return Poll::Ready(()),
338 SchedularPoll::Pending => return Poll::Pending,
339 SchedularPoll::PendingProgress => {}
340 }
341 }
342 });
343
344 #[cfg(feature = "parallel")]
345 let f = unsafe { AssertSendFuture::assert(AssertSyncFuture::assert(f)) };
346
347 f.await
348 }
349
350 pub fn drive(&self) -> DriveFuture {
354 DriveFuture::new(self.weak())
355 }
356}
357
358#[cfg(test)]
359macro_rules! async_test_case {
360 ($name:ident => ($rt:ident,$ctx:ident) { $($t:tt)* }) => {
361 #[test]
362 fn $name() {
363 #[cfg(feature = "parallel")]
364 let mut new_thread = tokio::runtime::Builder::new_multi_thread();
365
366 #[cfg(not(feature = "parallel"))]
367 let mut new_thread = tokio::runtime::Builder::new_current_thread();
368
369 let rt = new_thread
370 .enable_all()
371 .build()
372 .unwrap();
373
374 #[cfg(feature = "parallel")]
375 {
376 rt.block_on(async {
377 let $rt = crate::AsyncRuntime::new().unwrap();
378 let $ctx = crate::AsyncContext::full(&$rt).await.unwrap();
379
380 $($t)*
381
382 })
383 }
384 #[cfg(not(feature = "parallel"))]
385 {
386 let set = tokio::task::LocalSet::new();
387 set.block_on(&rt, async {
388 let $rt = crate::AsyncRuntime::new().unwrap();
389 let $ctx = crate::AsyncContext::full(&$rt).await.unwrap();
390
391 $($t)*
392 })
393 }
394 }
395 };
396}
397
398#[cfg(test)]
399mod test {
400 use std::time::Duration;
401
402 use crate::*;
403
404 use self::context::EvalOptions;
405
406 async_test_case!(basic => (_rt,ctx){
407 ctx.async_with(async |ctx|{
408 let res: i32 = ctx.eval("1 + 1").unwrap();
409 assert_eq!(res,2i32);
410 }).await;
411 });
412
413 async_test_case!(sleep_closure => (_rt,ctx){
414
415 let mut a = 1;
416 let a_ref = &mut a;
417
418
419 ctx.async_with(async |ctx|{
420 tokio::time::sleep(Duration::from_secs_f64(0.01)).await;
421 ctx.globals().set("foo","bar").unwrap();
422 *a_ref += 1;
423 }).await;
424 assert_eq!(a,2);
425 });
426
427 async_test_case!(drive => (rt,ctx){
428 use std::sync::{Arc, atomic::{Ordering,AtomicUsize}};
429
430 #[cfg(feature = "parallel")]
431 tokio::spawn(rt.drive());
432 #[cfg(not(feature = "parallel"))]
433 tokio::task::spawn_local(rt.drive());
434
435 tokio::time::sleep(Duration::from_secs_f64(0.01)).await;
437
438 let number = Arc::new(AtomicUsize::new(0));
439 let number_clone = number.clone();
440 let gate = Arc::new(tokio::sync::Notify::new());
441 let gate_clone = gate.clone();
442 let done = Arc::new(tokio::sync::Notify::new());
443 let done_clone = done.clone();
444
445 ctx.async_with(async |ctx|{
446 ctx.spawn(async move {
447 gate_clone.notified().await;
448 number_clone.store(1,Ordering::SeqCst);
449 done_clone.notify_one();
450 });
451 }).await;
452 assert_eq!(number.load(Ordering::SeqCst),0);
454 gate.notify_one();
456 done.notified().await;
457 assert_eq!(number.load(Ordering::SeqCst),1);
458
459 });
460
461 async_test_case!(no_drive => (rt,ctx){
462 use std::sync::{Arc, atomic::{Ordering,AtomicUsize}};
463
464 let number = Arc::new(AtomicUsize::new(0));
465 let number_clone = number.clone();
466
467 ctx.async_with(async |ctx|{
468 ctx.spawn(async move {
469 tokio::task::yield_now().await;
470 number_clone.store(1,Ordering::SeqCst);
471 });
472 }).await;
473 assert_eq!(number.load(Ordering::SeqCst),0);
474 tokio::time::sleep(Duration::from_secs_f64(0.01)).await;
475 assert_eq!(number.load(Ordering::SeqCst),0);
476
477 });
478
479 async_test_case!(idle => (rt,ctx){
480 use std::sync::{Arc, atomic::{Ordering,AtomicUsize}};
481
482 let number = Arc::new(AtomicUsize::new(0));
483 let number_clone = number.clone();
484
485 ctx.async_with(async |ctx|{
486 ctx.spawn(async move {
487 tokio::task::yield_now().await;
488 number_clone.store(1,Ordering::SeqCst);
489 });
490 }).await;
491 assert_eq!(number.load(Ordering::SeqCst),0);
492 rt.idle().await;
493 assert_eq!(number.load(Ordering::SeqCst),1);
494
495 });
496
497 async_test_case!(recursive_spawn => (rt,ctx){
498 use tokio::sync::oneshot;
499
500 ctx.async_with(async |ctx|{
501 let ctx_clone = ctx.clone();
502 let (tx,rx) = oneshot::channel::<()>();
503 let (tx2,rx2) = oneshot::channel::<()>();
504 ctx.spawn(async move {
505 tokio::task::yield_now().await;
506
507 let ctx = ctx_clone.clone();
508
509 ctx_clone.spawn(async move {
510 tokio::task::yield_now().await;
511 ctx.spawn(async move {
512 tokio::task::yield_now().await;
513 tx2.send(()).unwrap();
514 tokio::task::yield_now().await;
515 });
516 tokio::task::yield_now().await;
517 tx.send(()).unwrap();
518 });
519
520 for _ in 0..32{
523 ctx_clone.spawn(async move {})
524 }
525
526 });
527 tokio::time::timeout(Duration::from_millis(500), rx).await.unwrap().unwrap();
528 tokio::time::timeout(Duration::from_millis(500), rx2).await.unwrap().unwrap();
529 }).await;
530
531 });
532
533 async_test_case!(recursive_spawn_from_script => (rt,ctx) {
534 use std::sync::atomic::{Ordering, AtomicUsize};
535 use crate::prelude::Func;
536
537 static COUNT: AtomicUsize = AtomicUsize::new(0);
538 static SCRIPT: &str = r#"
539
540 async function main() {
541
542 setTimeout(() => {
543 inc_count()
544 setTimeout(async () => {
545 inc_count()
546 }, 100);
547 }, 100);
548 }
549
550 main().catch(print);
551
552
553 "#;
554
555 fn inc_count(){
556 COUNT.fetch_add(1,Ordering::Relaxed);
557 }
558
559 fn set_timeout_spawn<'js>(ctx: Ctx<'js>, callback: Function<'js>, millis: usize) -> Result<()> {
560 ctx.spawn(async move {
561 tokio::time::sleep(Duration::from_millis(millis as u64)).await;
562 callback.call::<_, ()>(()).unwrap();
563 });
564
565 Ok(())
566 }
567
568
569 ctx.async_with(async |ctx|{
570
571 let res: Result<Promise> = (|| {
572 let globals = ctx.globals();
573
574 globals.set("inc_count", Func::from(inc_count))?;
575
576 globals.set("setTimeout", Func::from(set_timeout_spawn))?;
577 let options = EvalOptions{
578 promise: true,
579 strict: false,
580 ..EvalOptions::default()
581 };
582
583 ctx.eval_with_options(SCRIPT, options)?
584 })();
585
586 match res.catch(&ctx){
587 Ok(promise) => {
588 if let Err(err) = promise.into_future::<Value>().await.catch(&ctx){
589 eprintln!("{}", err)
590 }
591 },
592 Err(err) => {
593 eprintln!("{}", err)
594 },
595 };
596
597 })
598 .await;
599
600 rt.idle().await;
601
602 assert_eq!(COUNT.load(Ordering::Relaxed),2);
603 });
604
605 async_test_case!(interrupt_handler_idle => (rt, ctx) {
606 use std::time::Instant;
607
608 let timeout = Duration::from_millis(100);
609 let start_time = Instant::now();
610
611 rt.set_interrupt_handler(Some(Box::new(move || start_time.elapsed() >= timeout)))
612 .await;
613
614 let _ = ctx.async_with(async |ctx| {
615 ctx.eval::<(), _>(r#"
616 async function example() {
617 while (true) {
618 await Promise.resolve();
619 }
620 }
621 example();
622 "#)
623 }).await;
624
625 rt.idle().await;
629 });
630
631 #[cfg(feature = "parallel")]
632 fn assert_is_send<T: Send>(t: T) -> T {
633 t
634 }
635
636 #[cfg(feature = "parallel")]
637 fn assert_is_sync<T: Send>(t: T) -> T {
638 t
639 }
640
641 #[cfg(feature = "parallel")]
642 #[tokio::test]
643 async fn ensure_types_are_send_sync() {
644 let rt = AsyncRuntime::new().unwrap();
645
646 std::mem::drop(assert_is_sync(rt.idle()));
647 std::mem::drop(assert_is_sync(rt.execute_pending_job()));
648 std::mem::drop(assert_is_sync(rt.drive()));
649
650 std::mem::drop(assert_is_send(rt.idle()));
651 std::mem::drop(assert_is_send(rt.execute_pending_job()));
652 std::mem::drop(assert_is_send(rt.drive()));
653 }
654}