Skip to content

Commit 7d0f981

Browse files
Wodannfvictorio
andauthored
fix: use weak references for stored threadsafe functions (#5025)
Co-authored-by: Franco Victorio <[email protected]>
1 parent 00cca62 commit 7d0f981

18 files changed

+203
-655
lines changed

.changeset/ten-spoons-study.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@nomicfoundation/edr": patch
3+
---
4+
5+
Fix node.js runtime freezing on shutdown

Cargo.lock

+6-6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/edr_napi/Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ k256 = { version = "0.13.1", default-features = false, features = ["arithmetic",
1414
log = { version = "0.4.20", default-features = false }
1515
# when napi is pinned, be sure to pin napi-derive to the same version
1616
# The `async` feature ensures that a tokio runtime is available
17-
napi = { version = "2.12.4", default-features = false, features = ["async", "error_anyhow", "napi8", "serde-json"] }
18-
napi-derive = "2.12.3"
17+
napi = { version = "2.16.0", default-features = false, features = ["async", "error_anyhow", "napi8", "serde-json"] }
18+
napi-derive = "2.16.0"
1919
edr_defaults = { version = "0.2.0-dev", path = "../edr_defaults" }
2020
edr_evm = { version = "0.2.0-dev", path = "../edr_evm", features = ["tracing"]}
2121
edr_eth = { version = "0.2.0-dev", path = "../edr_eth" }

crates/edr_napi/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@
5252
"universal": "napi universal",
5353
"version": "napi version",
5454
"pretest": "pnpm build",
55-
"test": "pnpm tsc && mocha --recursive \"test/**/*.ts\" --exit",
56-
"testNoBuild": "pnpm tsc && mocha --recursive \"test/**/*.ts\" --exit",
55+
"test": "pnpm tsc && mocha --recursive \"test/**/*.ts\"",
56+
"testNoBuild": "pnpm tsc && mocha --recursive \"test/**/*.ts\"",
5757
"clean": "rm -rf @nomicfoundation/edr.node"
5858
}
5959
}

crates/edr_napi/src/call_override.rs

+27-23
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
use std::sync::mpsc::{channel, Sender};
1+
use std::sync::mpsc::channel;
22

33
use edr_eth::{Address, Bytes};
4-
use napi::{bindgen_prelude::Buffer, Env, JsFunction, NapiRaw, Status};
4+
use napi::{
5+
bindgen_prelude::Buffer,
6+
threadsafe_function::{
7+
ErrorStrategy, ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode,
8+
},
9+
Env, JsFunction, Status,
10+
};
511
use napi_derive::napi;
612

7-
use crate::{
8-
cast::TryCast,
9-
sync::{await_promise, handle_error},
10-
threadsafe_function::{ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode},
11-
};
13+
use crate::cast::TryCast;
1214

1315
/// The result of executing a call override.
1416
#[napi(object)]
@@ -34,20 +36,16 @@ impl TryCast<Option<edr_provider::CallOverrideResult>> for Option<CallOverrideRe
3436
struct CallOverrideCall {
3537
contract_address: Address,
3638
data: Bytes,
37-
sender: Sender<napi::Result<Option<edr_provider::CallOverrideResult>>>,
3839
}
3940

4041
#[derive(Clone)]
4142
pub struct CallOverrideCallback {
42-
call_override_callback_fn: ThreadsafeFunction<CallOverrideCall>,
43+
call_override_callback_fn: ThreadsafeFunction<CallOverrideCall, ErrorStrategy::Fatal>,
4344
}
4445

4546
impl CallOverrideCallback {
4647
pub fn new(env: &Env, call_override_callback: JsFunction) -> napi::Result<Self> {
47-
let call_override_callback_fn = ThreadsafeFunction::create(
48-
env.raw(),
49-
// SAFETY: The callback is guaranteed to be valid for the lifetime of the inspector.
50-
unsafe { call_override_callback.raw() },
48+
let mut call_override_callback_fn = call_override_callback.create_threadsafe_function(
5149
0,
5250
|ctx: ThreadSafeCallContext<CallOverrideCall>| {
5351
let address = ctx
@@ -60,17 +58,14 @@ impl CallOverrideCallback {
6058
.create_buffer_with_data(ctx.value.data.to_vec())?
6159
.into_raw();
6260

63-
let sender = ctx.value.sender.clone();
64-
let promise = ctx.callback.call(None, &[address, data])?;
65-
let result = await_promise::<
66-
Option<CallOverrideResult>,
67-
Option<edr_provider::CallOverrideResult>,
68-
>(ctx.env, promise, ctx.value.sender);
69-
70-
handle_error(sender, result)
61+
Ok(vec![address, data])
7162
},
7263
)?;
7364

65+
// Maintain a weak reference to the function to avoid the event loop from
66+
// exiting.
67+
call_override_callback_fn.unref(env)?;
68+
7469
Ok(Self {
7570
call_override_callback_fn,
7671
})
@@ -83,13 +78,22 @@ impl CallOverrideCallback {
8378
) -> Option<edr_provider::CallOverrideResult> {
8479
let (sender, receiver) = channel();
8580

86-
let status = self.call_override_callback_fn.call(
81+
let status = self.call_override_callback_fn.call_with_return_value(
8782
CallOverrideCall {
8883
contract_address,
8984
data,
90-
sender,
9185
},
9286
ThreadsafeFunctionCallMode::Blocking,
87+
move |result: Option<CallOverrideResult>| {
88+
let result = result.try_cast();
89+
90+
sender.send(result).map_err(|_error| {
91+
napi::Error::new(
92+
Status::GenericFailure,
93+
"Failed to send result from call_override_callback",
94+
)
95+
})
96+
},
9397
);
9498

9599
assert_eq!(status, Status::Ok, "Call override callback failed");

crates/edr_napi/src/lib.rs

-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,5 @@ mod result;
1616
#[cfg(feature = "scenarios")]
1717
mod scenarios;
1818
mod subscribe;
19-
mod sync;
20-
mod threadsafe_function;
2119
mod trace;
2220
mod withdrawal;

crates/edr_napi/src/log.rs

+3-14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
use std::mem;
2-
31
use napi::{bindgen_prelude::Buffer, Env, JsBuffer, JsBufferValue};
42
use napi_derive::napi;
53

@@ -19,18 +17,9 @@ impl ExecutionLog {
1917
.map(|topic| Buffer::from(topic.as_slice()))
2018
.collect();
2119

22-
let data = log.data.data.clone();
23-
let data = unsafe {
24-
env.create_buffer_with_borrowed_data(
25-
data.as_ptr(),
26-
data.len(),
27-
data,
28-
|data: edr_eth::Bytes, _env| {
29-
mem::drop(data);
30-
},
31-
)
32-
}
33-
.map(JsBufferValue::into_raw)?;
20+
let data = env
21+
.create_buffer_with_data(log.data.data.to_vec())
22+
.map(JsBufferValue::into_raw)?;
3423

3524
Ok(Self {
3625
address: Buffer::from(log.address.as_slice()),

0 commit comments

Comments
 (0)