1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// Copyright 2021-2023 Protocol Labs
// SPDX-License-Identifier: Apache-2.0, MIT
use anyhow::anyhow;
use cid::Cid;
use fvm_shared::message::Message;
use lazy_static::lazy_static;

use super::{ApplyKind, ApplyRet, Executor};

lazy_static! {
    static ref EXEC_POOL: yastl::Pool = yastl::Pool::with_config(
        std::thread::available_parallelism().map(|n|n.get()).unwrap_or(8),
        yastl::ThreadConfig::new()
            .prefix("fvm-executor")
            // fvm needs more than the default available stack (2MiB):
            // - Max 2048 wasm stack elements, which is 16KiB of 64bit entries
            // - Roughly 20KiB overhead per actor call
            // - max 1024 nested calls, which means that in the worst case we need ~36MiB of stack
            // We also want some more space just to be conservative, so 64MiB seems like a reasonable choice
            .stack_size(64 << 20),
    );
}

/// An executor that executes messages on a separate thread with a 64MiB stack. If you can guarantee
/// at least 64MiB of stack space, you don't need this executor.
pub struct ThreadedExecutor<E>(pub E);

impl<E> Executor for ThreadedExecutor<E>
where
    E: Executor + Send,
{
    type Kernel = E::Kernel;

    /// This is the entrypoint to execute a message.
    fn execute_message(
        &mut self,
        msg: Message,
        apply_kind: ApplyKind,
        raw_length: usize,
    ) -> anyhow::Result<ApplyRet> {
        let mut ret = Err(anyhow!("failed to execute"));

        EXEC_POOL.scoped(|scope| {
            scope.execute(|| ret = self.0.execute_message(msg, apply_kind, raw_length));
        });

        ret
    }

    fn flush(&mut self) -> anyhow::Result<Cid> {
        self.0.flush()
    }
}