wasmer_wasix/runtime/task_manager/
tokio.rs1use std::sync::Mutex;
2use std::{num::NonZeroUsize, pin::Pin, sync::Arc, time::Duration};
3
4use futures::{Future, future::BoxFuture};
5use tokio::runtime::{Handle, Runtime};
6use virtual_mio::block_on;
7
8use crate::runtime::{SpawnType, task_manager::TaskWasmCallbacks};
9use crate::{WasiFunctionEnv, os::task::thread::WasiThreadError};
10
11use super::{SpawnMemoryTypeOrStore, TaskWasm, TaskWasmRunProperties, VirtualTaskManager};
12
13#[derive(Debug, Clone)]
14pub enum RuntimeOrHandle {
15 Handle(Handle),
16 Runtime(Handle, Arc<Mutex<Option<Runtime>>>),
17}
18impl From<Handle> for RuntimeOrHandle {
19 fn from(value: Handle) -> Self {
20 Self::Handle(value)
21 }
22}
23impl From<Runtime> for RuntimeOrHandle {
24 fn from(value: Runtime) -> Self {
25 Self::Runtime(value.handle().clone(), Arc::new(Mutex::new(Some(value))))
26 }
27}
28
29impl Drop for RuntimeOrHandle {
30 fn drop(&mut self) {
31 if let Self::Runtime(_, runtime) = self
32 && let Some(h) = runtime.lock().unwrap().take()
33 {
34 h.shutdown_timeout(Duration::from_secs(0))
35 }
36 }
37}
38
39impl RuntimeOrHandle {
40 pub fn handle(&self) -> &Handle {
41 match self {
42 Self::Handle(h) => h,
43 Self::Runtime(h, _) => h,
44 }
45 }
46}
47
48#[derive(Clone)]
49pub struct ThreadPool {
50 inner: rusty_pool::ThreadPool,
51}
52
53impl std::ops::Deref for ThreadPool {
54 type Target = rusty_pool::ThreadPool;
55
56 fn deref(&self) -> &Self::Target {
57 &self.inner
58 }
59}
60
61impl std::fmt::Debug for ThreadPool {
62 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63 f.debug_struct("ThreadPool")
64 .field("name", &self.get_name())
65 .field("current_worker_count", &self.get_current_worker_count())
66 .field("idle_worker_count", &self.get_idle_worker_count())
67 .finish()
68 }
69}
70
71#[derive(Clone, Debug)]
73pub struct TokioTaskManager {
74 rt: RuntimeOrHandle,
75 pool: Arc<ThreadPool>,
76}
77
78impl TokioTaskManager {
79 pub fn new<I>(rt: I) -> Self
80 where
81 I: Into<RuntimeOrHandle>,
82 {
83 let concurrency = std::thread::available_parallelism()
84 .unwrap_or(NonZeroUsize::new(1).unwrap())
85 .get();
86 let max_threads = 200usize.max(concurrency * 100);
87
88 Self {
89 rt: rt.into(),
90 pool: Arc::new(ThreadPool {
91 inner: rusty_pool::Builder::new()
92 .name("TokioTaskManager Thread Pool".to_string())
93 .core_size(max_threads)
94 .max_size(max_threads)
95 .build(),
96 }),
97 }
98 }
99
100 pub fn runtime_handle(&self) -> tokio::runtime::Handle {
101 self.rt.handle().clone()
102 }
103
104 pub fn pool_handle(&self) -> Arc<ThreadPool> {
105 self.pool.clone()
106 }
107}
108
109impl Default for TokioTaskManager {
110 fn default() -> Self {
111 Self::new(Handle::current())
112 }
113}
114
115impl VirtualTaskManager for TokioTaskManager {
116 fn sleep_now(&self, time: Duration) -> Pin<Box<dyn Future<Output = ()> + Send + Sync>> {
118 let handle = self.runtime_handle();
119 Box::pin(async move {
120 SleepNow::default()
121 .enter(handle, time)
122 .await
123 .ok()
124 .unwrap_or(())
125 })
126 }
127
128 fn task_shared(
130 &self,
131 task: Box<dyn FnOnce() -> BoxFuture<'static, ()> + Send + 'static>,
132 ) -> Result<(), WasiThreadError> {
133 self.rt.handle().spawn(async move {
134 let fut = task();
135 fut.await
136 });
137 Ok(())
138 }
139
140 fn task_wasm(&self, task: TaskWasm) -> Result<(), WasiThreadError> {
142 fn env_and_store(
143 task: TaskWasm,
144 ) -> Result<(WasiFunctionEnv, wasmer::Store, TaskWasmCallbacks), WasiThreadError> {
145 let (make_memory, instance_group_data) = match task.spawn_type {
146 SpawnType::CreateMemory => (SpawnMemoryTypeOrStore::New, None),
147 SpawnType::NewLinkerInstanceGroup(instance_group_data) => {
148 (SpawnMemoryTypeOrStore::New, Some(instance_group_data))
149 }
150 SpawnType::CreateMemoryOfType(t) => (SpawnMemoryTypeOrStore::Type(t), None),
151 SpawnType::AttachMemory(mem) => {
152 let mut store = task.env.runtime().new_store();
153 let memory = mem
154 .try_attach(&mut store)
155 .map_err(WasiThreadError::MemoryCreateFailed)?;
156 (SpawnMemoryTypeOrStore::StoreAndMemory(store, memory), None)
157 }
158 };
159
160 let (env, store) = WasiFunctionEnv::new_with_store(
161 task.module,
162 task.env,
163 task.globals,
164 make_memory,
165 task.update_layout,
166 task.call_initialize,
167 instance_group_data,
168 )?;
169 Ok((env, store, task.callbacks))
170 }
171
172 let (sx, rx) = std::sync::mpsc::channel();
173
174 if task.callbacks.trigger.is_some() {
175 tracing::trace!("spawning task_wasm trigger in async pool");
176 self.pool.execute(move || {
177 let (mut ctx, mut store, callbacks) = match env_and_store(task) {
178 Ok(x) => {
179 sx.send(Ok(())).unwrap();
180 x
181 }
182 Err(c) => {
183 tracing::error!("failed to prepare environment for task_wasm trigger: {c}");
184 sx.send(Err(c)).unwrap();
185 return;
186 }
187 };
188
189 let result = {
190 let mut trigger = (callbacks.trigger.unwrap())();
191 let pre_run = callbacks.pre_run;
192 let ctx = &mut ctx;
193 let store = &mut store;
194 block_on(async move {
195 let result = loop {
197 let env = ctx.data(store);
198 break tokio::select! {
199 r = &mut trigger => r,
200 _ = env.thread.wait_for_signal() => {
201 tracing::debug!("wait-for-signal(triggered)");
202 let mut ctx = ctx.env.clone().into_mut(store);
203 if let Err(err) =
204 crate::WasiEnv::do_pending_link_operations(
205 &mut ctx,
206 false
207 ).and_then(|()|
208 crate::WasiEnv::process_signals_and_exit(&mut ctx)
209 )
210 {
211 match err {
212 crate::WasiError::Exit(code) => Err(code),
213 err => {
214 tracing::error!("failed to process signals - {}", err);
215 continue;
216 }
217 }
218 } else {
219 continue;
220 }
221 }
222 _ = crate::wait_for_snapshot(env) => {
223 tracing::debug!("wait-for-snapshot(triggered)");
224 let mut ctx = ctx.env.clone().into_mut(store);
225 crate::os::task::WasiProcessInner::do_checkpoints_from_outside(&mut ctx);
226 continue;
227 }
228 };
229 };
230
231 if let Some(pre_run) = pre_run {
232 pre_run(ctx, store).await;
233 }
234
235 result
236 })
237 };
238
239 (callbacks.run)(TaskWasmRunProperties {
241 ctx,
242 store,
243 trigger_result: Some(result),
244 recycle: callbacks.recycle,
245 });
246 });
247 } else {
248 tracing::trace!("spawning task_wasm in blocking thread");
249
250 self.pool.execute(move || {
252 tracing::trace!("task_wasm started in blocking thread");
253 let (mut ctx, mut store, callbacks) = match env_and_store(task) {
254 Ok(x) => {
255 sx.send(Ok(())).unwrap();
256 x
257 }
258 Err(c) => {
259 sx.send(Err(c)).unwrap();
260 return;
261 }
262 };
263
264 if let Some(pre_run) = callbacks.pre_run {
265 block_on(pre_run(&mut ctx, &mut store));
266 }
267
268 (callbacks.run)(TaskWasmRunProperties {
270 ctx,
271 store,
272 trigger_result: None,
273 recycle: callbacks.recycle,
274 });
275 });
276 }
277
278 rx.recv()
279 .map_err(|_| WasiThreadError::InvalidWasmContext)??;
280
281 Ok(())
282 }
283
284 fn task_dedicated(
286 &self,
287 task: Box<dyn FnOnce() + Send + 'static>,
288 ) -> Result<(), WasiThreadError> {
289 self.pool.execute(move || {
290 task();
291 });
292 Ok(())
293 }
294
295 fn thread_parallelism(&self) -> Result<usize, WasiThreadError> {
297 Ok(std::thread::available_parallelism()
298 .map(usize::from)
299 .unwrap_or(8))
300 }
301}
302
303#[derive(Default)]
305pub struct SleepNow {
306 abort_handle: Option<tokio::task::AbortHandle>,
307}
308
309impl SleepNow {
310 pub async fn enter(
311 &mut self,
312 handle: tokio::runtime::Handle,
313 time: Duration,
314 ) -> Result<(), tokio::task::JoinError> {
315 let handle = handle.spawn(async move {
316 if time == Duration::ZERO {
317 tokio::task::yield_now().await;
318 } else {
319 tokio::time::sleep(time).await;
320 }
321 });
322 self.abort_handle = Some(handle.abort_handle());
323 handle.await
324 }
325}
326
327impl Drop for SleepNow {
328 fn drop(&mut self) {
329 if let Some(h) = self.abort_handle.as_ref() {
330 h.abort()
331 }
332 }
333}