Skip to content
File

Blob: src/rust/jsg-test/tests/traceable_gc.rs

rust162 lines
1// Copyright (c) 2026 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4 
5//! GC tracing tests for manual `jsg::Traced` implementations on helper types.
6//!
7//! `#[jsg_resource]` now traces every field via `Traced::trace`, so nested
8//! non-resource helper structs/enums just need to implement `Traced`.
9 
10use std::sync::atomic::AtomicUsize;
11use std::sync::atomic::Ordering;
12 
13use jsg::ToJS;
14use jsg_macros::jsg_resource;
15 
16// =============================================================================
17// Shared leaf resource
18// =============================================================================
19 
20static LEAF_DROPS: AtomicUsize = AtomicUsize::new(0);
21 
22#[jsg_resource]
23struct Leaf {
24 pub value: u32,
25}
26 
27impl Drop for Leaf {
28 fn drop(&mut self) {
29 LEAF_DROPS.fetch_add(1, Ordering::SeqCst);
30 }
31}
32 
33#[jsg_resource]
34impl Leaf {}
35 
36// =============================================================================
37// Enum helper implementing Traced
38// =============================================================================
39 
40static ENUM_PARENT_DROPS: AtomicUsize = AtomicUsize::new(0);
41 
42enum StreamState {
43 Closed,
44 Errored { reason: jsg::Rc<Leaf> },
45 Readable(jsg::Rc<Leaf>),
46}
47 
48impl jsg::Traced for StreamState {
49 fn trace(&self, visitor: &mut jsg::GcVisitor) {
50 match self {
51 Self::Closed => {}
52 Self::Errored { reason } => jsg::Traced::trace(reason, visitor),
53 Self::Readable(inner) => jsg::Traced::trace(inner, visitor),
54 }
55 }
56}
57 
58#[jsg_resource]
59struct StreamController {
60 pub state: StreamState,
61}
62 
63impl Drop for StreamController {
64 fn drop(&mut self) {
65 ENUM_PARENT_DROPS.fetch_add(1, Ordering::SeqCst);
66 }
67}
68 
69#[jsg_resource]
70impl StreamController {}
71 
72#[test]
73fn enum_unit_variant_does_not_crash_during_gc() {
74 ENUM_PARENT_DROPS.store(0, Ordering::SeqCst);
75 
76 let harness = crate::Harness::new();
77 harness.run_in_context(|lock, ctx| {
78 let parent = jsg::Rc::new(StreamController {
79 state: StreamState::Closed,
80 });
81 let wrapped = parent.clone().to_js(lock);
82 ctx.set_global("parent", wrapped);
83 std::mem::drop(parent);
84 
85 crate::Harness::request_gc(lock);
86 assert_eq!(ENUM_PARENT_DROPS.load(Ordering::SeqCst), 0);
87 Ok(())
88 });
89 
90 harness.run_in_context(|lock, _ctx| {
91 crate::Harness::request_gc(lock);
92 assert_eq!(ENUM_PARENT_DROPS.load(Ordering::SeqCst), 1);
93 Ok(())
94 });
95}
96 
97#[test]
98fn enum_variant_child_kept_alive_through_gc() {
99 LEAF_DROPS.store(0, Ordering::SeqCst);
100 ENUM_PARENT_DROPS.store(0, Ordering::SeqCst);
101 
102 let harness = crate::Harness::new();
103 harness.run_in_context(|lock, ctx| {
104 let child = jsg::Rc::new(Leaf { value: 10 });
105 
106 let parent = jsg::Rc::new(StreamController {
107 state: StreamState::Readable(child.clone()),
108 });
109 let wrapped = parent.clone().to_js(lock);
110 ctx.set_global("parent", wrapped);
111 
112 std::mem::drop(child);
113 std::mem::drop(parent);
114 
115 crate::Harness::request_gc(lock);
116 assert_eq!(ENUM_PARENT_DROPS.load(Ordering::SeqCst), 0);
117 assert_eq!(LEAF_DROPS.load(Ordering::SeqCst), 0);
118 Ok(())
119 });
120 
121 harness.run_in_context(|lock, _ctx| {
122 crate::Harness::request_gc(lock);
123 assert_eq!(ENUM_PARENT_DROPS.load(Ordering::SeqCst), 1);
124 assert_eq!(LEAF_DROPS.load(Ordering::SeqCst), 1);
125 Ok(())
126 });
127}
128 
129#[test]
130fn enum_named_variant_child_kept_alive_through_gc() {
131 LEAF_DROPS.store(0, Ordering::SeqCst);
132 ENUM_PARENT_DROPS.store(0, Ordering::SeqCst);
133 
134 let harness = crate::Harness::new();
135 harness.run_in_context(|lock, ctx| {
136 let child = jsg::Rc::new(Leaf { value: 11 });
137 
138 let parent = jsg::Rc::new(StreamController {
139 state: StreamState::Errored {
140 reason: child.clone(),
141 },
142 });
143 let wrapped = parent.clone().to_js(lock);
144 ctx.set_global("parent", wrapped);
145 
146 std::mem::drop(child);
147 std::mem::drop(parent);
148 
149 crate::Harness::request_gc(lock);
150 assert_eq!(ENUM_PARENT_DROPS.load(Ordering::SeqCst), 0);
151 assert_eq!(LEAF_DROPS.load(Ordering::SeqCst), 0);
152 Ok(())
153 });
154 
155 harness.run_in_context(|lock, _ctx| {
156 crate::Harness::request_gc(lock);
157 assert_eq!(ENUM_PARENT_DROPS.load(Ordering::SeqCst), 1);
158 assert_eq!(LEAF_DROPS.load(Ordering::SeqCst), 1);
159 Ok(())
160 });
161}