Skip to content

Commit 260f152

Browse files
committed
ffi: add getCurrentEventLoop
Signed-off-by: Paolo Insogna <paolo@cowtech.it> Assisted-By: OpenAI:GPT-5.5 <openai/gpt-5.5>
1 parent 7af2038 commit 260f152

6 files changed

Lines changed: 71 additions & 0 deletions

File tree

doc/api/ffi.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,25 @@ This is unsafe and dangerous. The returned pointer can become invalid if the
715715
underlying memory is detached, resized, transferred, or otherwise invalidated.
716716
Using stale pointers can cause memory corruption or process crashes.
717717

718+
## `ffi.getCurrentEventLoop()`
719+
720+
<!-- YAML
721+
added: REPLACEME
722+
-->
723+
724+
* Returns: {bigint}
725+
726+
Returns the address of the current thread's `uv_loop_t` as a `bigint`.
727+
728+
The returned address is for the current Node.js environment. In the main thread,
729+
this is the main thread event loop. In a worker thread, this is that worker's
730+
event loop.
731+
732+
This is unsafe and dangerous. The returned pointer is only valid for the lifetime
733+
of the current environment. Using it after the environment exits, or from native
734+
code that assumes a different thread or lifetime, can crash the process or
735+
corrupt memory.
736+
718737
## Safety notes
719738

720739
The `node:ffi` module does not track pointer validity, memory ownership, or

lib/ffi.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const {
4242
getUint64,
4343
getFloat32,
4444
getFloat64,
45+
getCurrentEventLoop,
4546
exportBytes,
4647
getRawPointer,
4748
kFastArguments,
@@ -320,6 +321,7 @@ module.exports = {
320321
getUint64,
321322
getFloat32,
322323
getFloat64,
324+
getCurrentEventLoop,
323325
getRawPointer,
324326
setInt8,
325327
setUint8,

src/node_ffi.cc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1215,6 +1215,17 @@ Local<FunctionTemplate> DynamicLibrary::GetConstructorTemplate(
12151215
return tmpl;
12161216
}
12171217

1218+
void GetCurrentEventLoop(const FunctionCallbackInfo<Value>& args) {
1219+
Environment* env = Environment::GetCurrent(args);
1220+
Isolate* isolate = env->isolate();
1221+
1222+
THROW_IF_INSUFFICIENT_PERMISSIONS(env, permission::PermissionScope::kFFI, "");
1223+
1224+
args.GetReturnValue().Set(BigInt::NewFromUnsigned(
1225+
isolate,
1226+
static_cast<uint64_t>(reinterpret_cast<uintptr_t>(env->event_loop()))));
1227+
}
1228+
12181229
// Module initialization.
12191230
static void Initialize(Local<Object> target,
12201231
Local<Value> unused,
@@ -1230,6 +1241,7 @@ static void Initialize(Local<Object> target,
12301241
SetMethod(context, target, "toArrayBuffer", ToArrayBuffer);
12311242
SetMethod(context, target, "exportBytes", ExportBytes);
12321243
SetMethod(context, target, "getRawPointer", GetRawPointer);
1244+
SetMethod(context, target, "getCurrentEventLoop", GetCurrentEventLoop);
12331245

12341246
SetMethod(context, target, "getInt8", GetInt8);
12351247
SetMethod(context, target, "getUint8", GetUint8);

src/node_ffi.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ void ToBuffer(const v8::FunctionCallbackInfo<v8::Value>& args);
178178
void ToArrayBuffer(const v8::FunctionCallbackInfo<v8::Value>& args);
179179
void ExportBytes(const v8::FunctionCallbackInfo<v8::Value>& args);
180180
void GetRawPointer(const v8::FunctionCallbackInfo<v8::Value>& args);
181+
void GetCurrentEventLoop(const v8::FunctionCallbackInfo<v8::Value>& args);
181182

182183
} // namespace node::ffi
183184

test/ffi/test-ffi-module.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const { spawnSyncAndAssert } = require('../common/child_process');
55
const assert = require('node:assert');
66
const { spawnSync } = require('node:child_process');
77
const { test } = require('node:test');
8+
const { Worker } = require('node:worker_threads');
89

910
common.skipIfFFIMissing();
1011

@@ -87,6 +88,7 @@ test('ffi exports expected API surface', () => {
8788
'exportArrayBufferView',
8889
'exportBuffer',
8990
'exportString',
91+
'getCurrentEventLoop',
9092
'getFloat32',
9193
'getFloat64',
9294
'getInt16',
@@ -135,6 +137,7 @@ test('ffi exports expected API surface', () => {
135137
assert.strictEqual(typeof ffi.getUint64, 'function');
136138
assert.strictEqual(typeof ffi.getFloat32, 'function');
137139
assert.strictEqual(typeof ffi.getFloat64, 'function');
140+
assert.strictEqual(typeof ffi.getCurrentEventLoop, 'function');
138141
assert.strictEqual(typeof ffi.setInt8, 'function');
139142
assert.strictEqual(typeof ffi.setUint8, 'function');
140143
assert.strictEqual(typeof ffi.setInt16, 'function');
@@ -180,3 +183,33 @@ test('ffi.types exports canonical type constants', () => {
180183
assert.deepStrictEqual(ffi.types, expected);
181184
assert.strictEqual(Object.isFrozen(ffi.types), true);
182185
});
186+
187+
test('ffi.getCurrentEventLoop returns the current thread event loop address', async () => {
188+
const ffi = require('node:ffi');
189+
const mainLoop = ffi.getCurrentEventLoop();
190+
191+
assert.strictEqual(typeof mainLoop, 'bigint');
192+
assert.ok(mainLoop > 0n);
193+
assert.strictEqual(ffi.getCurrentEventLoop(), mainLoop);
194+
195+
const workerLoop = await new Promise((resolve, reject) => {
196+
const worker = new Worker(`
197+
const { parentPort } = require('node:worker_threads');
198+
const ffi = require('node:ffi');
199+
const loop = ffi.getCurrentEventLoop();
200+
201+
parentPort.postMessage([loop, ffi.getCurrentEventLoop()]);
202+
`, { eval: true });
203+
204+
worker.on('message', resolve);
205+
worker.on('error', reject);
206+
worker.on('exit', (code) => {
207+
if (code !== 0) reject(new Error(`Worker stopped with exit code ${code}`));
208+
});
209+
});
210+
211+
assert.strictEqual(typeof workerLoop[0], 'bigint');
212+
assert.ok(workerLoop[0] > 0n);
213+
assert.strictEqual(workerLoop[0], workerLoop[1]);
214+
assert.notStrictEqual(workerLoop[0], mainLoop);
215+
});

test/ffi/test-ffi-permissions.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ test('permission model blocks ffi memory and helper APIs', () => {
7070
ffi.getRawPointer(Buffer.alloc(0));
7171
}, denied);
7272

73+
assert.throws(() => {
74+
ffi.getCurrentEventLoop();
75+
}, denied);
76+
7377
assert.throws(() => {
7478
ffi.dlclose({ close() {} });
7579
}, denied);

0 commit comments

Comments
 (0)