1 use std::sync::Arc;
2 use tokio::time::Duration;
3 use wasmtime::Error;
4 use wasmtime::{Config, Engine, Linker, Module, Store};
5 use wasmtime_wasi::{WasiCtx, p1::WasiP1Ctx};
6
7 #[tokio::main]
main() -> Result<(), Error>8 async fn main() -> Result<(), Error> {
9 // Create an environment shared by all wasm execution. This contains
10 // the `Engine` and the `Module` we are executing.
11 let env = Environment::new()?;
12
13 // The inputs to run_wasm are `Send`: we can create them here and send
14 // them to a new task that we spawn.
15 let inputs1 = Inputs::new(env.clone(), "Gussie");
16 let inputs2 = Inputs::new(env.clone(), "Willa");
17 let inputs3 = Inputs::new(env, "Sparky");
18
19 // Spawn some tasks. Insert sleeps before run_wasm so that the
20 // interleaving is easy to observe.
21 let join1 = tokio::task::spawn(async move { run_wasm(inputs1).await });
22 let join2 = tokio::task::spawn(async move {
23 tokio::time::sleep(Duration::from_millis(750)).await;
24 run_wasm(inputs2).await
25 });
26 let join3 = tokio::task::spawn(async move {
27 tokio::time::sleep(Duration::from_millis(1250)).await;
28 run_wasm(inputs3).await
29 });
30
31 // All tasks should join successfully.
32 join1.await??;
33 join2.await??;
34 join3.await??;
35 Ok(())
36 }
37
38 #[derive(Clone)]
39 struct Environment {
40 engine: Engine,
41 module: Module,
42 linker: Arc<Linker<WasiP1Ctx>>,
43 }
44
45 impl Environment {
new() -> Result<Self, Error>46 pub fn new() -> Result<Self, Error> {
47 let mut config = Config::new();
48 // Consume fuel for guests so that they can co-operatively yield during
49 // execution.
50 config.consume_fuel(true);
51
52 let engine = Engine::new(&config)?;
53 let module = Module::from_file(&engine, "target/wasm32-wasip1/debug/tokio-wasi.wasm")?;
54
55 // A `Linker` is shared in the environment amongst all stores, and this
56 // linker is used to instantiate the `module` above. This example only
57 // adds WASI functions to the linker, notably the async versions built
58 // on tokio.
59 let mut linker = Linker::new(&engine);
60 wasmtime_wasi::p1::add_to_linker_async(&mut linker, |cx| cx)?;
61
62 Ok(Self {
63 engine,
64 module,
65 linker: Arc::new(linker),
66 })
67 }
68 }
69
70 struct Inputs {
71 env: Environment,
72 name: String,
73 }
74
75 impl Inputs {
new(env: Environment, name: &str) -> Self76 fn new(env: Environment, name: &str) -> Self {
77 Self {
78 env,
79 name: name.to_owned(),
80 }
81 }
82 }
83
run_wasm(inputs: Inputs) -> Result<(), Error>84 async fn run_wasm(inputs: Inputs) -> Result<(), Error> {
85 let wasi = WasiCtx::builder()
86 // Let wasi print to this process's stdout.
87 .inherit_stdout()
88 // Set an environment variable so the wasm knows its name.
89 .env("NAME", &inputs.name)
90 .build_p1();
91 let mut store = Store::new(&inputs.env.engine, wasi);
92
93 // Put effectively unlimited fuel so it can run forever.
94 store.set_fuel(u64::MAX)?;
95 // WebAssembly execution will be paused for an async yield every time it
96 // consumes 10000 fuel.
97 store.fuel_async_yield_interval(Some(10000))?;
98
99 // Instantiate into our own unique store using the shared linker, afterwards
100 // acquiring the `_start` function for the module and executing it.
101 let instance = inputs
102 .env
103 .linker
104 .instantiate_async(&mut store, &inputs.env.module)
105 .await?;
106 instance
107 .get_typed_func::<(), ()>(&mut store, "_start")?
108 .call_async(&mut store, ())
109 .await?;
110
111 Ok(())
112 }
113