ext_php_rs/zend/
bailout_guard.rs1use std::cell::RefCell;
51use std::ops::{Deref, DerefMut};
52
53type CleanupEntry = Option<Box<dyn FnOnce()>>;
56
57thread_local! {
58 static CLEANUP_STACK: RefCell<Vec<CleanupEntry>> = const { RefCell::new(Vec::new()) };
59}
60
61pub struct BailoutGuard<T> {
73 value: *mut T,
74 index: usize,
75}
76
77impl<T: 'static> BailoutGuard<T> {
78 pub fn new(value: T) -> Self {
83 let ptr = Box::into_raw(Box::new(value));
84 let index = CLEANUP_STACK.with(|stack| {
85 let mut stack = stack.borrow_mut();
86 stack.push(Some(Box::new(move || {
87 unsafe { drop(Box::from_raw(ptr)) }
91 })));
92 stack.len() - 1
93 });
94
95 Self { value: ptr, index }
96 }
97
98 #[inline]
100 #[must_use]
101 pub fn get(&self) -> &T {
102 unsafe { &*self.value }
104 }
105
106 #[inline]
108 pub fn get_mut(&mut self) -> &mut T {
109 unsafe { &mut *self.value }
111 }
112
113 #[must_use]
117 pub fn into_inner(self) -> T {
118 self.release();
119 let value = unsafe { *Box::from_raw(self.value) };
121 std::mem::forget(self);
122 value
123 }
124}
125
126impl<T> BailoutGuard<T> {
127 fn release(&self) {
128 CLEANUP_STACK.with(|stack| {
129 let mut stack = stack.borrow_mut();
130 if self.index + 1 == stack.len() {
131 stack.pop();
132 while stack.last().is_some_and(Option::is_none) {
133 stack.pop();
134 }
135 } else if let Some(entry) = stack.get_mut(self.index) {
136 *entry = None;
137 }
138 });
139 }
140}
141
142impl<T> Deref for BailoutGuard<T> {
143 type Target = T;
144
145 #[inline]
146 fn deref(&self) -> &T {
147 unsafe { &*self.value }
149 }
150}
151
152impl<T> DerefMut for BailoutGuard<T> {
153 #[inline]
154 fn deref_mut(&mut self) -> &mut T {
155 unsafe { &mut *self.value }
157 }
158}
159
160impl<T> Drop for BailoutGuard<T> {
161 fn drop(&mut self) {
162 self.release();
163 unsafe { drop(Box::from_raw(self.value)) }
165 }
166}
167
168pub(crate) fn cleanup_depth() -> usize {
170 CLEANUP_STACK.with(|stack| stack.borrow().len())
171}
172
173pub(crate) fn run_cleanups_above(depth: usize) {
178 let entries: Vec<CleanupEntry> = CLEANUP_STACK.with(|stack| {
179 let mut stack = stack.borrow_mut();
180 if depth >= stack.len() {
181 return Vec::new();
182 }
183 stack.drain(depth..).collect()
184 });
185 for cleanup in entries.into_iter().rev().flatten() {
186 cleanup();
187 }
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193 use std::sync::Arc;
194 use std::sync::atomic::{AtomicUsize, Ordering};
195
196 struct DropCounter(Arc<AtomicUsize>);
197 impl Drop for DropCounter {
198 fn drop(&mut self) {
199 self.0.fetch_add(1, Ordering::SeqCst);
200 }
201 }
202
203 fn counter() -> (Arc<AtomicUsize>, impl Fn() -> DropCounter) {
204 let count = Arc::new(AtomicUsize::new(0));
205 let make = {
206 let count = Arc::clone(&count);
207 move || DropCounter(Arc::clone(&count))
208 };
209 (count, make)
210 }
211
212 fn reset() {
213 CLEANUP_STACK.with(|stack| stack.borrow_mut().clear());
214 }
215
216 #[test]
217 fn normal_drop_pops_entry() {
218 reset();
219 let (count, make) = counter();
220 {
221 let _guard = BailoutGuard::new(make());
222 assert_eq!(cleanup_depth(), 1);
223 }
224 assert_eq!(count.load(Ordering::SeqCst), 1);
225 assert_eq!(cleanup_depth(), 0);
226 }
227
228 #[test]
229 fn out_of_order_drop_drains_once_newer_guard_goes() {
230 reset();
231 let (count, make) = counter();
232 let older = BailoutGuard::new(make());
233 let newer = BailoutGuard::new(make());
234 drop(older);
235 assert_eq!(cleanup_depth(), 2);
236 drop(newer);
237 assert_eq!(cleanup_depth(), 0);
238 assert_eq!(count.load(Ordering::SeqCst), 2);
239 }
240
241 #[test]
242 fn cleanups_above_depth_spare_outer_guard() {
243 reset();
244 let (count, make) = counter();
245 let outer = BailoutGuard::new(make());
246 let depth = cleanup_depth();
247 let inner = BailoutGuard::new(make());
248 std::mem::forget(inner);
249
250 run_cleanups_above(depth);
251
252 assert_eq!(count.load(Ordering::SeqCst), 1);
253 assert_eq!(cleanup_depth(), 1);
254 assert_eq!(outer.get().0.load(Ordering::SeqCst), 1);
255 drop(outer);
256 assert_eq!(count.load(Ordering::SeqCst), 2);
257 assert_eq!(cleanup_depth(), 0);
258 }
259
260 struct Record(Arc<std::sync::Mutex<Vec<u8>>>, u8);
261 impl Drop for Record {
262 fn drop(&mut self) {
263 self.0.lock().expect("poisoned").push(self.1);
264 }
265 }
266
267 #[test]
268 fn cleanups_run_newest_first() {
269 reset();
270 let order = Arc::new(std::sync::Mutex::new(Vec::new()));
271 std::mem::forget(BailoutGuard::new(Record(Arc::clone(&order), 1)));
272 std::mem::forget(BailoutGuard::new(Record(Arc::clone(&order), 2)));
273
274 run_cleanups_above(0);
275
276 assert_eq!(*order.lock().expect("poisoned"), vec![2, 1]);
277 }
278
279 #[test]
280 fn into_inner_releases_entry() {
281 reset();
282 let (count, make) = counter();
283 let value = BailoutGuard::new(make()).into_inner();
284 assert_eq!(cleanup_depth(), 0);
285 assert_eq!(count.load(Ordering::SeqCst), 0);
286 drop(value);
287 assert_eq!(count.load(Ordering::SeqCst), 1);
288 }
289
290 #[test]
291 fn deref_and_deref_mut() {
292 let mut guard = BailoutGuard::new(String::from("hello"));
293 guard.push_str(" world");
294 assert_eq!(&*guard, "hello world");
295 assert_eq!(guard.len(), 11);
296 }
297}