Skip to main content

odem_rs_core/continuation/
mod.rs

1//! The `continuation` module provides the implementation of a type-erased,
2//! intrusively linked futures abstraction used in the scheduler of the
3//! simulation library.
4//!
5//! Continuations are held by a calendar, allowing them to be scheduled and
6//! managed based on model time.
7//!
8//! The [`Continuation`] type represents a continuation that can be scheduled
9//! and executed within the simulation library. It is designed to handle
10//! type-erased futures and supports branding to tie [token witnesses] to their
11//! runtime-state, enabling statically checked validity of state transitions.
12//!
13//! [token witnesses]: token::State
14
15use core::{
16	any::Any,
17	cell::Cell,
18	fmt,
19	hint::unreachable_unchecked,
20	panic::Location,
21	pin::Pin,
22	ptr::NonNull,
23	task::{Context, Poll},
24};
25use intrusive_collections::{LinkedList, RBTreeLink};
26
27pub use adapter::{Adapter, PointerOps};
28pub use puck::Puck;
29pub use share::{Label, Share};
30
31use crate::{
32	Dispatch, ExitStatus,
33	calendar::{PlanState, Scheduler},
34	config::Config,
35	error::NotIdle,
36	fsm::*,
37	ptr::{IntrusivelyCounted, Irc, IrcBox, IrcBoxed},
38	simulator::{Mark, Prec},
39};
40
41mod adapter;
42mod puck;
43mod share;
44
45/* ********************************************************************* Continuation */
46
47/// The type of intrusive link stored in every [Continuation].
48pub type Link = RBTreeLink;
49
50/// A structure of type-erased, intrusively linked [futures].
51///
52/// Pointers to instances of this type are used in the scheduler of the
53/// simulation library. They are oblivious to lifetime-restrictions and
54/// erase the concrete types of the future and the process instance.
55/// The latter types are recovered during dynamic lookup, but the
56/// lifetime-restrictions have to be observed by the abstractions provided
57/// in the simulation library.
58///
59/// Continuations may be *branded* using an additional lifetime that ties
60/// [Token] to specific instances. This allows composition and chaining of
61/// method calls that depend on specific [States] without having to
62/// continuously test the current states within the method implementations.
63///
64/// [futures]: Future
65/// [Token]: token
66/// [States]: State
67pub struct Continuation<'brand, C: ?Sized + Config> {
68	/// An intrusive link for insertion of a continuation into the calendar.
69	hook: Link,
70	/// Intrusive reference counter and raw pointer to the most specialized
71	/// version of the type containing this continuation.
72	///
73	/// This is needed to provide run-time polymorphism over the type
74	/// of future being executed as well as what code is run on dropping. See
75	/// [Dispatch] for further information.
76	task_box: IrcBox<ContBox>,
77	/// Pointer to data shared across different continuations.
78	share: NonNull<Share<C>>,
79	/// A list of continuations waiting for this continuation to terminate.
80	pending: Cell<LinkedList<Adapter<C>>>,
81	/// Precedence of this continuation relative to other continuations from the
82	/// same agent.
83	prec: Cell<Prec>,
84	/// Current state of this continuation instance.
85	state: StateMachine<'brand, State<C>>,
86	/// Span information for the current continuation.
87	#[cfg(feature = "tracing")]
88	span: tracing::Span,
89}
90
91impl<C: ?Sized + Config> Continuation<'static, C> {
92	/// Creates a new continuation initialized in [State::Born] with specific
93	/// location information attached.
94	///
95	/// [State::Born]: erased::State::Born
96	pub(crate) fn new(prec: Prec, location: &'static Location<'static>) -> Self {
97		Self {
98			hook: Link::new(),
99			task_box: IrcBox::with_location(ContBox::new(), location),
100			share: NonNull::dangling(),
101			pending: Cell::new(LinkedList::new(Adapter::NEW)),
102			prec: Cell::new(prec),
103			state: StateMachine::default(),
104			#[cfg(feature = "tracing")]
105			span: tracing::Span::current(),
106		}
107	}
108
109	/// Returns the exit code of the continuation, or `None` if it hasn't
110	/// terminated.
111	pub(crate) fn result(&self) -> Option<ExitStatus> {
112		self.brand(|task, once| {
113			task.token(once)
114				.into_term()
115				.map(|state| task.branded_result(&state))
116				.ok()
117		})
118	}
119
120	/// Reactivates the continuation at the current model-time.
121	pub(crate) fn wake(this: Irc<Self>) -> Result<(), NotIdle> {
122		this.clone().brand(|task, once| {
123			let idle = task.token(once).into_idle()?;
124
125			task.branded_share(&idle)
126				.sim()
127				.calendar()
128				.activate(task.clone(), idle);
129
130			Ok(())
131		})
132	}
133
134	/// Returns the next model time this continuation is scheduled for, or
135	/// `None` if it isn't scheduled.
136	pub(crate) fn time(&self) -> Option<C::Time> {
137		self.brand(|task, once| {
138			task.token(once)
139				.into_next()
140				.ok()
141				.map(|next| task.next_time(&next))
142		})
143	}
144}
145
146impl<'brand, C: ?Sized + Config> Continuation<'brand, C> {
147	/// Enters the [`Span`] associated with the agent owning the shared data.
148	///
149	/// [`Span`]: tracing::Span
150	#[cfg(feature = "tracing")]
151	pub fn enter_span(&self) -> tracing::span::Entered<'_> {
152		self.span.enter()
153	}
154
155	/// No-op in lieu of entering the span associated with the agent owning the
156	/// shared data.
157	#[cfg(not(feature = "tracing"))]
158	pub const fn enter_span(&self) {}
159
160	/// Sets a new virtual function pointer for this continuation.
161	///
162	/// # Safety
163	/// The caller is responsible to ensure that the pointer stays valid for
164	/// the duration of the continuation's life.
165	pub(crate) unsafe fn set_vptr(&self, vptr: NonNull<dyn Dispatch + '_>) {
166		unsafe {
167			self.task_box.set_vptr(vptr);
168		}
169	}
170
171	/// Clears the virtual function pointer for this continuation, preventing any
172	/// methods from being called.
173	///
174	/// This can be used to prevent [`Dispatch::reclaim`] from being called,
175	/// even if the reference counter reaches zero.
176	pub(crate) fn clear_vptr(&self) {
177		self.task_box.clear_vptr();
178	}
179
180	/// Returns the number of references to this continuation.
181	pub(crate) fn use_count(&self) -> usize {
182		self.task_box.refs.get()
183	}
184
185	/// Returns a copy of the internal [State](erased::State).
186	pub(crate) fn state(&self) -> &StateMachine<'brand, State<C>> {
187		&self.state
188	}
189
190	/// Returns the current [Prec] of this continuation.
191	pub(crate) fn prec(&self) -> Prec {
192		self.prec.get()
193	}
194
195	/// Sets the new [Prec] of this continuation.
196	pub(crate) fn set_prec(&self, prec: Prec) {
197		self.prec.set(prec);
198	}
199
200	/// Adds another continuation to the list of continuations to be awoken upon terminating.
201	#[inline]
202	pub(crate) fn insert_pending(&self, other: Irc<Continuation<'static, C>>) {
203		let mut list = self.pending.take();
204		list.push_back(other);
205		self.pending.set(list);
206	}
207
208	/// Removes a previously added continuation from the list of pending continuations.
209	///
210	/// # Safety
211	/// It is the caller's responsibility to ensure that the continuation had been
212	/// added previously via [Self::insert_pending].
213	pub(crate) unsafe fn remove_pending(&self, other: &Continuation<'static, C>) {
214		// Only unlink if the other continuation is actually linked right now.
215		// This can happen during panics, when the `drop` impl of a `Join`
216		// attempts to remove a stored continuation from the pending list, but
217		// it has already been removed by a prior reactivation.
218		if other.hook.is_linked() {
219			let mut list = self.pending.take();
220
221			unsafe {
222				list.cursor_mut_from_ptr(other.detach()).remove();
223			}
224
225			self.pending.set(list);
226		}
227	}
228
229	/// Awakens all pending continuations and clears the list.
230	pub(crate) fn wake_pending(&self) {
231		for task in self.pending.take() {
232			Continuation::wake(task).ok();
233		}
234	}
235
236	/// Converts the specific brand into a generic brand, breaking the
237	/// connection with the equally branded token.
238	pub(crate) fn detach(&self) -> &Continuation<'static, C> {
239		unsafe { core::mem::transmute(self) }
240	}
241
242	/// Returns the [`Location`] information for this `Continuation`.
243	pub(crate) fn location(&self) -> &'static Location<'static> {
244		IrcBox::location(&self.task_box)
245	}
246
247	/// Performs a runtime-check if this continuation has been dereferenced on the
248	/// same thread as the one the executor is running and panicks if that is
249	/// not the case.
250	///
251	/// # Safety
252	/// This method can only be called after the `Continuation` has been
253	/// activated. Calling it is thread-safe.
254	unsafe fn is_same_thread(&self) -> bool {
255		// Extract the shared data from the task.
256		let share = unsafe { self.share.as_ref() };
257
258		// Compare the pointer-address of this continuation's simulation context
259		// to the pointer-address of the thread-local simulation context.
260		crate::erased::with(|sim| core::ptr::addr_eq(&**share.sim(), sim)).unwrap_or(false)
261	}
262
263	/// Returns an enumeration copy of the internal [State](token::State).
264	pub(crate) fn token(&self, once: Ephemeral<'brand>) -> token::State<'brand> {
265		self.state.token(once)
266	}
267
268	/// Binds a continuation to [shared data].
269	///
270	/// # Safety
271	/// The caller is responsible to ensure that the shared-data-reference
272	/// outlives the (active) part of the continuation's life.
273	///
274	/// [shared data]: Share
275	pub(crate) unsafe fn bind(
276		mut self: Pin<&mut Self>,
277		born: token::Born<'brand>,
278		share: &Share<C>,
279	) -> token::Idle<'brand> {
280		// assign the reference to the shared data
281		self.share = NonNull::from(share);
282
283		// enter our `Span` to properly record the transition
284		let _span = self.enter_span();
285
286		// transition into state `Idle`
287		self.state.transition(born, ())
288	}
289
290	/// Purges the continuation from the calendar.
291	pub(crate) fn deschedule(&self, next: token::Next<'brand>) -> token::Idle<'brand> {
292		let share = self.branded_share(&next);
293
294		// remove the continuation from the calendar
295		share.sim().calendar().remove(self, next)
296	}
297
298	/// Removes the active state from the continuation.
299	pub(crate) fn deactivate(&self, busy: token::Busy<'brand>) -> token::Idle<'brand> {
300		let share = self.branded_share(&busy);
301
302		// deregister the continuation from the active cell
303		share.sim().unslot(self, busy)
304	}
305
306	/// Polls the underlying future of this continuation.
307	pub(crate) fn poll(&self, busy: token::Busy<'brand>, cx: &mut Context<'_>) -> Poll<()> {
308		// read the virtual function from the table
309
310		// SAFETY: the busy-token testifies that the continuation has been
311		// bound, which ensures that the vptr to the virtual function table has
312		// been set; pinning is ensured by the binding routine requiring it
313		let vtab = unsafe { Pin::new_unchecked(self.task_box.vptr.get().unwrap().as_ref()) };
314
315		// temporarily escape the branding to allow `Future::poll()` to rebrand
316		// without accidentally creating two tokens for the same instance
317		let (once, res) = self.state.debrand(busy, move |_| vtab.poll(cx));
318
319		// analyze the resulting state
320		match self.token(once).into_busy() {
321			Ok(busy) => match res {
322				Poll::Ready(result) => {
323					// busy -> done
324					let _: token::Done<'_> = self.state.transition(busy, result);
325
326					// reactivate pending continuations on completion
327					self.wake_pending();
328
329					Poll::Ready(())
330				}
331				Poll::Pending => {
332					// busy -> idle
333					let _: token::Idle<'_> = self.state.transition(busy, ());
334					Poll::Pending
335				}
336			},
337			Err(_err) => {
338				debug_assert!(
339					res.is_pending(),
340					"task in state `{:?}` should not have been able to terminate",
341					_err.0
342				);
343				Poll::Pending
344			}
345		}
346	}
347
348	/// Returns a reference to the shared data for this continuation.
349	pub(crate) fn branded_share<'s, I>(&'s self, init: &I) -> &'s Share<C>
350	where
351		I: Into<token::Init<'brand>>,
352	{
353		let _ = init;
354
355		// SAFETY: the token witness testifies that the shared data is
356		// initialized, which only happens during binding
357		unsafe { self.share.as_ref() }
358	}
359
360	/// Returns a reference to the shared data if initialization of this continuation
361	/// has been completed.
362	pub(crate) fn share(&self) -> Option<&Share<C>> {
363		if self.state().erased().is_init() {
364			// SAFETY: once bound, the pointer stays valid
365			Some(unsafe { self.share.as_ref() })
366		} else {
367			None
368		}
369	}
370
371	/// Grants access to the calendar state if in state [`Next`](State::Next).
372	pub(crate) fn next_state<F, R>(&self, next: &token::Next<'brand>, f: F) -> R
373	where
374		F: FnOnce(&<C::Plan as Scheduler>::State) -> R,
375	{
376		let _ = next;
377
378		// SAFETY: the `Next` token guarantees that this continuation is in the
379		// correct state
380		match &*self.state.borrow() {
381			State::Next(state) => f(state),
382			_ => unsafe { unreachable_unchecked() },
383		}
384	}
385
386	/// Returns the model time that the continuation will be activated.
387	pub(crate) fn next_time(&self, next: &token::Next<'brand>) -> C::Time {
388		self.next_state(next, |s| s.time())
389	}
390
391	/// Returns the [Cell] containing the current mark of the shared data
392	/// associated with this continuation.
393	///
394	/// This value is used to organize different continuations with identical shared
395	/// data in the calendar such that they are executed in a contiguous batch.
396	pub(crate) fn mark<'s, I>(&'s self, init: &I) -> &'s Cell<Mark>
397	where
398		I: Into<token::Init<'brand>>,
399	{
400		self.branded_share(init).mark()
401	}
402
403	/// Returns the exit code of this continuation.
404	pub(crate) fn branded_result<T>(&self, _: &T) -> ExitStatus
405	where
406		T: Into<token::Term<'brand>>,
407	{
408		// SAFETY: the continuation is in state `Done` or `Gone` per the token witness
409		match &*self.state.borrow() {
410			State::Done(rc) | State::Gone(rc) => *rc,
411			_ => unsafe { unreachable_unchecked() },
412		}
413	}
414}
415
416// Continuations offer unique methods for Branded variants
417impl<'b, C: ?Sized + Config> Stateful for Continuation<'b, C> {
418	type Brand = &'b ();
419
420	unsafe fn enter(&self) {
421		unsafe {
422			self.state.enter();
423		}
424	}
425
426	unsafe fn leave(&self) {
427		unsafe {
428			self.state.leave();
429		}
430	}
431}
432
433impl<'b, C: ?Sized + Config> Rebrand<'b> for Continuation<'b, C> {
434	type Kind<'a> = Continuation<'a, C>;
435}
436
437// Continuations only contain pointers to pinned data and are not pinned themselves
438impl<C: ?Sized + Config> Unpin for Continuation<'_, C> {}
439
440impl<C: ?Sized + Config> fmt::Debug for Continuation<'_, C> {
441	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
442		let mut s = f.debug_struct("Continuation");
443
444		s.field("state", &self.state.borrow())
445			.field("location", self.location());
446
447		self.brand(|task, once| {
448			struct PrettyName(Label);
449
450			impl fmt::Debug for PrettyName {
451				fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
452					write!(f, "\"{}\"", &self.0)
453				}
454			}
455
456			let state = task.token(once);
457			let init = state.as_init()?;
458
459			s.field("label", &PrettyName(task.branded_share(init).label()))
460				.field("rank", &task.branded_share(init).rank());
461
462			None::<()>
463		});
464
465		s.field("prec", &self.prec().float_range())
466			.field("refs", &self.use_count())
467			.finish()
468	}
469}
470
471impl<C: Config> crate::erased::Continuation for Continuation<'static, C> {
472	fn subject(&self) -> &dyn Any {
473		self.share().map_or(&(), |shared| shared.subject())
474	}
475
476	fn label(&self) -> Label {
477		self.share()
478			.map_or(Label::default(), |shared| shared.label())
479	}
480
481	fn prec(&self) -> Prec {
482		self.prec()
483	}
484
485	fn state(&self) -> erased::State {
486		self.state().borrow().erased()
487	}
488}
489
490unsafe impl<C: ?Sized + Config> IntrusivelyCounted for Continuation<'_, C> {
491	fn irc_box(&self) -> &IrcBox<dyn IrcBoxed> {
492		&self.task_box
493	}
494}
495
496/* ************************************************************** Irc Support */
497
498/// Container type used to support [intrusive reference counting](Irc).
499pub struct ContBox {
500	/// Contains a counter for the number of references to the outer
501	/// [`Continuation`].
502	refs: Cell<usize>,
503	/// Pointer to the most-specialized version of the instance.
504	/// Used to recover type information during dynamic dispatch.
505	vptr: Cell<Option<NonNull<dyn Dispatch>>>,
506}
507
508impl ContBox {
509	/// Creates a new irc box from a location with zero references and no
510	/// dispatcher.
511	const fn new() -> Self {
512		ContBox {
513			refs: Cell::new(0),
514			vptr: Cell::new(None),
515		}
516	}
517
518	/// Sets the pointer to the vtable.
519	///
520	/// # Safety
521	/// It is the callers' responsibility to ensure that the `vptr` pointer to
522	/// the `dyn Dispatch` object outlives the continuation box.
523	unsafe fn set_vptr(&self, vptr: NonNull<dyn Dispatch + '_>) {
524		unsafe {
525			use core::mem::transmute;
526
527			self.vptr.set(Some(transmute::<
528				NonNull<dyn Dispatch + '_>,
529				NonNull<dyn Dispatch + 'static>,
530			>(vptr)));
531		}
532	}
533
534	/// Clears the pointer to the vtable.
535	///
536	/// This can be useful to prevent [`IrcBoxed::release`] from returning
537	/// a destructor function in case the reference counter reaches zero.
538	fn clear_vptr(&self) {
539		self.vptr.set(None);
540	}
541}
542
543unsafe impl IrcBoxed for ContBox {
544	fn ref_count(&self) -> usize {
545		self.refs.get()
546	}
547
548	fn acquire(&self, _: crate::ptr::Private) {
549		self.refs.set(self.refs.get() + 1);
550	}
551
552	fn release(&self, _: crate::ptr::Private) {
553		self.refs.set(self.refs.get() - 1);
554	}
555
556	fn reclaim(&self, _: crate::ptr::Private) -> Option<fn(NonNull<dyn IrcBoxed>)> {
557		self.vptr.get().is_some().then_some(
558			// return a function reclaiming the outer type if no references point
559			// to the Continuation anymore; this separation is necessary to prevent
560			// overlapping references to this Continuation from self and (indirectly) from
561			// the inner dyn object
562			|this| unsafe {
563				// restore the pointer to the virtual table
564				let this = this.cast::<Self>().as_ref();
565				let mut vptr = this.vptr.get().unwrap_unchecked();
566
567				// call the reclaim-method on an exclusive reference to the dyn
568				// object; the temporary reference to the continuation has been dropped
569				// previously, and no other active references exist to the continuation
570				// which allows us to take this exclusive reference
571				Pin::new_unchecked(vptr.as_mut()).reclaim();
572			},
573		)
574	}
575}
576
577/* ************************************************************** Continuation States */
578
579fsm! {
580	/// Represents the state of a [`Continuation`] throughout its lifecycle.
581	///
582	/// A `Continuation` starts in the [`Born`] state without references to
583	/// shared data or its future, as provided by its owning [Job] or [Agent].
584	/// After binding pinned references to it, the `Continuation` moves to the
585	/// [`Idle`] state, indicating it is unscheduled.
586	///
587	/// From the [`Idle`] state, the `Continuation` can be scheduled by
588	/// inserting it into the event calendar at a specific model time,
589	/// transitioning it to the [`Next`] state. When the model time reaches this
590	/// point and the `Continuation` is activated, it enters the [`Busy`] state.
591	///
592	/// Upon completion, the `Continuation` transitions to the [`Done`] state,
593	/// indicating that a return value is available for extraction. At any point
594	/// before normal termination, the `Continuation` can be aborted, moving it
595	/// to the [`Gone`] state and dropping its bound future. The [`Gone`] state
596	/// is the final state in a `Continuation`'s lifecycle.
597	///
598	/// [Job]: crate::job::Job
599	/// [Agent]: crate::Agent
600	/// [`Born`]: State::Born
601	/// [`Idle`]: State::Idle
602	/// [`Next`]: State::Next
603	/// [`Busy`]: State::Busy
604	/// [`Done`]: State::Done
605	/// [`Gone`]: State::Gone
606	#[derive(Default)]
607	pub enum State<C: Config> {
608		/// State of a continuation signifying a non-bound future and shared data.
609		#[default]
610		Born -> {Idle, Gone},
611		/// State of a continuation that is waiting for external reactivation.
612		Idle -> {Next, Gone},
613		/// State of the continuation that is currently active. At most one
614		/// continuation may be in this state during a simulation run at any
615		/// time.
616		Busy -> {Idle, Done},
617		/// State of a continuation that is managed by the calendar.
618		Next(<C::Plan as Scheduler>::State) -> {Idle, Busy},
619		/// State of a completed continuation with a result available for extraction.
620		Done(ExitStatus) -> {Gone},
621		/// State of a continuation that cannot be scheduled anymore.
622		Gone(ExitStatus) -> {}
623	}
624
625	/// Meta-state for all non-[`Born`] states.
626	///
627	/// [`Born`]: State::Born
628	pub Init = {Idle, Busy, Next, Done, Gone};
629
630	/// Meta-state for [`Done`] and [`Gone`] states and a subset of the [`Init`]
631	/// meta-state.
632	///
633	/// [`Done`]: State::Done
634	/// [`Gone`]: State::Gone
635	/// [`Init`]: token::Init
636	pub Term: Init = {Done, Gone};
637}
638
639impl<C: ?Sized + Config> fmt::Debug for State<C> {
640	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
641		let mut debug = f.debug_tuple(self.label());
642
643		match self {
644			State::Next(plan) => debug.field(plan),
645			State::Done(exit) => debug.field(exit),
646			State::Gone(exit) => debug.field(exit),
647			_ => &mut debug,
648		}
649		.finish()
650	}
651}