wasmer_wasix/syscalls/wasi/
poll_oneoff.rs1use serde::{Deserialize, Serialize};
2use wasmer_wasix_types::wasi::{Subclockflags, SubscriptionClock, Userdata};
3
4use super::*;
5use crate::{
6 fs::{InodeValFilePollGuard, InodeValFilePollGuardJoin},
7 state::PollEventSet,
8 syscalls::*,
9};
10
11pub(crate) fn validate_poll_subscriptions_count(
12 env: &WasiEnv,
13 nsubscriptions: usize,
14) -> Result<(), Errno> {
15 if nsubscriptions == 0 {
16 return Err(Errno::Inval);
17 }
18 if let Some(max) = env.capabilities.polling.max_poll_subscriptions
19 && nsubscriptions > max
20 {
21 return Err(Errno::Toobig);
22 }
23 Ok(())
24}
25
26#[derive(Serialize, Deserialize)]
28pub enum EventResultType {
29 Clock(u8),
30 Fd(EventFdReadwrite),
31}
32
33#[derive(Serialize, Deserialize)]
35pub struct EventResult {
36 pub userdata: Userdata,
38 pub error: Errno,
40 pub type_: Eventtype,
42 pub inner: EventResultType,
44}
45impl EventResult {
46 pub fn into_event(self) -> Event {
47 Event {
48 userdata: self.userdata,
49 error: self.error,
50 type_: self.type_,
51 u: match self.inner {
52 EventResultType::Clock(id) => EventUnion { clock: id },
53 EventResultType::Fd(fd) => EventUnion { fd_readwrite: fd },
54 },
55 }
56 }
57}
58
59#[instrument(level = "trace", skip_all, fields(timeout_ms = field::Empty, fd_guards = field::Empty, seen = field::Empty), ret)]
74pub fn poll_oneoff<M: MemorySize + 'static>(
75 mut ctx: FunctionEnvMut<'_, WasiEnv>,
76 in_: WasmPtr<Subscription, M>,
77 out_: WasmPtr<Event, M>,
78 nsubscriptions: M::Offset,
79 nevents: WasmPtr<M::Offset, M>,
80) -> Result<Errno, WasiError> {
81 WasiEnv::do_pending_operations(&mut ctx)?;
82
83 let Ok::<usize, _>(nsubscriptions_usize) = nsubscriptions.try_into() else {
84 return Ok(Errno::Toobig);
85 };
86 wasi_try_ok!(validate_poll_subscriptions_count(
87 ctx.data(),
88 nsubscriptions_usize
89 ));
90
91 ctx = wasi_try_ok!(maybe_backoff::<M>(ctx)?);
92 ctx = wasi_try_ok!(maybe_snapshot::<M>(ctx)?);
93 ctx.data_mut().poll_seed += 1;
94
95 let mut env = ctx.data();
96 let mut memory = unsafe { env.memory_view(&ctx) };
97
98 let subscription_array = wasi_try_mem_ok!(in_.slice(&memory, nsubscriptions));
99 let mut subscriptions = Vec::new();
100 wasi_try_ok!(
101 subscriptions
102 .try_reserve_exact(subscription_array.len() as usize)
103 .map_err(|_| Errno::Nomem)
104 );
105 for n in 0..subscription_array.len() {
106 let n = (n + env.poll_seed) % subscription_array.len();
107 let sub = subscription_array.index(n);
108 let s = wasi_try_mem_ok!(sub.read());
109 subscriptions.push((None, PollEventSet::default(), s));
110 }
111
112 wasi_try_mem_ok!(nevents.write(&memory, M::ZERO));
114
115 let process_events = |ctx: &FunctionEnvMut<'_, WasiEnv>, triggered_events: Vec<Event>| {
117 let mut env = ctx.data();
118 let mut memory = unsafe { env.memory_view(&ctx) };
119
120 let mut events_seen: u32 = 0;
122 let event_array = wasi_try_mem!(out_.slice(&memory, nsubscriptions));
123 for event in triggered_events {
124 wasi_try_mem!(event_array.index(events_seen as u64).write(event));
125 events_seen += 1;
126 }
127 let events_seen: M::Offset = events_seen.into();
128 let out_ptr = nevents.deref(&memory);
129 wasi_try_mem!(out_ptr.write(events_seen));
130 Errno::Success
131 };
132
133 poll_oneoff_internal::<M, _>(ctx, subscriptions, process_events)
135}
136
137struct PollBatch {
138 pid: WasiProcessId,
139 tid: WasiThreadId,
140 evts: Vec<Event>,
141 joins: Vec<InodeValFilePollGuardJoin>,
142}
143impl PollBatch {
144 fn new(pid: WasiProcessId, tid: WasiThreadId, fds: Vec<InodeValFilePollGuard>) -> Self {
145 Self {
146 pid,
147 tid,
148 evts: Vec::new(),
149 joins: fds
150 .into_iter()
151 .map(InodeValFilePollGuardJoin::new)
152 .collect(),
153 }
154 }
155}
156impl Future for PollBatch {
157 type Output = Result<Vec<EventResult>, Errno>;
158 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
159 let pid = self.pid;
160 let tid = self.tid;
161 let mut done = false;
162
163 let mut evts = Vec::new();
164 for mut join in self.joins.iter_mut() {
165 let fd = join.fd();
166 let peb = join.peb();
167 let mut guard = Pin::new(join);
168 match guard.poll(cx) {
169 Poll::Pending => {}
170 Poll::Ready(e) => {
171 for (evt, readiness) in e {
172 tracing::trace!(
173 fd,
174 readiness = ?readiness,
175 userdata = evt.userdata,
176 ty = evt.type_ as u8,
177 peb,
178 "triggered"
179 );
180 evts.push(evt);
181 }
182 }
183 }
184 }
185
186 if !evts.is_empty() {
187 return Poll::Ready(Ok(evts));
188 }
189
190 Poll::Pending
191 }
192}
193
194pub(crate) fn poll_fd_guard(
195 state: &Arc<WasiState>,
196 peb: PollEventSet,
197 fd: WasiFd,
198 s: Subscription,
199) -> Result<InodeValFilePollGuard, Errno> {
200 let fd_entry = state.fs.get_fd(fd)?;
201 let requires_access = match s.type_ {
202 Eventtype::FdRead => Rights::FD_READ,
203 Eventtype::FdWrite => Rights::FD_WRITE,
204 _ => Rights::empty(),
205 };
206
207 if !(fd_entry.inner.rights.contains(Rights::POLL_FD_READWRITE)
208 && fd_entry.inner.rights.contains(requires_access))
209 {
210 return Err(Errno::Access);
211 }
212 let inode = fd_entry.inode;
213
214 let guard = inode.read();
215 crate::fs::InodeValFilePollGuard::new(fd, peb, s, guard.deref()).ok_or(Errno::Badf)
216}
217
218pub(crate) fn poll_oneoff_internal<'a, M: MemorySize, After>(
233 mut ctx: FunctionEnvMut<'a, WasiEnv>,
234 mut subs: Vec<(Option<WasiFd>, PollEventSet, Subscription)>,
235 process_events: After,
236) -> Result<Errno, WasiError>
237where
238 After: FnOnce(&FunctionEnvMut<'a, WasiEnv>, Vec<Event>) -> Errno,
239{
240 wasi_try_ok!(WasiEnv::process_signals_and_exit(&mut ctx)?);
241 let subs_len = subs.len();
242 wasi_try_ok!(validate_poll_subscriptions_count(ctx.data(), subs_len));
243
244 let pid = ctx.data().pid();
245 let tid = ctx.data().tid();
246
247 let mut env = ctx.data();
249 let state = ctx.data().state.deref();
250 let memory = unsafe { env.memory_view(&ctx) };
251
252 let clock_cnt = subs
255 .iter()
256 .filter(|a| a.2.type_ == Eventtype::Clock)
257 .count();
258 let mut clock_subs: Vec<(SubscriptionClock, u64)> = Vec::with_capacity(subs.len());
259 let mut time_to_sleep = Duration::MAX;
260
261 let mut env = ctx.data();
264 let state = ctx.data().state.deref();
265 let mut memory = unsafe { env.memory_view(&ctx) };
266 for (fd, peb, s) in subs.iter_mut() {
267 let fd = match s.type_ {
268 Eventtype::FdRead => {
269 let file_descriptor = unsafe { s.data.fd_readwrite.file_descriptor };
270 *fd = Some(file_descriptor);
271 *peb |= (PollEvent::PollIn as PollEventSet);
272 file_descriptor
273 }
274 Eventtype::FdWrite => {
275 let file_descriptor = unsafe { s.data.fd_readwrite.file_descriptor };
276 *fd = Some(file_descriptor);
277 *peb |= (PollEvent::PollOut as PollEventSet);
278 file_descriptor
279 }
280 Eventtype::Clock => {
281 let clock_info = unsafe { s.data.clock };
282 if clock_info.clock_id == Clockid::Realtime
283 || clock_info.clock_id == Clockid::Monotonic
284 {
285 if clock_subs
287 .iter()
288 .any(|c| c.0.clock_id == clock_info.clock_id && c.1 == s.userdata)
289 {
290 continue;
291 }
292
293 if clock_info.timeout == 0 {
296 time_to_sleep = Duration::MAX;
297 } else if clock_info.timeout == 1 {
298 time_to_sleep = Duration::ZERO;
299 clock_subs.push((clock_info, s.userdata));
300 } else {
301 time_to_sleep = if clock_info
304 .flags
305 .contains(Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME)
306 {
307 let now = wasi_try_ok!(platform_clock_time_get(
308 Snapshot0Clockid::Monotonic,
309 1
310 )) as u64;
311
312 if clock_info.timeout <= now {
313 Duration::ZERO
314 } else {
315 Duration::from_nanos(clock_info.timeout) - Duration::from_nanos(now)
316 }
317 } else {
318 Duration::from_nanos(clock_info.timeout)
320 };
321
322 clock_subs.push((clock_info, s.userdata));
323 }
324 continue;
325 } else {
326 error!("polling not implemented for these clocks yet");
327 return Ok(Errno::Inval);
328 }
329 }
330 _ => {
331 continue;
332 }
333 };
334 }
335
336 let mut events_seen: u32 = 0;
337
338 let batch = {
339 let state = ctx.data().state.clone();
341 let tasks = ctx.data().tasks().clone();
342 let mut guards = {
343 let mut fd_guards = Vec::with_capacity(subs.len());
346
347 #[allow(clippy::significant_drop_in_scrutinee)]
348 for (fd, peb, s) in subs {
349 if let Some(fd) = fd {
350 let wasi_file_ref = wasi_try_ok!(poll_fd_guard(&state, peb, fd, s));
351 fd_guards.push(wasi_file_ref);
352 }
353 }
354
355 if fd_guards.len() > 10 {
356 let small_list: Vec<_> = fd_guards.iter().take(10).collect();
357 tracing::Span::current().record("fd_guards", format!("{small_list:?}..."));
358 } else {
359 tracing::Span::current().record("fd_guards", format!("{fd_guards:?}"));
360 }
361
362 fd_guards
363 };
364
365 PollBatch::new(pid, tid, guards)
367 };
368
369 let timeout = match time_to_sleep {
371 Duration::ZERO => {
372 Span::current().record("timeout_ns", "nonblocking");
373 Some(Duration::ZERO)
374 }
375 Duration::MAX => {
376 Span::current().record("timeout_ns", "infinite");
377 None
378 }
379 time => {
380 Span::current().record("timeout_ns", time.as_millis());
381 Some(time)
382 }
383 };
384
385 let process_timeout = {
387 let clock_subs = clock_subs.clone();
388 |ctx: &FunctionEnvMut<'a, WasiEnv>| {
389 if clock_subs.is_empty() {
391 tracing::warn!("triggered_timeout (without any clock subscriptions)");
392 }
393 let mut evts = Vec::new();
394 for (clock_info, userdata) in clock_subs {
395 let evt = Event {
396 userdata,
397 error: Errno::Success,
398 type_: Eventtype::Clock,
399 u: EventUnion { clock: 0 },
400 };
401 Span::current().record(
402 "seen",
403 format!(
404 "clock(id={},userdata={})",
405 clock_info.clock_id as u32, evt.userdata
406 ),
407 );
408 evts.push(evt);
409 }
410 evts
411 }
412 };
413
414 #[cfg(feature = "sys")]
415 if env.capabilities.threading.enable_blocking_sleep && subs_len == 1 {
416 if let Some(timeout) = timeout {
422 std::thread::sleep(timeout);
423 process_events(&ctx, process_timeout(&ctx));
424 return Ok(Errno::Success);
425 }
426 }
427
428 let tasks = env.tasks().clone();
429 let timeout = async move {
430 if let Some(timeout) = timeout {
431 tasks.sleep_now(timeout).await;
432 } else {
433 InfiniteSleep::default().await
434 }
435 };
436
437 let trigger = async move {
439 tokio::select! {
440 biased;
441 res = batch => res,
442 _ = timeout => Err(Errno::Timedout)
443 }
444 };
445
446 let process_events = {
449 let clock_subs = clock_subs.clone();
450 |ctx: &FunctionEnvMut<'a, WasiEnv>, events: Result<Vec<Event>, Errno>| {
451 match events {
453 Ok(evts) => {
454 if evts.len() == 1 {
456 Span::current().record("seen", format!("{:?}", evts.first().unwrap()));
457 } else {
458 Span::current().record("seen", format!("trigger_cnt=({})", evts.len()));
459 }
460
461 process_events(ctx, evts)
463 }
464 Err(Errno::Timedout) => process_events(ctx, process_timeout(ctx)),
465 Err(Errno::Again) => process_events(ctx, Default::default()),
467 Err(err) => {
469 tracing::warn!("failed to poll during deep sleep - {}", err);
470 err
471 }
472 }
473 }
474 };
475
476 if let Some(events) = unsafe { handle_rewind::<M, Result<Vec<EventResult>, Errno>>(&mut ctx) } {
478 let events = events.map(|events| events.into_iter().map(EventResult::into_event).collect());
479 process_events(&ctx, events);
480 return Ok(Errno::Success);
481 }
482
483 let res = __asyncify_with_deep_sleep::<M, Result<Vec<EventResult>, Errno>, _>(
485 ctx,
486 Box::pin(trigger),
487 )?;
488 if let AsyncifyAction::Finish(mut ctx, events) = res {
489 let events = events.map(|events| events.into_iter().map(EventResult::into_event).collect());
490 process_events(&ctx, events);
491 }
492 Ok(Errno::Success)
493}