Skip to content

Commit ec8412c

Browse files
authored
Fix leak issues (#385)
1 parent 7d14a90 commit ec8412c

File tree

6 files changed

+352
-4
lines changed

6 files changed

+352
-4
lines changed

temporalio/Cargo.lock

Lines changed: 74 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

temporalio/ext/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,7 @@ tokio-util = "0.7"
2626
tonic = { workspace = true }
2727
tracing = "0.1"
2828
url = "2.5"
29+
dhat = { version = "0.3", optional = true }
30+
31+
[features]
32+
dhat-heap = ["dhat"]

temporalio/ext/src/lib.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
use magnus::{Error, ExceptionClass, RModule, Ruby, prelude::*, value::Lazy};
22

3+
#[cfg(feature = "dhat-heap")]
4+
#[global_allocator]
5+
static ALLOC: dhat::Alloc = dhat::Alloc;
6+
7+
#[cfg(feature = "dhat-heap")]
8+
static DHAT_PROFILER: std::sync::Mutex<Option<dhat::Profiler>> = std::sync::Mutex::new(None);
9+
310
mod client;
411
mod client_rpc_generated;
512
mod envconfig;
@@ -58,12 +65,54 @@ macro_rules! lazy_id {
5865
fn init(ruby: &Ruby) -> Result<(), Error> {
5966
Lazy::force(&ROOT_ERR, ruby);
6067

68+
#[cfg(feature = "dhat-heap")]
69+
{
70+
let profiler = dhat::Profiler::builder()
71+
.file_name("dhat-heap.json")
72+
.build();
73+
*DHAT_PROFILER.lock().unwrap() = Some(profiler);
74+
eprintln!("[dhat] Heap profiling enabled.");
75+
}
76+
6177
client::init(ruby)?;
6278
envconfig::init(ruby)?;
6379
metric::init(ruby)?;
6480
runtime::init(ruby)?;
6581
testing::init(ruby)?;
6682
worker::init(ruby)?;
6783

84+
#[cfg(feature = "dhat-heap")]
85+
{
86+
let bridge_mod = ruby.get_inner(&ROOT_MOD);
87+
bridge_mod
88+
.define_module_function("dhat_heap_stats", magnus::function!(dhat_heap_stats, 0))?;
89+
bridge_mod.define_module_function(
90+
"dhat_dump_and_stop",
91+
magnus::function!(dhat_dump_and_stop, 0),
92+
)?;
93+
}
94+
6895
Ok(())
6996
}
97+
98+
/// Print current dhat heap stats to stderr
99+
#[cfg(feature = "dhat-heap")]
100+
fn dhat_heap_stats() {
101+
let stats = dhat::HeapStats::get();
102+
eprintln!(
103+
"[dhat] curr_bytes={} curr_blocks={} total_bytes={} total_blocks={}",
104+
stats.curr_bytes, stats.curr_blocks, stats.total_bytes, stats.total_blocks
105+
);
106+
}
107+
108+
/// Drop the profiler to force writing the JSON profile
109+
#[cfg(feature = "dhat-heap")]
110+
fn dhat_dump_and_stop() {
111+
let mut guard = DHAT_PROFILER.lock().unwrap();
112+
if let Some(profiler) = guard.take() {
113+
drop(profiler); // This triggers the profile write to dhat-heap.json
114+
eprintln!("[dhat] Profile written to dhat-heap.json");
115+
} else {
116+
eprintln!("[dhat] Profiler already stopped or not initialized");
117+
}
118+
}

temporalio/ext/src/util.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,22 +88,30 @@ where
8888
where
8989
U: FnMut(),
9090
{
91-
let mut func: U = unsafe { *Box::from_raw(data as _) };
92-
91+
// Borrow rather than take ownership — the caller frees after
92+
// rb_thread_call_without_gvl returns. This avoids leaking the
93+
// unblock closure when Ruby never invokes it (the common case).
94+
let func: &mut U = unsafe { &mut *(data as *mut U) };
9395
func();
9496
}
9597

9698
let boxed_func = Box::new(func);
9799
let boxed_unblock = Box::new(unblock);
100+
let unblock_ptr = Box::into_raw(boxed_unblock);
98101

99102
unsafe {
100103
let result = rb_sys::rb_thread_call_without_gvl(
101104
Some(anon_func::<F, R>),
102105
Box::into_raw(boxed_func) as *mut _,
103106
Some(anon_unblock::<U>),
104-
Box::into_raw(boxed_unblock) as *mut _,
107+
unblock_ptr as *mut _,
105108
);
106109

110+
// Free the unblock closure. By the time rb_thread_call_without_gvl
111+
// returns, anon_unblock (if called at all) has already completed,
112+
// so this is safe.
113+
drop(Box::from_raw(unblock_ptr));
114+
107115
*Box::from_raw(result as _)
108116
}
109117
}

0 commit comments

Comments
 (0)