rune/runtime/budget.rs
1//! Budgeting module for Runestick.
2//!
3//! This module contains methods which allows for limiting the execution of the
4//! virtual machine to abide by the specified budget.
5//!
6//! By default the budget is disabled, but can be enabled by wrapping your
7//! function call in [with].
8
9#[cfg_attr(feature = "std", path = "budget/std.rs")]
10mod no_std;
11
12use core::future::Future;
13use core::pin::Pin;
14use core::task::{Context, Poll};
15
16use pin_project::pin_project;
17use rune_alloc::callable::Callable;
18
19/// Wrapper for something being [budgeted].
20///
21/// See [with].
22///
23/// [budgeted]: self
24#[pin_project]
25pub struct Budget<T> {
26 /// Instruction budget.
27 budget: usize,
28 /// The thing being budgeted.
29 #[pin]
30 value: T,
31}
32
33/// Wrap the given value with a budget.
34///
35/// Budgeting is only performed on a per-instruction basis in the virtual
36/// machine. What exactly constitutes an instruction might be a bit vague. But
37/// important to note is that without explicit co-operation from native
38/// functions the budget cannot be enforced. So care must be taken with the
39/// native functions that you provide to Rune to ensure that the limits you
40/// impose cannot be circumvented.
41///
42/// The following things can be wrapped:
43/// * A [`FnOnce`] closure, like `with(|| println!("Hello World")).call()`.
44/// * A [`Future`], like `with(async { /* async work */ }).await`;
45///
46/// It's also possible to wrap other wrappers which implement [`Callable`].
47///
48/// # Examples
49///
50/// ```no_run
51/// use rune::runtime::budget;
52/// use rune::Vm;
53///
54/// let mut vm: Vm = todo!();
55/// // The virtual machine and any tasks associated with it is only allowed to execute 100 budget.
56/// budget::with(100, || vm.call(&["main"], ())).call()?;
57/// # Ok::<(), rune::support::Error>(())
58/// ```
59///
60/// This budget can be conveniently combined with the memory [`limit`] module
61/// due to both wrappers implementing [`Callable`].
62///
63/// [`limit`]: crate::alloc::limit
64///
65/// ```
66/// use rune::runtime::budget;
67/// use rune::alloc::{limit, Vec};
68///
69/// #[derive(Debug, PartialEq)]
70/// struct Marker;
71///
72/// // Limit the given closure to run one instruction and allocate 1024 bytes.
73/// let f = budget::with(1, limit::with(1024, || {
74/// let mut budget = budget::acquire();
75/// assert!(budget.take());
76/// assert!(!budget.take());
77/// assert!(Vec::<u8>::try_with_capacity(1).is_ok());
78/// assert!(Vec::<u8>::try_with_capacity(1024).is_ok());
79/// assert!(Vec::<u8>::try_with_capacity(1025).is_err());
80/// Marker
81/// }));
82///
83/// assert_eq!(f.call(), Marker);
84/// ```
85pub fn with<T>(budget: usize, value: T) -> Budget<T> {
86 tracing::trace!(?budget);
87 Budget { budget, value }
88}
89
90/// Replace the current budget returning a guard that will release it.
91///
92/// Use [`BudgetGuard::take`] to take permites from the returned budget.
93#[inline(never)]
94pub fn replace(budget: usize) -> BudgetGuard {
95 BudgetGuard(self::no_std::rune_budget_replace(budget))
96}
97
98/// Acquire the current budget.
99///
100/// Use [`BudgetGuard::take`] to take permites from the returned budget.
101#[inline(never)]
102pub fn acquire() -> BudgetGuard {
103 BudgetGuard(self::no_std::rune_budget_replace(usize::MAX))
104}
105
106/// A locally acquired budget.
107///
108/// This guard is acquired by calling [`take`] and can be used to take permits.
109///
110/// [`take`]: BudgetGuard::take
111#[repr(transparent)]
112pub struct BudgetGuard(usize);
113
114impl BudgetGuard {
115 /// Take a ticker from the budget.
116 #[inline]
117 pub fn take(&mut self) -> bool {
118 if self.0 == usize::MAX {
119 return true;
120 }
121
122 if self.0 == 0 {
123 return false;
124 }
125
126 self.0 -= 1;
127 true
128 }
129}
130
131impl Drop for BudgetGuard {
132 #[inline]
133 fn drop(&mut self) {
134 let _ = self::no_std::rune_budget_replace(self.0);
135 }
136}
137
138impl<T> Budget<T>
139where
140 T: Callable,
141{
142 /// Call the budgeted function.
143 #[inline]
144 pub fn call(self) -> T::Output {
145 Callable::call(self)
146 }
147}
148
149impl<T> Callable for Budget<T>
150where
151 T: Callable,
152{
153 type Output = T::Output;
154
155 #[inline]
156 fn call(self) -> Self::Output {
157 let _guard = BudgetGuard(self::no_std::rune_budget_replace(self.budget));
158 self.value.call()
159 }
160}
161
162impl<T> Future for Budget<T>
163where
164 T: Future,
165{
166 type Output = T::Output;
167
168 #[inline]
169 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
170 let this = self.project();
171
172 let _guard = BudgetGuard(self::no_std::rune_budget_replace(*this.budget));
173 let poll = this.value.poll(cx);
174 *this.budget = self::no_std::rune_budget_get();
175 poll
176 }
177}