jsonld_syntax/native_stack.rs
1//! Keeping the recursion of the JSON-LD algorithms off the caller's native
2//! stack.
3//!
4//! Context conversion, the Context Processing algorithm, Expansion and
5//! Compaction all recurse once per nesting level of their input. An embedder
6//! may run them on a thread whose stack it has already partly used, such as a
7//! Tokio worker deep inside a request handler, so the recursion depth alone
8//! cannot decide whether the thread's stack suffices.
9//!
10//! A synchronous recursion point runs its callee through
11//! [`on_sufficient_stack`]. An `async` one wraps its callee in [`Recursion`]:
12//! boxing a recursive call moves the callee's *state* to the heap but not its
13//! *polling*, because a parent polls its child from inside its own `poll`, so
14//! the native stack still deepens by one set of poll frames per level. Either
15//! way, when less than [`RED_ZONE`] of the current stack is left, the callee
16//! runs on a freshly allocated segment of [`SEGMENT`] bytes instead. Whatever
17//! bounds a recursion stays what bounds it; this only guarantees the bound is
18//! reached on any thread.
19
20use std::{
21 future::Future,
22 pin::Pin,
23 task::{Context as TaskContext, Poll},
24};
25
26/// Stack left below which a recursion level moves to a fresh segment.
27///
28/// The value `jstrict` uses, so one red zone governs every recursion over a
29/// document, from parsing and printing its JSON to the JSON-LD algorithms.
30pub const RED_ZONE: usize = jstrict::native_stack::RED_ZONE;
31
32/// Size of each fresh stack segment.
33pub const SEGMENT: usize = jstrict::native_stack::SEGMENT;
34
35/// Runs `callee`, on a fresh stack segment when the current one is nearly used.
36///
37/// Delegates to [`jstrict::native_stack::on_sufficient_stack`].
38pub fn on_sufficient_stack<R>(callee: impl FnOnce() -> R) -> R {
39 jstrict::native_stack::on_sufficient_stack(callee)
40}
41
42/// A recursive call of an `async` algorithm.
43///
44/// The callee lives on the heap, like a `Box::pin` of it would, and every poll
45/// of it runs through [`on_sufficient_stack`], so polling a chain of nested
46/// calls cannot exhaust the native stack.
47pub struct Recursion<F>(Pin<Box<F>>);
48
49impl<F> Recursion<F> {
50 /// Wraps the recursive call `callee`.
51 pub fn new(callee: F) -> Self {
52 Self(Box::pin(callee))
53 }
54}
55
56impl<F: Future> Future for Recursion<F> {
57 type Output = F::Output;
58
59 fn poll(self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll<Self::Output> {
60 let callee = &mut self.get_mut().0;
61 on_sufficient_stack(|| callee.as_mut().poll(cx))
62 }
63}