Skip to main content

ferrijs_std/
context.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3use std::future::Future;
4use std::sync::OnceLock;
5
6use crate::utils::primordials::{BasePrimordials, Primordial};
7use rquickjs::{atom::PredefinedAtom, CatchResultExt, CaughtError, Ctx, Object, Result};
8use tokio::sync::oneshot::{self, Receiver};
9use tracing::trace;
10
11#[allow(clippy::type_complexity)]
12static ERROR_HANDLER: OnceLock<Box<dyn for<'js> Fn(&Ctx<'js>, CaughtError<'js>) + Sync + Send>> =
13    OnceLock::new();
14
15pub trait CtxExtension<'js> {
16    /// Despite naming, this will not necessarily exit the parent process.
17    /// It depends on the handler set by `set_spawn_error_handler`.
18    fn spawn_exit<F, R>(&self, future: F) -> Result<Receiver<R>>
19    where
20        F: Future<Output = Result<R>> + 'js,
21        R: 'js;
22
23    fn spawn_exit_simple<F>(&self, future: F)
24    where
25        F: Future<Output = Result<()>> + 'js;
26}
27
28impl<'js> CtxExtension<'js> for Ctx<'js> {
29    fn spawn_exit<F, R>(&self, future: F) -> Result<Receiver<R>>
30    where
31        F: Future<Output = Result<R>> + 'js,
32        R: 'js,
33    {
34        let ctx = self.clone();
35
36        let primordials = BasePrimordials::get(self)?;
37        let type_error: Object = primordials.constructor_type_error.construct(())?;
38        let stack: Option<String> = type_error.get(PredefinedAtom::Stack).ok();
39
40        let (join_channel_tx, join_channel_rx) = oneshot::channel();
41
42        self.spawn(async move {
43            match future.await.catch(&ctx) {
44                Ok(res) => {
45                    //result here doesn't matter if receiver has dropped
46                    let _ = join_channel_tx.send(res);
47                },
48                Err(err) => handle_spawn_error(&ctx, err, stack),
49            }
50        });
51        Ok(join_channel_rx)
52    }
53
54    /// Same as above but fire & forget and without a forced stack trace collection
55    fn spawn_exit_simple<F>(&self, future: F)
56    where
57        F: Future<Output = Result<()>> + 'js,
58    {
59        let ctx = self.clone();
60        self.spawn(async move {
61            if let Err(err) = future.await.catch(&ctx) {
62                handle_spawn_error(&ctx, err, None)
63            }
64        });
65    }
66}
67
68fn handle_spawn_error<'js>(ctx: &Ctx<'js>, err: CaughtError<'js>, stack: Option<String>) {
69    let error_handler = if let Some(handler) = ERROR_HANDLER.get() {
70        handler
71    } else {
72        trace!("Future error: {:?}", err);
73        return;
74    };
75    if let CaughtError::Exception(err) = err {
76        if err.stack().is_none() {
77            if let Some(stack) = stack {
78                err.set(PredefinedAtom::Stack, stack).unwrap();
79            }
80        }
81        error_handler(ctx, CaughtError::Exception(err));
82    } else {
83        error_handler(ctx, err);
84    }
85}
86
87pub fn set_spawn_error_handler<F>(handler: F)
88where
89    F: for<'js> Fn(&Ctx<'js>, CaughtError<'js>) + Sync + Send + 'static,
90{
91    _ = ERROR_HANDLER.set(Box::new(handler));
92}