Skip to content

Commit a870d24

Browse files
committed
process: add Notification API
1 parent 34cb10b commit a870d24

11 files changed

Lines changed: 383 additions & 0 deletions

File tree

doc/api/errors.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2045,6 +2045,16 @@ valid.
20452045
The imported module string is an invalid URL, package name, or package subpath
20462046
specifier.
20472047

2048+
<a id="ERR_INVALID_NOTIFICATION"></a>
2049+
2050+
### `ERR_INVALID_NOTIFICATION`
2051+
2052+
<!-- YAML
2053+
added: REPLACEME
2054+
-->
2055+
2056+
The notification ID is not valid.
2057+
20482058
<a id="ERR_INVALID_OBJECT_DEFINE_PROPERTY"></a>
20492059

20502060
### `ERR_INVALID_OBJECT_DEFINE_PROPERTY`

doc/api/process.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2328,6 +2328,19 @@ The references returned by `process.getBuiltinModule(id)` always point to
23282328
the built-in module corresponding to `id` even if users modify
23292329
[`require.cache`][] so that `require(id)` returns something else.
23302330
2331+
## `process.getNotifications()`
2332+
2333+
<!-- YAML
2334+
added: REPLACEME
2335+
-->
2336+
2337+
> Stability: 1 - Experimental
2338+
2339+
* Returns: {Array<bigint>} A list of notifications registered in the current thread.
2340+
2341+
Return the list of all notifications registered in the current thread via
2342+
[`process.registerNotification(callback)`](#processregisternotificationcallback).
2343+
23312344
## `process.getegid()`
23322345
23332346
<!-- YAML
@@ -3100,6 +3113,21 @@ the [`'warning'` event][process_warning] and the
31003113
[`emitWarning()` method][process_emit_warning] for more information about this
31013114
flag's behavior.
31023115
3116+
## `process.notify(id)`
3117+
3118+
<!-- YAML
3119+
added: REPLACEME
3120+
-->
3121+
3122+
> Stability: 1 - Experimental
3123+
3124+
* `id` {bigint}: The notification ID
3125+
3126+
Triggers the notification previously registered via [`process.registerNotification(callback)`](#processregisternotificationcallback).
3127+
3128+
It will throw an error if the notification is not found. It is safe to invoke a notification
3129+
registered in the same thread.
3130+
31033131
## `process.permission`
31043132
31053133
<!-- YAML
@@ -3253,6 +3281,24 @@ This pattern, however, is being deprecated in favor of the "Refable protocol"
32533281
in order to better support Web Platform API types whose APIs cannot be modified
32543282
to add `ref()` and `unref()` methods but still need to support that behavior.
32553283
3284+
## `process.registerNotification(callback)`
3285+
3286+
<!-- YAML
3287+
added: REPLACEME
3288+
-->
3289+
3290+
> Stability: 1 - Experimental
3291+
3292+
* `callback` {Function} A function to execute when the notification is received.
3293+
* Returns: {bigint} The notification ID
3294+
3295+
Register a callback associated to a notification.
3296+
3297+
A notification is a number which can be safely transferred and used in other threads to trigger
3298+
the associated callback via [`process.notify(id`)](#processnotifyid).
3299+
3300+
The notification can be unregistered using [`process.unregisterNotification(id)`](#processunregisternotificationid).
3301+
32563302
## `process.release`
32573303
32583304
<!-- YAML
@@ -4340,6 +4386,21 @@ This pattern, however, is being deprecated in favor of the "Refable protocol"
43404386
in order to better support Web Platform API types whose APIs cannot be modified
43414387
to add `ref()` and `unref()` methods but still need to support that behavior.
43424388
4389+
## `process.unregisterNotification(id)`
4390+
4391+
<!-- YAML
4392+
added: REPLACEME
4393+
-->
4394+
4395+
> Stability: 1 - Experimental
4396+
4397+
* `id` {number}
4398+
4399+
Unregister a notification previously registered with [`process.registerNotification(callback)`](#processregisternotificationcallback).
4400+
4401+
It will throw an error if the notification is not found. The notification must be unregistered from the same thread
4402+
that registered it.
4403+
43434404
## `process.uptime()`
43444405
43454406
<!-- YAML

lib/internal/bootstrap/node.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,10 @@ const rawMethods = internalBinding('process_methods');
180180
process.execve = wrapped.execve;
181181
process.ref = perThreadSetup.ref;
182182
process.unref = perThreadSetup.unref;
183+
process.getNotifications = wrapped.getNotifications;
184+
process.registerNotification = wrapped.registerNotification;
185+
process.unregisterNotification = wrapped.unregisterNotification;
186+
process.notify = wrapped.notify;
183187

184188
let finalizationMod;
185189
ObjectDefineProperty(process, 'finalization', {

lib/internal/process/per_thread.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@ const {
1111
ArrayPrototypeMap,
1212
ArrayPrototypePush,
1313
ArrayPrototypeSplice,
14+
BigInt,
1415
BigUint64Array,
1516
Float64Array,
1617
FunctionPrototypeCall,
18+
NumberIsInteger,
1719
NumberMAX_SAFE_INTEGER,
1820
ObjectDefineProperty,
1921
ObjectEntries,
@@ -49,6 +51,7 @@ const { emitExperimentalWarning } = require('internal/util');
4951
const format = require('internal/util/inspect').format;
5052
const {
5153
validateArray,
54+
validateFunction,
5255
validateNumber,
5356
validateObject,
5457
validateString,
@@ -113,6 +116,10 @@ function wrapProcessMethods(binding) {
113116
resourceUsage: _resourceUsage,
114117
loadEnvFile: _loadEnvFile,
115118
execve: _execve,
119+
getNotifications,
120+
registerNotification: _registerNotification,
121+
sendNotification,
122+
unregisterNotification: _unregisterNotification,
116123
} = binding;
117124

118125
function _rawDebug(...args) {
@@ -365,6 +372,39 @@ function wrapProcessMethods(binding) {
365372
}
366373
}
367374

375+
function notify(id) {
376+
if (typeof id !== 'number' && typeof id !== 'bigint') {
377+
throw new ERR_INVALID_ARG_TYPE('id', ['number', 'bigint'], id);
378+
}
379+
380+
if (typeof id === 'number' && !NumberIsInteger(id)) {
381+
throw new ERR_OUT_OF_RANGE('id', 'an integer', id);
382+
} else if (id < 0) {
383+
throw new ERR_OUT_OF_RANGE('id', `>= 1`, id);
384+
}
385+
386+
sendNotification(BigInt(id));
387+
}
388+
389+
function registerNotification(listener) {
390+
validateFunction(listener, 'listener');
391+
return _registerNotification(listener);
392+
}
393+
394+
function unregisterNotification(id) {
395+
if (typeof id !== 'number' && typeof id !== 'bigint') {
396+
throw new ERR_INVALID_ARG_TYPE('id', ['number', 'bigint'], id);
397+
}
398+
399+
if (typeof id === 'number' && !NumberIsInteger(id)) {
400+
throw new ERR_OUT_OF_RANGE('id', 'an integer', id);
401+
} else if (id < 0) {
402+
throw new ERR_OUT_OF_RANGE('id', `>= 1`, id);
403+
}
404+
405+
_unregisterNotification(BigInt(id));
406+
}
407+
368408
return {
369409
_rawDebug,
370410
cpuUsage,
@@ -375,6 +415,10 @@ function wrapProcessMethods(binding) {
375415
exit,
376416
execve,
377417
loadEnvFile,
418+
getNotifications,
419+
notify,
420+
registerNotification,
421+
unregisterNotification,
378422
};
379423
}
380424

src/env.cc

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1103,9 +1103,19 @@ void Environment::InitializeLibuv() {
11031103
Context::Scope context_scope(env->context());
11041104
env->RunAndClearNativeImmediates();
11051105
}));
1106+
1107+
CHECK_EQ(
1108+
0,
1109+
uv_async_init(event_loop(), &notifications_async_, [](uv_async_t* async) {
1110+
Environment* env =
1111+
ContainerOf(&Environment::notifications_async_, async);
1112+
env->DispatchNotifications();
1113+
}));
1114+
11061115
uv_unref(reinterpret_cast<uv_handle_t*>(&idle_prepare_handle_));
11071116
uv_unref(reinterpret_cast<uv_handle_t*>(&idle_check_handle_));
11081117
uv_unref(reinterpret_cast<uv_handle_t*>(&task_queues_async_));
1118+
uv_unref(reinterpret_cast<uv_handle_t*>(&notifications_async_));
11091119

11101120
{
11111121
Mutex::ScopedLock lock(native_immediates_threadsafe_mutex_);
@@ -1215,6 +1225,7 @@ void Environment::ClosePerEnvHandles() {
12151225
close_and_finish(reinterpret_cast<uv_handle_t*>(&idle_prepare_handle_));
12161226
close_and_finish(reinterpret_cast<uv_handle_t*>(&idle_check_handle_));
12171227
close_and_finish(reinterpret_cast<uv_handle_t*>(&task_queues_async_));
1228+
close_and_finish(reinterpret_cast<uv_handle_t*>(&notifications_async_));
12181229
}
12191230

12201231
void Environment::CleanupHandles() {
@@ -2260,4 +2271,97 @@ v8::CpuProfile* Environment::StopCpuProfile(v8::ProfilerId profile_id) {
22602271
return profile;
22612272
}
22622273

2274+
Mutex Environment::notifications_mutex_;
2275+
uint64_t Environment::next_notification_id_(0);
2276+
std::unordered_map<uint64_t, Environment*> Environment::notifications_;
2277+
2278+
void Environment::RegisterNotification(
2279+
const v8::FunctionCallbackInfo<v8::Value>& args) {
2280+
CHECK(args[0]->IsFunction());
2281+
2282+
Mutex::ScopedLock lock(notifications_mutex_);
2283+
Environment* env = Environment::GetCurrent(args);
2284+
Isolate* isolate = env->isolate();
2285+
2286+
uint64_t id = ++Environment::next_notification_id_;
2287+
notifications_.emplace(id, env);
2288+
2289+
v8::Global<v8::Function> global;
2290+
global.Reset(isolate, args[0].As<v8::Function>());
2291+
env->notifications_callbacks_.emplace(id, std::move(global));
2292+
2293+
args.GetReturnValue().Set(v8::BigInt::New(isolate, id));
2294+
}
2295+
2296+
void Environment::UnregisterNotification(
2297+
const v8::FunctionCallbackInfo<v8::Value>& args) {
2298+
CHECK(args[0]->IsBigInt());
2299+
2300+
bool lossless = false;
2301+
uint64_t id = args[0].As<v8::BigInt>()->Uint64Value(&lossless);
2302+
2303+
if (!lossless) {
2304+
std::cout << "LOSSLESS TRUE\n";
2305+
return THROW_ERR_INVALID_NOTIFICATION(
2306+
args.GetIsolate(), "Invalid notification %un", id);
2307+
}
2308+
2309+
Mutex::ScopedLock lock(notifications_mutex_);
2310+
Environment* env = Environment::GetCurrent(args);
2311+
2312+
if (env->notifications_callbacks_.erase(id) == 0) {
2313+
return THROW_ERR_INVALID_NOTIFICATION(
2314+
args.GetIsolate(), "Invalid notification %llun", id);
2315+
}
2316+
2317+
env->notifications_queue_.erase(id);
2318+
notifications_.erase(id);
2319+
}
2320+
2321+
void Environment::GetNotifications(
2322+
const v8::FunctionCallbackInfo<v8::Value>& args) {
2323+
Mutex::ScopedLock lock(notifications_mutex_);
2324+
Environment* env = Environment::GetCurrent(args);
2325+
Isolate* isolate = env->isolate();
2326+
2327+
v8::LocalVector<Value> ids(isolate);
2328+
for (auto& pairs : env->notifications_callbacks_) {
2329+
ids.push_back(v8::BigInt::New(isolate, pairs.first));
2330+
}
2331+
2332+
args.GetReturnValue().Set(Array::New(isolate, ids.data(), ids.size()));
2333+
}
2334+
2335+
void Environment::SendNotification(
2336+
const v8::FunctionCallbackInfo<v8::Value>& args) {
2337+
CHECK(args[0]->IsBigInt());
2338+
2339+
uint64_t id = args[0].As<v8::BigInt>()->Uint64Value();
2340+
Environment* env = notifications_[id];
2341+
2342+
if (env == nullptr) {
2343+
return THROW_ERR_INVALID_NOTIFICATION(
2344+
args.GetIsolate(), "Invalid notification %llun", id);
2345+
}
2346+
2347+
Mutex::ScopedLock lock(notifications_mutex_);
2348+
env->notifications_queue_.insert(id);
2349+
uv_async_send(&env->notifications_async_);
2350+
}
2351+
2352+
void Environment::DispatchNotifications() {
2353+
Isolate* isolate = this->isolate();
2354+
v8::Local<v8::Object> process = process_object();
2355+
Mutex::ScopedLock lock(notifications_mutex_);
2356+
HandleScope handle_scope(isolate);
2357+
2358+
for (auto id : notifications_queue_) {
2359+
v8::Local<v8::Function> callback =
2360+
notifications_callbacks_[id].Get(isolate);
2361+
MakeCallback(isolate, process, callback, 0, nullptr, {0, 0})
2362+
.ToLocalChecked();
2363+
}
2364+
2365+
notifications_queue_.clear();
2366+
}
22632367
} // namespace node

src/env.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1067,6 +1067,15 @@ class Environment final : public MemoryRetainer {
10671067

10681068
v8::Global<v8::Module> temporary_required_module_facade_original;
10691069

1070+
void DispatchNotifications();
1071+
1072+
static void RegisterNotification(
1073+
const v8::FunctionCallbackInfo<v8::Value>& args);
1074+
static void UnregisterNotification(
1075+
const v8::FunctionCallbackInfo<v8::Value>& args);
1076+
static void GetNotifications(const v8::FunctionCallbackInfo<v8::Value>& args);
1077+
static void SendNotification(const v8::FunctionCallbackInfo<v8::Value>& args);
1078+
10701079
private:
10711080
inline void ThrowError(v8::Local<v8::Value> (*fun)(v8::Local<v8::String>,
10721081
v8::Local<v8::Value>),
@@ -1087,6 +1096,7 @@ class Environment final : public MemoryRetainer {
10871096
uv_prepare_t idle_prepare_handle_;
10881097
uv_check_t idle_check_handle_;
10891098
uv_async_t task_queues_async_;
1099+
uv_async_t notifications_async_;
10901100
int64_t task_queues_async_refs_ = 0;
10911101

10921102
// These may be read by ctors and should be listed before complex fields.
@@ -1246,6 +1256,13 @@ class Environment final : public MemoryRetainer {
12461256

12471257
v8::CpuProfiler* cpu_profiler_ = nullptr;
12481258
std::vector<v8::ProfilerId> pending_profiles_;
1259+
1260+
static Mutex notifications_mutex_;
1261+
static uint64_t next_notification_id_;
1262+
static std::unordered_map<uint64_t, Environment*> notifications_;
1263+
std::unordered_map<uint64_t, v8::Global<v8::Function>>
1264+
notifications_callbacks_;
1265+
std::set<uint64_t> notifications_queue_;
12491266
};
12501267

12511268
} // namespace node

src/node_errors.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ void OOMErrorHandler(const char* location, const v8::OOMDetails& details);
9898
V(ERR_INVALID_PACKAGE_CONFIG, Error) \
9999
V(ERR_INVALID_OBJECT_DEFINE_PROPERTY, TypeError) \
100100
V(ERR_INVALID_MODULE, Error) \
101+
V(ERR_INVALID_NOTIFICATION, Error) \
101102
V(ERR_INVALID_STATE, Error) \
102103
V(ERR_INVALID_THIS, TypeError) \
103104
V(ERR_INVALID_URL, TypeError) \
@@ -217,6 +218,7 @@ ERRORS_WITH_CODE(V)
217218
V(ERR_INVALID_ADDRESS, "Invalid socket address") \
218219
V(ERR_INVALID_INVOCATION, "Invalid invocation") \
219220
V(ERR_INVALID_MODULE, "No such module") \
221+
V(ERR_INVALID_NOTIFICATION, "Invalid notification") \
220222
V(ERR_INVALID_STATE, "Invalid state") \
221223
V(ERR_INVALID_THIS, "Value of \"this\" is the wrong type") \
222224
V(ERR_INVALID_URL_SCHEME, "The URL must be of scheme file:") \

0 commit comments

Comments
 (0)