jrsonnet_gcmodule/lib.rs
1#![deny(missing_docs)]
2#![cfg_attr(feature = "nightly", feature(coerce_unsized), feature(unsize))]
3#![cfg_attr(all(feature = "debug", feature = "nightly"), feature(specialization))]
4
5//! Reference cycle garbage collection inspired by
6//! [cpython](https://github.com/python/cpython/).
7//!
8//! The type [`Cc<T>`](type.Cc.html) provides shared ownership of a value of type `T`,
9//! similar to `std::rc::Rc<T>`. Unlike `Rc<T>`, [`collect_thread_cycles`](fn.collect_thread_cycles.html)
10//! can be used to drop unreachable values that form circular references.
11//!
12//! # Examples
13//!
14//! ## Cloning references
15//!
16//! Similar to `Rc<T>`, use `clone()` to get cloned references.
17//!
18//! ```
19//! use jrsonnet_gcmodule::Cc;
20//! let foo = Cc::new(vec![1, 2, 3]);
21//! let foo_cloned = foo.clone();
22//!
23//! // foo and foo_cloned both point to the same `vec![1, 2, 3]`.
24//! assert!(std::ptr::eq(&foo[0], &foo_cloned[0]));
25//! ```
26//!
27//! ## Collecting cycles
28//!
29//! Use [`collect_thread_cycles()`](fn.collect_thread_cycles.html) to collect
30//! thread-local garbage.
31//!
32//! Use [`count_thread_tracked()`](fn.count_thread_tracked.html) to count how
33//! many objects are tracked by the collector.
34//!
35//! ```
36//! use jrsonnet_gcmodule::{Cc, Trace, TraceBox};
37//! use std::cell::RefCell;
38//! {
39//! type List = Cc<RefCell<Vec<TraceBox<dyn Trace>>>>;
40//! let a: List = Default::default();
41//! let b: List = Default::default();
42//! a.borrow_mut().push(TraceBox(Box::new(b.clone())));
43//! b.borrow_mut().push(TraceBox(Box::new(a.clone())));
44//! }
45//!
46//! // a and b form circular references. The objects they point to are not
47//! // dropped automatically, despite both variables run out of scope.
48//!
49//! assert_eq!(jrsonnet_gcmodule::count_thread_tracked(), 2); // 2 values are tracked.
50//! assert_eq!(jrsonnet_gcmodule::collect_thread_cycles(), 2); // This will drop a and b.
51//! assert_eq!(jrsonnet_gcmodule::count_thread_tracked(), 0); // no values are tracked.
52//! ```
53//!
54//! ## Defining new types
55//!
56//! [`Cc<T>`](type.Cc.html) requires [`Trace`](trait.Trace.html) implemented
57//! for `T` so the collector knows how values are referred. That can usually
58//! be done by `#[derive(Trace)]`.
59//!
60//! ### Acyclic types
61//!
62//! If a type is acyclic (cannot form reference circles about [`Cc`](type.Cc.html)),
63//! [`Trace::is_type_tracked()`](trait.Trace.html#method.is_type_tracked) will return `false`.
64//!
65//! ```
66//! use jrsonnet_gcmodule::{Cc, Trace};
67//!
68//! #[derive(Trace)]
69//! struct Foo(String);
70//!
71//! #[derive(Trace)]
72//! struct Bar;
73//!
74//! assert!(!Foo::is_type_tracked()); // Acyclic
75//! assert!(!Bar::is_type_tracked()); // Acyclic
76//!
77//! let foo = Cc::new(Foo("abc".to_string()));
78//! let bar = Cc::new(Bar);
79//! let foo_cloned = foo.clone(); // Share the same `"abc"` with `foo`.
80//! assert_eq!(jrsonnet_gcmodule::count_thread_tracked(), 0); // The collector tracks nothing.
81//! drop(foo); // The ref count of `"abc"` drops from 2 to 1.
82//! drop(foo_cloned); // `"abc"` will be dropped here..
83//! # drop(bar);
84//! ```
85//!
86//! ### Container types
87//!
88//! Whether a container type is acyclic or not depends on its fields. Usually,
89//! types without referring to trait objects or itself are considered acyclic.
90//!
91//! ```
92//! use jrsonnet_gcmodule::{Cc, Trace, TraceBox};
93//!
94//! #[derive(Trace)]
95//! struct Foo<T1: Trace, T2: Trace>(T1, T2, u8);
96//!
97//! // `a` is not tracked - types are acyclic.
98//! let a = Cc::new(Foo(Foo(Cc::new(1), 2, 3), Cc::new("abc"), 10));
99//! assert_eq!(jrsonnet_gcmodule::count_thread_tracked(), 0);
100//!
101//! // `b` is tracked because it contains a trait object.
102//! let b = Cc::new(Foo(TraceBox(Box::new(1) as Box<dyn Trace>), 2, 3));
103//! assert_eq!(jrsonnet_gcmodule::count_thread_tracked(), 1);
104//! ```
105//!
106//! The `#[skip_trace]` attribute can be used to skip tracking specified fields
107//! in a structure.
108//!
109//! ```
110//! use jrsonnet_gcmodule::{Cc, Trace};
111//!
112//! struct AlienStruct; // Does not implement Trace
113//!
114//! #[derive(Trace)]
115//! struct Foo {
116//! field: String,
117//!
118//! #[trace(skip)]
119//! alien: AlienStruct, // Field skipped in Trace implementation.
120//! }
121//! ```
122//!
123//! ### Weak references
124//!
125//! Similar to `std::rc::Rc`, use [`Cc::downgrade`](struct.RawCc.html#method.downgrade)
126//! to create weak references. Use [`Weak::upgrade`](struct.RawWeak.html#method.upgrade)
127//! to test if the value is still alive and to access the value. For example:
128//!
129//! ```
130//! use jrsonnet_gcmodule::{Cc, Weak};
131//!
132//! let value = Cc::new("foo");
133//! let weak: Weak<_> = value.downgrade();
134//! assert_eq!(*weak.upgrade().unwrap(), "foo");
135//! drop(value);
136//! assert!(weak.upgrade().is_none()); // Cannot upgrade after dropping value
137//! ```
138//!
139//! # Technical Details
140//!
141//! ## Memory Layouts
142//!
143//! [`Cc<T>`](type.Cc.html) uses different memory layouts depending on `T`.
144//!
145//! ### Untracked types
146//!
147//! If [`<T as Trace>::is_type_tracked()`](trait.Trace.html#method.is_type_tracked)
148//! returns `false`, the layout is similar to `Rc<T>`:
149//!
150//! ```plain,ignore
151//! Shared T Pointer
152//! +-------------------+ .-- Cc<T>
153//! | ref_count: usize | <--<
154//! | weak_count: usize | '-- Cc<T>::clone()
155//! |-------------------|
156//! | T (shared data) | <--- Cc<T>::deref()
157//! +-------------------+
158//! ```
159//!
160//! ### Tracked types
161//!
162//! If [`<T as Trace>::is_type_tracked()`](trait.Trace.html#method.is_type_tracked)
163//! returns `true`, the layout has an extra `GcHeader` that makes the value visible
164//! in a thread-local linked list:
165//!
166//! ```plain,ignore
167//! Shared T with GcHeader
168//! +-------------------+
169//! | gc_prev: pointer | ---> GcHeader in a linked list.
170//! | gc_next: pointer |
171//! | vptr<T>: pointer | ---> Pointer to the `&T as &dyn Trace` virtual table.
172//! |-------------------|
173//! | ref_count: usize | <--- Cc<T>
174//! | weak_count: usize |
175//! | ----------------- |
176//! | T (shared data) | <--- Cc<T>::deref()
177//! +-------------------+
178//! ```
179//!
180//! ## Incorrect `Trace` implementation
181//!
182//! While most public APIs provided by this library looks safe, incorrectly
183//! implementing the [`Trace`](trait.Trace.html) trait has consequences.
184//!
185//! This library should cause no undefined behaviors (UB) even with incorrect
186//! [`Trace`](trait.Trace.html) implementation on _debug_ build.
187//!
188//! Below are some consequences of a wrong [`Trace`](trait.Trace.html)
189//! implementation.
190//!
191//! ### Memory leak
192//!
193//! If [`Trace::trace`](trait.Trace.html#method.trace) does not visit all
194//! referred values, the collector might fail to detect cycles, and take
195//! no actions on cycles. That causes memory leak.
196//!
197//! Note: there are other ways to cause memory leak unrelated to an incorrect
198//! [`Trace`](trait.Trace.html) implementation. For example, forgetting
199//! to call collect functions can cause memory leak. When using the advanced
200//! [`ObjectSpace`](struct.ObjectSpace.html) APIs, objects in one space
201//! referring to objects in a different space can cause memory leak.
202//!
203//! ### Panic
204//!
205//! If [`Trace::trace`](trait.Trace.html#method.trace) visits more values
206//! than it should (for example, visit indirectly referred values, or visit
207//! a directly referred value multiple times), the collector can detect
208//! such issues and panic the thread with the message:
209//!
210//! ```plain,ignore
211//! bug: unexpected ref-count after dropping cycles
212//! This usually indicates a buggy Trace or Drop implementation.
213//! ```
214//!
215//! ### Undefined behavior (UB)
216//!
217//! After the above panic (`bug: unexpected ref-count after dropping cycles`),
218//! dereferencing a garbage-collected [`Cc<T>`](type.Cc.html) will trigger
219//! `panic!` or UB depending on whether it's a debug build or not.
220//!
221//! On debug build, sanity checks are added at `Cc::<T>::deref()`.
222//! It will panic if `T` was garbage-collected:
223//!
224//! ```plain,ignore
225//! bug: accessing a dropped CcBox detected
226//! ```
227//!
228//! In other words, no UB on debug build.
229//!
230//! On release build the dereference would access dropped values, which is an
231//! undefined behavior. Again, the UB can only happen if the [`Trace::trace`](trait.Trace.html#method.trace)
232//! is implemented wrong, and panic will happen before the UB.
233
234extern crate self as jrsonnet_gcmodule;
235
236mod cc;
237mod cc_impls;
238mod collect;
239#[cfg(test)]
240mod debug;
241mod ref_count;
242#[cfg(feature = "sync")]
243mod sync;
244#[cfg(test)]
245mod tests;
246#[cfg(any(test, feature = "testutil"))]
247pub mod testutil;
248mod trace;
249mod trace_impls;
250
251pub use trace_impls::TraceBox;
252
253pub use cc::{Cc, RawCc, RawWeak, Weak};
254pub use collect::{
255 ObjectSpace, collect_thread_cycles, count_thread_tracked, with_thread_object_space,
256};
257pub use trace::{Acyclic, Trace, Tracer};
258
259#[cfg(feature = "sync")]
260pub use sync::{ThreadedCc, ThreadedCcRef, collect::ThreadedObjectSpace};
261
262/// Derive [`Trace`](trait.Trace.html) implementation for a structure.
263///
264/// # Examples
265///
266/// ```
267/// use jrsonnet_gcmodule::{Cc, Trace};
268///
269/// #[derive(Trace)]
270/// struct S1(u32, String);
271///
272/// #[derive(Trace)]
273/// struct S2<T1: Trace, T2: Trace>(T1, T2, u8);
274///
275/// #[derive(Trace)]
276/// struct S3<T: Trace> {
277/// a: S1,
278/// b: Option<S2<T, u8>>,
279///
280/// #[trace(skip)]
281/// c: AlienStruct, // c is not tracked by the collector.
282/// }
283///
284/// struct AlienStruct;
285/// ```
286#[cfg(feature = "derive")]
287pub use jrsonnet_gcmodule_derive::{Acyclic, Trace};
288
289#[cfg(not(test))]
290mod debug {
291 #[cfg(any(feature = "debug", feature = "testutil", test))]
292 thread_local!(pub(crate) static NEXT_DEBUG_NAME: std::cell::Cell<usize> = std::cell::Cell::default());
293 #[cfg(any(feature = "debug", test))]
294 thread_local!(pub(crate) static GC_DROPPING: std::cell::Cell<bool> = const { std::cell::Cell::new(false) });
295 pub(crate) fn log<S1: ToString, S2: ToString>(func: impl Fn() -> (S1, S2)) {
296 if cfg!(feature = "debug") {
297 let (name, message) = func();
298 eprintln!("[gc] {} {}", name.to_string(), message.to_string());
299 }
300 }
301}
302
303/// Whether the `debug` feature is enabled.
304pub const DEBUG_ENABLED: bool = cfg!(feature = "debug");
305
306/// Jrsonnet golang bindings require that it is possible to move jsonnet
307/// VM between OS threads, and this is not possible due to usage of
308/// `thread_local`. Instead, there is two methods added, one should be
309/// called at the end of current thread work, and one that should be
310/// used when using other thread.
311// It won't be able to preserve debug state.
312#[cfg(not(any(test, feature = "debug")))]
313pub mod interop {
314 use std::mem;
315
316 use crate::collect::{OwnedGcHeader, THREAD_OBJECT_SPACE, new_gc_list};
317
318 /// Type-erased gc object list
319 pub enum GcState {}
320
321 type UnerasedState = OwnedGcHeader;
322
323 /// Dump current interned string pool, to be restored by
324 /// `reenter_thread`
325 ///
326 /// # Safety
327 ///
328 /// Current thread gc becomes broken after this call, you should not use gc after this
329 /// call, and before `reenter_thread` call.
330 #[must_use]
331 pub unsafe fn exit_thread() -> *mut GcState {
332 let object_list: UnerasedState = THREAD_OBJECT_SPACE
333 .with(|space| mem::replace(&mut *space.list.borrow_mut(), new_gc_list()));
334 Box::into_raw(Box::new(object_list)).cast()
335 }
336
337 /// Reenter thread, using state dumped by `exit_thread`.
338 ///
339 /// # Safety
340 ///
341 /// `state` should be acquired from `exit_thread`, it is not allowed
342 /// to reuse state to reenter multiple threads.
343 pub unsafe fn reenter_thread(state: *mut GcState) {
344 let ptr: *mut UnerasedState = state.cast();
345 // SAFETY: ptr is an unique state per method safety requirements.
346 let ptr: Box<UnerasedState> = unsafe { Box::from_raw(ptr) };
347 let ptr: UnerasedState = *ptr;
348 THREAD_OBJECT_SPACE.with(|space| {
349 let _ = mem::replace(&mut *space.list.borrow_mut(), ptr);
350 });
351 }
352}
353
354#[cfg(test)]
355mod dyn_cc {
356 use crate::Trace;
357 use std::fmt::Debug;
358
359 use crate::cc_dyn;
360
361 #[derive(Debug, Trace)]
362 struct Test {
363 a: String,
364 }
365
366 trait DebugAndTrace: Debug + Trace {}
367 impl<T> DebugAndTrace for T where T: Debug + Trace {}
368 cc_dyn!(
369 #[derive(Debug)]
370 CcDebugAndTrace,
371 DebugAndTrace
372 );
373
374 trait BorrowTy<O> {
375 fn borrow_ty(&self) -> &O;
376 }
377 cc_dyn!(CcBorrowTy<O>, BorrowTy<O>);
378 cc_dyn!(
379 CcBorrowTyAlsoDebug<O: Debug>,
380 BorrowTy<O>
381 );
382
383 #[derive(Trace)]
384 struct Borrowable<T: Trace> {
385 v: T,
386 }
387 impl<T: Trace> BorrowTy<T> for Borrowable<T> {
388 fn borrow_ty(&self) -> &T {
389 &self.v
390 }
391 }
392
393 #[test]
394 fn test_dyn() {
395 let test = Test {
396 a: "hello".to_owned(),
397 };
398
399 let dynccgeneric = CcBorrowTy::new(Borrowable { v: 1u32 });
400 let v = dynccgeneric.0.borrow_ty();
401 assert_eq!(*v, 1);
402
403 let dynccgeneric = CcBorrowTyAlsoDebug::new(Borrowable { v: 1u32 });
404 let v = dynccgeneric.0.borrow_ty();
405 assert_eq!(*v, 1);
406
407 let dyncc = CcDebugAndTrace::new(test);
408 assert_eq!(
409 format!("{dyncc:?}"),
410 "CcDebugAndTrace(Cc(Test { a: \"hello\" }))"
411 );
412 let dyncc_is_trace = dyncc;
413 assert_eq!(
414 format!("{dyncc_is_trace:?}"),
415 "CcDebugAndTrace(Cc(Test { a: \"hello\" }))"
416 );
417 let dyncc_is_trace_as_dyn = CcDebugAndTrace::new(dyncc_is_trace);
418 assert_eq!(
419 format!("{dyncc_is_trace_as_dyn:?}"),
420 "CcDebugAndTrace(Cc(CcDebugAndTrace(Cc(Test { a: \"hello\" }))))"
421 );
422 }
423}