@nomicfoundation/edr 0.2.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/.cargo/config.toml +8 -0
  2. package/.mocharc.json +4 -0
  3. package/Cargo.toml +50 -0
  4. package/LICENSE +1 -0
  5. package/artifacts/bindings-aarch64-apple-darwin/edr.darwin-arm64.node +0 -0
  6. package/artifacts/bindings-aarch64-pc-windows-msvc/edr.win32-arm64-msvc.node +0 -0
  7. package/artifacts/bindings-aarch64-unknown-linux-gnu/edr.linux-arm64-gnu.node +0 -0
  8. package/artifacts/bindings-aarch64-unknown-linux-musl/edr.linux-arm64-musl.node +0 -0
  9. package/artifacts/bindings-i686-pc-windows-msvc/edr.win32-ia32-msvc.node +0 -0
  10. package/artifacts/bindings-x86_64-apple-darwin/edr.darwin-x64.node +0 -0
  11. package/artifacts/bindings-x86_64-pc-windows-msvc/edr.win32-x64-msvc.node +0 -0
  12. package/artifacts/bindings-x86_64-unknown-linux-gnu/edr.linux-x64-gnu.node +0 -0
  13. package/artifacts/bindings-x86_64-unknown-linux-musl/edr.linux-x64-musl.node +0 -0
  14. package/build.rs +3 -0
  15. package/index.d.ts +383 -0
  16. package/index.js +264 -0
  17. package/npm/darwin-arm64/README.md +3 -0
  18. package/npm/darwin-arm64/edr.darwin-arm64.node +0 -0
  19. package/npm/darwin-arm64/package.json +22 -0
  20. package/npm/darwin-x64/README.md +3 -0
  21. package/npm/darwin-x64/edr.darwin-x64.node +0 -0
  22. package/npm/darwin-x64/package.json +22 -0
  23. package/npm/linux-arm64-gnu/README.md +3 -0
  24. package/npm/linux-arm64-gnu/edr.linux-arm64-gnu.node +0 -0
  25. package/npm/linux-arm64-gnu/package.json +25 -0
  26. package/npm/linux-arm64-musl/README.md +3 -0
  27. package/npm/linux-arm64-musl/edr.linux-arm64-musl.node +0 -0
  28. package/npm/linux-arm64-musl/package.json +25 -0
  29. package/npm/linux-x64-gnu/README.md +3 -0
  30. package/npm/linux-x64-gnu/edr.linux-x64-gnu.node +0 -0
  31. package/npm/linux-x64-gnu/package.json +25 -0
  32. package/npm/linux-x64-musl/README.md +3 -0
  33. package/npm/linux-x64-musl/edr.linux-x64-musl.node +0 -0
  34. package/npm/linux-x64-musl/package.json +25 -0
  35. package/npm/win32-arm64-msvc/README.md +3 -0
  36. package/npm/win32-arm64-msvc/edr.win32-arm64-msvc.node +0 -0
  37. package/npm/win32-arm64-msvc/package.json +22 -0
  38. package/npm/win32-ia32-msvc/README.md +3 -0
  39. package/npm/win32-ia32-msvc/edr.win32-ia32-msvc.node +0 -0
  40. package/npm/win32-ia32-msvc/package.json +22 -0
  41. package/npm/win32-x64-msvc/README.md +3 -0
  42. package/npm/win32-x64-msvc/edr.win32-x64-msvc.node +0 -0
  43. package/npm/win32-x64-msvc/package.json +22 -0
  44. package/package.json +61 -0
  45. package/src/account.rs +28 -0
  46. package/src/block.rs +110 -0
  47. package/src/cast.rs +119 -0
  48. package/src/config.rs +70 -0
  49. package/src/context.rs +74 -0
  50. package/src/debug_trace.rs +38 -0
  51. package/src/lib.rs +18 -0
  52. package/src/log.rs +41 -0
  53. package/src/logger.rs +1277 -0
  54. package/src/provider/config.rs +271 -0
  55. package/src/provider.rs +185 -0
  56. package/src/result.rs +254 -0
  57. package/src/subscribe.rs +60 -0
  58. package/src/sync.rs +85 -0
  59. package/src/threadsafe_function.rs +305 -0
  60. package/src/trace.rs +168 -0
  61. package/test/provider.ts +104 -0
  62. package/tsconfig.json +17 -0
package/src/result.rs ADDED
@@ -0,0 +1,254 @@
1
+ use std::mem;
2
+
3
+ use napi::{
4
+ bindgen_prelude::{BigInt, Buffer, Either3},
5
+ Either, Env, JsBuffer, JsBufferValue,
6
+ };
7
+ use napi_derive::napi;
8
+
9
+ use crate::log::ExecutionLog;
10
+
11
+ /// The possible reasons for successful termination of the EVM.
12
+ #[napi]
13
+ pub enum SuccessReason {
14
+ /// The opcode `STOP` was called
15
+ Stop,
16
+ /// The opcode `RETURN` was called
17
+ Return,
18
+ /// The opcode `SELFDESTRUCT` was called
19
+ SelfDestruct,
20
+ }
21
+
22
+ impl From<edr_evm::Eval> for SuccessReason {
23
+ fn from(eval: edr_evm::Eval) -> Self {
24
+ match eval {
25
+ edr_evm::Eval::Stop => Self::Stop,
26
+ edr_evm::Eval::Return => Self::Return,
27
+ edr_evm::Eval::SelfDestruct => Self::SelfDestruct,
28
+ }
29
+ }
30
+ }
31
+
32
+ impl From<SuccessReason> for edr_evm::Eval {
33
+ fn from(value: SuccessReason) -> Self {
34
+ match value {
35
+ SuccessReason::Stop => Self::Stop,
36
+ SuccessReason::Return => Self::Return,
37
+ SuccessReason::SelfDestruct => Self::SelfDestruct,
38
+ }
39
+ }
40
+ }
41
+
42
+ #[napi(object)]
43
+ pub struct CallOutput {
44
+ /// Return value
45
+ pub return_value: JsBuffer,
46
+ }
47
+
48
+ #[napi(object)]
49
+ pub struct CreateOutput {
50
+ /// Return value
51
+ pub return_value: JsBuffer,
52
+ /// Optionally, a 160-bit address
53
+ pub address: Option<Buffer>,
54
+ }
55
+
56
+ /// The result when the EVM terminates successfully.
57
+ #[napi(object)]
58
+ pub struct SuccessResult {
59
+ /// The reason for termination
60
+ pub reason: SuccessReason,
61
+ /// The amount of gas used
62
+ pub gas_used: BigInt,
63
+ /// The amount of gas refunded
64
+ pub gas_refunded: BigInt,
65
+ /// The logs
66
+ pub logs: Vec<ExecutionLog>,
67
+ /// The transaction output
68
+ pub output: Either<CallOutput, CreateOutput>,
69
+ }
70
+
71
+ /// The result when the EVM terminates due to a revert.
72
+ #[napi(object)]
73
+ pub struct RevertResult {
74
+ /// The amount of gas used
75
+ pub gas_used: BigInt,
76
+ /// The transaction output
77
+ pub output: JsBuffer,
78
+ }
79
+
80
+ /// Indicates that the EVM has experienced an exceptional halt. This causes
81
+ /// execution to immediately end with all gas being consumed.
82
+ #[napi]
83
+ pub enum ExceptionalHalt {
84
+ OutOfGas,
85
+ OpcodeNotFound,
86
+ InvalidFEOpcode,
87
+ InvalidJump,
88
+ NotActivated,
89
+ StackUnderflow,
90
+ StackOverflow,
91
+ OutOfOffset,
92
+ CreateCollision,
93
+ PrecompileError,
94
+ NonceOverflow,
95
+ /// Create init code size exceeds limit (runtime).
96
+ CreateContractSizeLimit,
97
+ /// Error on created contract that begins with EF
98
+ CreateContractStartingWithEF,
99
+ /// EIP-3860: Limit and meter initcode. Initcode size limit exceeded.
100
+ CreateInitcodeSizeLimit,
101
+ }
102
+
103
+ impl From<edr_evm::Halt> for ExceptionalHalt {
104
+ fn from(halt: edr_evm::Halt) -> Self {
105
+ match halt {
106
+ edr_evm::Halt::OutOfGas(..) => ExceptionalHalt::OutOfGas,
107
+ edr_evm::Halt::OpcodeNotFound => ExceptionalHalt::OpcodeNotFound,
108
+ edr_evm::Halt::InvalidFEOpcode => ExceptionalHalt::InvalidFEOpcode,
109
+ edr_evm::Halt::InvalidJump => ExceptionalHalt::InvalidJump,
110
+ edr_evm::Halt::NotActivated => ExceptionalHalt::NotActivated,
111
+ edr_evm::Halt::StackUnderflow => ExceptionalHalt::StackUnderflow,
112
+ edr_evm::Halt::StackOverflow => ExceptionalHalt::StackOverflow,
113
+ edr_evm::Halt::OutOfOffset => ExceptionalHalt::OutOfOffset,
114
+ edr_evm::Halt::CreateCollision => ExceptionalHalt::CreateCollision,
115
+ edr_evm::Halt::PrecompileError => ExceptionalHalt::PrecompileError,
116
+ edr_evm::Halt::NonceOverflow => ExceptionalHalt::NonceOverflow,
117
+ edr_evm::Halt::CreateContractSizeLimit => ExceptionalHalt::CreateContractSizeLimit,
118
+ edr_evm::Halt::CreateContractStartingWithEF => {
119
+ ExceptionalHalt::CreateContractStartingWithEF
120
+ }
121
+ edr_evm::Halt::CreateInitcodeSizeLimit => ExceptionalHalt::CreateInitcodeSizeLimit,
122
+ edr_evm::Halt::OverflowPayment
123
+ | edr_evm::Halt::StateChangeDuringStaticCall
124
+ | edr_evm::Halt::CallNotAllowedInsideStatic
125
+ | edr_evm::Halt::OutOfFund
126
+ | edr_evm::Halt::CallTooDeep => {
127
+ unreachable!("Internal halts that can be only found inside Inspector: {halt:?}")
128
+ }
129
+ }
130
+ }
131
+ }
132
+
133
+ impl From<ExceptionalHalt> for edr_evm::Halt {
134
+ fn from(value: ExceptionalHalt) -> Self {
135
+ match value {
136
+ ExceptionalHalt::OutOfGas => Self::OutOfGas(edr_evm::OutOfGasError::BasicOutOfGas),
137
+ ExceptionalHalt::OpcodeNotFound => Self::OpcodeNotFound,
138
+ ExceptionalHalt::InvalidFEOpcode => Self::InvalidFEOpcode,
139
+ ExceptionalHalt::InvalidJump => Self::InvalidJump,
140
+ ExceptionalHalt::NotActivated => Self::NotActivated,
141
+ ExceptionalHalt::StackUnderflow => Self::StackUnderflow,
142
+ ExceptionalHalt::StackOverflow => Self::StackOverflow,
143
+ ExceptionalHalt::OutOfOffset => Self::OutOfOffset,
144
+ ExceptionalHalt::CreateCollision => Self::CreateCollision,
145
+ ExceptionalHalt::PrecompileError => Self::PrecompileError,
146
+ ExceptionalHalt::NonceOverflow => Self::NonceOverflow,
147
+ ExceptionalHalt::CreateContractSizeLimit => Self::CreateContractSizeLimit,
148
+ ExceptionalHalt::CreateContractStartingWithEF => Self::CreateContractStartingWithEF,
149
+ ExceptionalHalt::CreateInitcodeSizeLimit => Self::CreateInitcodeSizeLimit,
150
+ }
151
+ }
152
+ }
153
+
154
+ /// The result when the EVM terminates due to an exceptional halt.
155
+ #[napi(object)]
156
+ pub struct HaltResult {
157
+ /// The exceptional halt that occurred
158
+ pub reason: ExceptionalHalt,
159
+ /// Halting will spend all the gas and will thus be equal to the specified
160
+ /// gas limit
161
+ pub gas_used: BigInt,
162
+ }
163
+
164
+ /// The result of executing a transaction.
165
+ #[napi(object)]
166
+ pub struct ExecutionResult {
167
+ /// The transaction result
168
+ pub result: Either3<SuccessResult, RevertResult, HaltResult>,
169
+ }
170
+
171
+ impl ExecutionResult {
172
+ pub fn new(env: &Env, result: &edr_evm::ExecutionResult) -> napi::Result<Self> {
173
+ let result = match result {
174
+ edr_evm::ExecutionResult::Success {
175
+ reason,
176
+ gas_used,
177
+ gas_refunded,
178
+ logs,
179
+ output,
180
+ } => {
181
+ let logs = logs
182
+ .iter()
183
+ .map(|log| ExecutionLog::new(env, log))
184
+ .collect::<napi::Result<_>>()?;
185
+
186
+ Either3::A(SuccessResult {
187
+ reason: SuccessReason::from(*reason),
188
+ gas_used: BigInt::from(*gas_used),
189
+ gas_refunded: BigInt::from(*gas_refunded),
190
+ logs,
191
+ output: match output {
192
+ edr_evm::Output::Call(return_value) => {
193
+ let return_value = return_value.clone();
194
+ Either::A(CallOutput {
195
+ return_value: unsafe {
196
+ env.create_buffer_with_borrowed_data(
197
+ return_value.as_ptr(),
198
+ return_value.len(),
199
+ return_value,
200
+ |return_value: edr_eth::Bytes, _env| {
201
+ mem::drop(return_value);
202
+ },
203
+ )
204
+ }
205
+ .map(JsBufferValue::into_raw)?,
206
+ })
207
+ }
208
+ edr_evm::Output::Create(return_value, address) => {
209
+ let return_value = return_value.clone();
210
+
211
+ Either::B(CreateOutput {
212
+ return_value: unsafe {
213
+ env.create_buffer_with_borrowed_data(
214
+ return_value.as_ptr(),
215
+ return_value.len(),
216
+ return_value,
217
+ |return_value: edr_eth::Bytes, _env| {
218
+ mem::drop(return_value);
219
+ },
220
+ )
221
+ }
222
+ .map(JsBufferValue::into_raw)?,
223
+ address: address.map(|address| Buffer::from(address.as_slice())),
224
+ })
225
+ }
226
+ },
227
+ })
228
+ }
229
+ edr_evm::ExecutionResult::Revert { gas_used, output } => {
230
+ let output = output.clone();
231
+ Either3::B(RevertResult {
232
+ gas_used: BigInt::from(*gas_used),
233
+ output: unsafe {
234
+ env.create_buffer_with_borrowed_data(
235
+ output.as_ptr(),
236
+ output.len(),
237
+ output,
238
+ |output: edr_eth::Bytes, _env| {
239
+ mem::drop(output);
240
+ },
241
+ )
242
+ }
243
+ .map(JsBufferValue::into_raw)?,
244
+ })
245
+ }
246
+ edr_evm::ExecutionResult::Halt { reason, gas_used } => Either3::C(HaltResult {
247
+ reason: ExceptionalHalt::from(*reason),
248
+ gas_used: BigInt::from(*gas_used),
249
+ }),
250
+ };
251
+
252
+ Ok(Self { result })
253
+ }
254
+ }
@@ -0,0 +1,60 @@
1
+ use edr_eth::{remote::eth, B256};
2
+ use napi::{bindgen_prelude::BigInt, Env, JsFunction, NapiRaw};
3
+ use napi_derive::napi;
4
+
5
+ use crate::threadsafe_function::{
6
+ ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode,
7
+ };
8
+
9
+ #[derive(Clone)]
10
+ pub struct SubscriberCallback {
11
+ inner: ThreadsafeFunction<edr_provider::SubscriptionEvent>,
12
+ }
13
+
14
+ impl SubscriberCallback {
15
+ pub fn new(env: &Env, subscription_event_callback: JsFunction) -> napi::Result<Self> {
16
+ let callback = ThreadsafeFunction::create(
17
+ env.raw(),
18
+ // SAFETY: The callback is guaranteed to be valid for the lifetime of the inspector.
19
+ unsafe { subscription_event_callback.raw() },
20
+ 0,
21
+ |ctx: ThreadSafeCallContext<edr_provider::SubscriptionEvent>| {
22
+ // SubscriptionEvent
23
+ let mut event = ctx.env.create_object()?;
24
+
25
+ ctx.env
26
+ .create_bigint_from_words(false, ctx.value.filter_id.as_limbs().to_vec())
27
+ .and_then(|filter_id| event.set_named_property("filterId", filter_id))?;
28
+
29
+ let result = match ctx.value.result {
30
+ edr_provider::SubscriptionEventData::Logs(logs) => ctx.env.to_js_value(&logs),
31
+ edr_provider::SubscriptionEventData::NewHeads(block) => {
32
+ let block = eth::Block::<B256>::from(block);
33
+ ctx.env.to_js_value(&block)
34
+ }
35
+ edr_provider::SubscriptionEventData::NewPendingTransactions(tx_hash) => {
36
+ ctx.env.to_js_value(&tx_hash)
37
+ }
38
+ }?;
39
+
40
+ event.set_named_property("result", result)?;
41
+
42
+ ctx.callback.call(None, &[event])?;
43
+ Ok(())
44
+ },
45
+ )?;
46
+ Ok(Self { inner: callback })
47
+ }
48
+
49
+ pub fn call(&self, event: edr_provider::SubscriptionEvent) {
50
+ // This is blocking because it's important that the subscription events are
51
+ // in-order
52
+ self.inner.call(event, ThreadsafeFunctionCallMode::Blocking);
53
+ }
54
+ }
55
+
56
+ #[napi(object)]
57
+ pub struct SubscriptionEvent {
58
+ pub filter_id: BigInt,
59
+ pub result: serde_json::Value,
60
+ }
package/src/sync.rs ADDED
@@ -0,0 +1,85 @@
1
+ use std::{fmt::Debug, sync::mpsc::Sender};
2
+
3
+ use napi::{bindgen_prelude::FromNapiValue, Env, JsFunction, JsObject, JsUnknown, NapiRaw, Status};
4
+
5
+ use crate::cast::TryCast;
6
+
7
+ pub fn await_promise<I, O>(
8
+ env: Env,
9
+ result: JsUnknown,
10
+ tx: Sender<napi::Result<O>>,
11
+ ) -> napi::Result<()>
12
+ where
13
+ I: FromNapiValue + TryCast<O, Error = napi::Error>,
14
+ O: 'static,
15
+ {
16
+ // If the result is a promise, wait for it to resolve, and send the result to
17
+ // the channel. Otherwise, send the result immediately.
18
+ if result.is_promise()? {
19
+ let result: JsObject = result.try_into()?;
20
+ let then: JsFunction = result.get_named_property("then")?;
21
+ let tx2 = tx.clone();
22
+ let cb = env.create_function_from_closure("callback", move |ctx| {
23
+ let result = ctx.get::<I>(0)?;
24
+ tx.send(Ok(result.try_cast()?)).unwrap();
25
+ ctx.env.get_undefined()
26
+ })?;
27
+ let eb = env.create_function_from_closure("error_callback", move |ctx| {
28
+ // TODO: need a way to convert a JsUnknown to an Error
29
+ tx2.send(Err(napi::Error::from_reason("Promise rejected")))
30
+ .unwrap();
31
+ ctx.env.get_undefined()
32
+ })?;
33
+ then.call(Some(&result), &[cb, eb])?;
34
+ } else {
35
+ let result = unsafe { I::from_napi_value(env.raw(), result.raw())? };
36
+ tx.send(Ok(result.try_cast()?)).unwrap();
37
+ }
38
+
39
+ Ok(())
40
+ }
41
+
42
+ #[allow(dead_code)]
43
+ pub fn await_void_promise(
44
+ env: Env,
45
+ result: JsUnknown,
46
+ tx: Sender<napi::Result<()>>,
47
+ ) -> napi::Result<()> {
48
+ // If the result is a promise, wait for it to resolve, and send the result to
49
+ // the channel. Otherwise, send the result immediately.
50
+ if result.is_promise()? {
51
+ let result: JsObject = result.try_into()?;
52
+ let then: JsFunction = result.get_named_property("then")?;
53
+ let tx2 = tx.clone();
54
+ let cb = env.create_function_from_closure("callback", move |ctx| {
55
+ tx.send(Ok(())).unwrap();
56
+ ctx.env.get_undefined()
57
+ })?;
58
+ let eb = env.create_function_from_closure("error_callback", move |ctx| {
59
+ // TODO: need a way to convert a JsUnknown to an Error
60
+ tx2.send(Err(napi::Error::from_reason("Promise rejected")))
61
+ .unwrap();
62
+ ctx.env.get_undefined()
63
+ })?;
64
+ then.call(Some(&result), &[cb, eb])?;
65
+ Ok(())
66
+ } else {
67
+ Err(napi::Error::new(
68
+ Status::ObjectExpected,
69
+ "Expected promise".to_owned(),
70
+ ))
71
+ }
72
+ }
73
+
74
+ pub fn handle_error<T: Debug>(
75
+ tx: Sender<napi::Result<T>>,
76
+ res: napi::Result<()>,
77
+ ) -> napi::Result<()> {
78
+ match res {
79
+ Ok(_) => Ok(()),
80
+ Err(e) => {
81
+ tx.send(Err(e)).expect("send error");
82
+ Ok(())
83
+ }
84
+ }
85
+ }
@@ -0,0 +1,305 @@
1
+ // Fork of threadsafe_function from napi-rs that allows calling JS function
2
+ // manually rather than only returning args. This enables us to use the return
3
+ // value of the function.
4
+
5
+ #![allow(clippy::single_component_path_imports)]
6
+
7
+ use std::{
8
+ convert::Into,
9
+ ffi::CString,
10
+ marker::PhantomData,
11
+ os::raw::c_void,
12
+ ptr,
13
+ sync::{
14
+ atomic::{AtomicBool, AtomicUsize, Ordering},
15
+ Arc,
16
+ },
17
+ };
18
+
19
+ use napi::{check_status, sys, Env, JsError, JsFunction, NapiValue, Result, Status};
20
+
21
+ /// `ThreadSafeFunction` context object
22
+ /// the `value` is the value passed to `call` method
23
+ pub struct ThreadSafeCallContext<T: 'static> {
24
+ pub env: Env,
25
+ pub value: T,
26
+ pub callback: JsFunction,
27
+ }
28
+
29
+ #[repr(u8)]
30
+ pub enum ThreadsafeFunctionCallMode {
31
+ NonBlocking,
32
+ Blocking,
33
+ }
34
+
35
+ impl From<ThreadsafeFunctionCallMode> for sys::napi_threadsafe_function_call_mode {
36
+ fn from(value: ThreadsafeFunctionCallMode) -> Self {
37
+ match value {
38
+ ThreadsafeFunctionCallMode::Blocking => sys::ThreadsafeFunctionCallMode::blocking,
39
+ ThreadsafeFunctionCallMode::NonBlocking => sys::ThreadsafeFunctionCallMode::nonblocking,
40
+ }
41
+ }
42
+ }
43
+
44
+ /// Communicate with the addon's main thread by invoking a JavaScript function
45
+ /// from other threads.
46
+ ///
47
+ /// ## Example
48
+ /// An example of using `ThreadsafeFunction`:
49
+ ///
50
+ /// ```rust
51
+ /// #[macro_use]
52
+ /// extern crate napi_derive;
53
+ ///
54
+ /// use std::thread;
55
+ ///
56
+ /// use napi::{
57
+ /// threadsafe_function::{
58
+ /// ThreadSafeCallContext, ThreadsafeFunctionCallMode, ThreadsafeFunctionReleaseMode,
59
+ /// },
60
+ /// CallContext, Error, JsFunction, JsNumber, JsUndefined, Result, Status,
61
+ /// };
62
+ ///
63
+ /// #[js_function(1)]
64
+ /// pub fn test_threadsafe_function(ctx: CallContext) -> Result<JsUndefined> {
65
+ /// let func = ctx.get::<JsFunction>(0)?;
66
+ ///
67
+ /// let tsfn =
68
+ /// ctx
69
+ /// .env
70
+ /// .create_threadsafe_function(&func, 0, |ctx: ThreadSafeCallContext<Vec<u32>>| {
71
+ /// ctx.value
72
+ /// .iter()
73
+ /// .map(|v| ctx.env.create_uint32(*v))
74
+ /// .collect::<Result<Vec<JsNumber>>>()
75
+ /// })?;
76
+ ///
77
+ /// let tsfn_cloned = tsfn.clone();
78
+ ///
79
+ /// thread::spawn(move || {
80
+ /// let output: Vec<u32> = vec![0, 1, 2, 3];
81
+ /// // It's okay to call a threadsafe function multiple times.
82
+ /// tsfn.call(Ok(output.clone()), ThreadsafeFunctionCallMode::Blocking);
83
+ /// });
84
+ ///
85
+ /// thread::spawn(move || {
86
+ /// let output: Vec<u32> = vec![3, 2, 1, 0];
87
+ /// // It's okay to call a threadsafe function multiple times.
88
+ /// tsfn_cloned.call(Ok(output.clone()), ThreadsafeFunctionCallMode::NonBlocking);
89
+ /// });
90
+ ///
91
+ /// ctx.env.get_undefined()
92
+ /// }
93
+ /// ```
94
+ #[derive(Debug)]
95
+ pub struct ThreadsafeFunction<T: 'static> {
96
+ raw_tsfn: sys::napi_threadsafe_function,
97
+ aborted: Arc<AtomicBool>,
98
+ ref_count: Arc<AtomicUsize>,
99
+ _phantom: PhantomData<T>,
100
+ }
101
+
102
+ impl<T: 'static> Clone for ThreadsafeFunction<T> {
103
+ fn clone(&self) -> Self {
104
+ if !self.aborted.load(Ordering::Acquire) {
105
+ let acquire_status = unsafe { sys::napi_acquire_threadsafe_function(self.raw_tsfn) };
106
+ debug_assert!(
107
+ acquire_status == sys::Status::napi_ok,
108
+ "Acquire threadsafe function failed in clone"
109
+ );
110
+ }
111
+
112
+ Self {
113
+ raw_tsfn: self.raw_tsfn,
114
+ aborted: Arc::clone(&self.aborted),
115
+ ref_count: Arc::clone(&self.ref_count),
116
+ _phantom: PhantomData,
117
+ }
118
+ }
119
+ }
120
+
121
+ unsafe impl<T> Send for ThreadsafeFunction<T> {}
122
+ unsafe impl<T> Sync for ThreadsafeFunction<T> {}
123
+
124
+ impl<T: 'static> ThreadsafeFunction<T> {
125
+ /// See [napi_create_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_create_threadsafe_function)
126
+ /// for more information.
127
+ pub(crate) fn create<R: 'static + Send + FnMut(ThreadSafeCallContext<T>) -> Result<()>>(
128
+ env: sys::napi_env,
129
+ func: sys::napi_value,
130
+ max_queue_size: usize,
131
+ callback: R,
132
+ ) -> Result<Self> {
133
+ let mut async_resource_name = ptr::null_mut();
134
+ let s = "napi_rs_threadsafe_function";
135
+ let len = s.len();
136
+ let s = CString::new(s)?;
137
+ check_status!(unsafe {
138
+ sys::napi_create_string_utf8(env, s.as_ptr(), len, &mut async_resource_name)
139
+ })?;
140
+
141
+ let initial_thread_count = 1usize;
142
+ let mut raw_tsfn = ptr::null_mut();
143
+ let ptr = Box::into_raw(Box::new(callback)).cast::<c_void>();
144
+ check_status!(unsafe {
145
+ sys::napi_create_threadsafe_function(
146
+ env,
147
+ func,
148
+ ptr::null_mut(),
149
+ async_resource_name,
150
+ max_queue_size,
151
+ initial_thread_count,
152
+ ptr,
153
+ Some(thread_finalize_cb::<T, R>),
154
+ ptr,
155
+ Some(call_js_cb::<T, R>),
156
+ &mut raw_tsfn,
157
+ )
158
+ })?;
159
+
160
+ let aborted = Arc::new(AtomicBool::new(false));
161
+ let aborted_ptr = Arc::into_raw(aborted.clone()) as *mut c_void;
162
+ check_status!(unsafe {
163
+ sys::napi_add_env_cleanup_hook(env, Some(cleanup_cb), aborted_ptr)
164
+ })?;
165
+
166
+ Ok(ThreadsafeFunction {
167
+ raw_tsfn,
168
+ aborted,
169
+ ref_count: Arc::new(AtomicUsize::new(initial_thread_count)),
170
+ _phantom: PhantomData,
171
+ })
172
+ }
173
+ }
174
+
175
+ impl<T: 'static> ThreadsafeFunction<T> {
176
+ /// See [napi_call_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_call_threadsafe_function)
177
+ /// for more information.
178
+ pub fn call(&self, value: T, mode: ThreadsafeFunctionCallMode) -> Status {
179
+ if self.aborted.load(Ordering::Acquire) {
180
+ return Status::Closing;
181
+ }
182
+ unsafe {
183
+ sys::napi_call_threadsafe_function(
184
+ self.raw_tsfn,
185
+ Box::into_raw(Box::new(value)).cast(),
186
+ mode.into(),
187
+ )
188
+ }
189
+ .into()
190
+ }
191
+ }
192
+
193
+ impl<T: 'static> Drop for ThreadsafeFunction<T> {
194
+ fn drop(&mut self) {
195
+ if !self.aborted.load(Ordering::Acquire) && self.ref_count.load(Ordering::Acquire) > 0usize
196
+ {
197
+ let release_status = unsafe {
198
+ sys::napi_release_threadsafe_function(
199
+ self.raw_tsfn,
200
+ sys::ThreadsafeFunctionReleaseMode::release,
201
+ )
202
+ };
203
+ assert!(
204
+ release_status == sys::Status::napi_ok,
205
+ "Threadsafe Function release failed"
206
+ );
207
+ }
208
+ }
209
+ }
210
+
211
+ unsafe extern "C" fn cleanup_cb(cleanup_data: *mut c_void) {
212
+ let aborted = Arc::<AtomicBool>::from_raw(cleanup_data.cast());
213
+ aborted.store(true, Ordering::SeqCst);
214
+ }
215
+
216
+ unsafe extern "C" fn thread_finalize_cb<T: 'static, R>(
217
+ _raw_env: sys::napi_env,
218
+ finalize_data: *mut c_void,
219
+ _finalize_hint: *mut c_void,
220
+ ) where
221
+ R: 'static + Send + FnMut(ThreadSafeCallContext<T>) -> Result<()>,
222
+ {
223
+ // cleanup
224
+ drop(Box::<R>::from_raw(finalize_data.cast()));
225
+ }
226
+
227
+ unsafe extern "C" fn call_js_cb<T: 'static, R>(
228
+ raw_env: sys::napi_env,
229
+ js_callback: sys::napi_value,
230
+ context: *mut c_void,
231
+ data: *mut c_void,
232
+ ) where
233
+ R: 'static + Send + FnMut(ThreadSafeCallContext<T>) -> Result<()>,
234
+ {
235
+ // env and/or callback can be null when shutting down
236
+ if raw_env.is_null() || js_callback.is_null() {
237
+ return;
238
+ }
239
+
240
+ let ctx: &mut R = &mut *context.cast::<R>();
241
+ let val: Result<T> = Ok(*Box::<T>::from_raw(data.cast()));
242
+
243
+ let mut recv = ptr::null_mut();
244
+ sys::napi_get_undefined(raw_env, &mut recv);
245
+
246
+ let ret = val.and_then(|v| {
247
+ (ctx)(ThreadSafeCallContext {
248
+ env: Env::from_raw(raw_env),
249
+ value: v,
250
+ callback: JsFunction::from_raw(raw_env, js_callback).unwrap(), // TODO: unwrap
251
+ })
252
+ });
253
+
254
+ let status = match ret {
255
+ Ok(()) => sys::Status::napi_ok,
256
+ Err(e) => sys::napi_fatal_exception(raw_env, JsError::from(e).into_value(raw_env)),
257
+ };
258
+ if status == sys::Status::napi_ok {
259
+ return;
260
+ }
261
+ if status == sys::Status::napi_pending_exception {
262
+ let mut error_result = ptr::null_mut();
263
+ assert_eq!(
264
+ sys::napi_get_and_clear_last_exception(raw_env, &mut error_result),
265
+ sys::Status::napi_ok
266
+ );
267
+
268
+ // When shutting down, napi_fatal_exception sometimes returns another exception
269
+ let stat = sys::napi_fatal_exception(raw_env, error_result);
270
+ assert!(stat == sys::Status::napi_ok || stat == sys::Status::napi_pending_exception);
271
+ } else {
272
+ let error_code: Status = status.into();
273
+ let error_code_string = format!("{error_code:?}");
274
+ let mut error_code_value = ptr::null_mut();
275
+ assert_eq!(
276
+ sys::napi_create_string_utf8(
277
+ raw_env,
278
+ error_code_string.as_ptr().cast(),
279
+ error_code_string.len(),
280
+ &mut error_code_value,
281
+ ),
282
+ sys::Status::napi_ok,
283
+ );
284
+ let error_msg = "Call JavaScript callback failed in thread safe function";
285
+ let mut error_msg_value = ptr::null_mut();
286
+ assert_eq!(
287
+ sys::napi_create_string_utf8(
288
+ raw_env,
289
+ error_msg.as_ptr().cast(),
290
+ error_msg.len(),
291
+ &mut error_msg_value,
292
+ ),
293
+ sys::Status::napi_ok,
294
+ );
295
+ let mut error_value = ptr::null_mut();
296
+ assert_eq!(
297
+ sys::napi_create_error(raw_env, error_code_value, error_msg_value, &mut error_value),
298
+ sys::Status::napi_ok,
299
+ );
300
+ assert_eq!(
301
+ sys::napi_fatal_exception(raw_env, error_value),
302
+ sys::Status::napi_ok
303
+ );
304
+ }
305
+ }