@gjsify/node-gi 0.58.0 → 0.59.1
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.
- package/binding.gyp +2 -0
- package/gettext.js +2 -2
- package/gi.js +15 -15
- package/host.nativescript.js +186 -0
- package/host.node.js +224 -0
- package/index.js +6 -190
- package/nativescript.webpack.js +16 -0
- package/overrides/byte-array.js +24 -2
- package/package.json +12 -1
- package/prebuilds/darwin-arm64/node_gi.node +0 -0
- package/prebuilds/darwin-x64/node_gi.node +0 -0
- package/prebuilds/linux-arm64/node_gi.node +0 -0
- package/prebuilds/linux-x64/node_gi.node +0 -0
- package/prebuilds/win32-arm64/node_gi.node +0 -0
- package/prebuilds/win32-x64/node_gi.node +0 -0
- package/src/addon.cc +7 -0
- package/src/android-gdk.cc +291 -0
- package/src/android-log.cc +187 -0
- package/src/common.h +23 -0
- package/src/loop.cc +478 -141
package/src/loop.cc
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
// libuv <-> GLib main loop bridge: startMainLoop (uv-in-GLib GSource) +
|
|
3
3
|
// iterateMainContext + the uv-driven GLib auto-pump (GLib-in-libuv, the
|
|
4
|
-
// non-blocking case).
|
|
4
|
+
// non-blocking case). Where there is no libuv (Android/NativeScript,
|
|
5
|
+
// NODE_GI_HAS_LIBUV off) the auto-pump rides the thread's ALooper instead; both
|
|
6
|
+
// halves implement the same Pump* interface and only one is compiled in.
|
|
5
7
|
|
|
6
8
|
#include "common.h"
|
|
7
9
|
|
|
@@ -18,8 +20,36 @@
|
|
|
18
20
|
#include <glib-unix.h>
|
|
19
21
|
#endif
|
|
20
22
|
|
|
23
|
+
#if !NODE_GI_HAS_LIBUV && defined(__ANDROID__)
|
|
24
|
+
// The libuv-less half of the auto-pump rides the thread's ALooper instead of a
|
|
25
|
+
// uv loop, with a timerfd carrying GLib's next deadline. ALooper_* comes from
|
|
26
|
+
// libandroid, so this build needs -landroid — the NativeScript runtime links it
|
|
27
|
+
// already; the standalone cross-build passes it explicitly.
|
|
28
|
+
#include <algorithm>
|
|
29
|
+
|
|
30
|
+
#include <android/looper.h>
|
|
31
|
+
#include <sys/timerfd.h>
|
|
32
|
+
#include <unistd.h>
|
|
33
|
+
#endif
|
|
34
|
+
|
|
21
35
|
namespace nodegi {
|
|
22
36
|
|
|
37
|
+
static napi_env g_loop_env = nullptr; // captured at startMainLoop (main thread)
|
|
38
|
+
static gboolean g_loop_started = FALSE;
|
|
39
|
+
|
|
40
|
+
// TRUE while THIS addon's auto-pump is inside its own GLib work — draining the
|
|
41
|
+
// context or running the prepare/query hint pass. On Node the UvLoopSource
|
|
42
|
+
// consults it to PARK itself (mask the backend fd, report not-ready, skip
|
|
43
|
+
// uv_run): a pump-driven context iteration must never dispatch the uv-in-GLib
|
|
44
|
+
// source, or it would nest uv_run inside the very uv callback that drives the
|
|
45
|
+
// pump (uv_run is not reentrant) — and recursively re-enter the pump itself. On
|
|
46
|
+
// every host it additionally keeps the drain and the hint pass out of each
|
|
47
|
+
// other, which is all it does on the libuv-less Android pump (no co-pump there,
|
|
48
|
+
// so nothing to park). Name kept for the incident trail in common.h.
|
|
49
|
+
static gboolean g_in_uv_pump = FALSE;
|
|
50
|
+
|
|
51
|
+
#if NODE_GI_HAS_LIBUV
|
|
52
|
+
|
|
23
53
|
// ---- libuv <-> GLib main loop bridge (milestone: mainloop) ----
|
|
24
54
|
//
|
|
25
55
|
// Port of node-gtk's src/loop.cc (romgrk and contributors, MIT) to N-API. Nests
|
|
@@ -43,19 +73,9 @@ struct UvLoopSource {
|
|
|
43
73
|
#endif
|
|
44
74
|
};
|
|
45
75
|
|
|
46
|
-
static napi_env g_loop_env = nullptr; // captured at startMainLoop (main thread)
|
|
47
|
-
static gboolean g_loop_started = FALSE;
|
|
48
76
|
static napi_ref g_process_ref = nullptr; // process
|
|
49
77
|
static napi_ref g_tick_callback_ref = nullptr; // process._tickCallback
|
|
50
78
|
|
|
51
|
-
// TRUE while the uv-driven auto-pump (below) is inside its own GLib work —
|
|
52
|
-
// draining the context or running the prepare/query hint pass. The UvLoopSource
|
|
53
|
-
// consults it to PARK itself (mask the backend fd, report not-ready, skip
|
|
54
|
-
// uv_run): a pump-driven context iteration must never dispatch the uv-in-GLib
|
|
55
|
-
// source, or it would nest uv_run inside the very uv callback that drives the
|
|
56
|
-
// pump (uv_run is not reentrant) — and recursively re-enter the pump itself.
|
|
57
|
-
static gboolean g_in_uv_pump = FALSE;
|
|
58
|
-
|
|
59
79
|
// Drain Node's nextTick queue + run a microtask checkpoint. process._tickCallback
|
|
60
80
|
// invoked through napi_make_callback runs the tick queue, and the surrounding
|
|
61
81
|
// callback scope's close performs the microtask checkpoint — the N-API analogue
|
|
@@ -92,6 +112,8 @@ static void DrainMicrotasks() {
|
|
|
92
112
|
napi_close_handle_scope(env, scope);
|
|
93
113
|
}
|
|
94
114
|
|
|
115
|
+
#endif // NODE_GI_HAS_LIBUV (UvLoopSource + the Node nextTick/microtask drain)
|
|
116
|
+
|
|
95
117
|
// ---- cross-runtime microtask checkpoint (Bun/Deno) --------------------------
|
|
96
118
|
//
|
|
97
119
|
// Node's napi_make_callback performs the nextTick + microtask checkpoint when
|
|
@@ -176,6 +198,7 @@ void NodeGiMaybeDrainMicrotasks(napi_env env) {
|
|
|
176
198
|
napi_close_handle_scope(env, scope);
|
|
177
199
|
}
|
|
178
200
|
|
|
201
|
+
#if NODE_GI_HAS_LIBUV
|
|
179
202
|
#ifndef _WIN32
|
|
180
203
|
static gboolean uv_source_prepare(GSource* base, gint* timeout) {
|
|
181
204
|
UvLoopSource* s = reinterpret_cast<UvLoopSource*>(base);
|
|
@@ -316,6 +339,7 @@ static GSourceFuncs uv_source_funcs = {
|
|
|
316
339
|
uv_source_prepare, uv_source_check, uv_source_dispatch, nullptr, nullptr, nullptr,
|
|
317
340
|
};
|
|
318
341
|
#endif // _WIN32 (UvLoopSource: POSIX fd-driven / Windows readiness-bounded co-pump)
|
|
342
|
+
#endif // NODE_GI_HAS_LIBUV
|
|
319
343
|
|
|
320
344
|
// iterateMainContext(mayBlock?) -> boolean
|
|
321
345
|
//
|
|
@@ -334,13 +358,139 @@ Napi::Value IterateMainContext(const Napi::CallbackInfo& info) {
|
|
|
334
358
|
return Napi::Boolean::New(env, dispatched == TRUE);
|
|
335
359
|
}
|
|
336
360
|
|
|
337
|
-
// ----
|
|
361
|
+
// ---- the GLib auto-pump (the non-blocking case) -----------------------------
|
|
338
362
|
//
|
|
339
|
-
//
|
|
340
|
-
//
|
|
363
|
+
// With NO blocking GLib loop running, a plain `node bundle.mjs` (or a
|
|
364
|
+
// NativeScript Android app) still needs pending GLib sources (Gio async
|
|
341
365
|
// completions, GLib timeouts/idles, DBus) to dispatch — under GJS the GLib loop
|
|
342
|
-
// IS the process loop, so they always do.
|
|
343
|
-
// default GLib main context instead
|
|
366
|
+
// IS the process loop, so they always do. The HOST's own event loop drives the
|
|
367
|
+
// default GLib main context instead, and the two halves of that are the same on
|
|
368
|
+
// every host:
|
|
369
|
+
//
|
|
370
|
+
// • DRAIN — dispatch everything the context has ready right now
|
|
371
|
+
// (PumpDrainContext), run from the host loop with a valid napi scope;
|
|
372
|
+
// • WAKE — ask the context when its earliest timer is due and which fds it
|
|
373
|
+
// polls (PumpQueryWakeups), then mirror both into whatever primitive the
|
|
374
|
+
// host wakes on (PumpArmWakeups, the one host-specific half: libuv
|
|
375
|
+
// timer/poll watchers on Node, a timerfd + ALooper_addFd on Android).
|
|
376
|
+
//
|
|
377
|
+
// Both of those must never run while a blocking GLib loop owns the context
|
|
378
|
+
// (g_main_depth() > 0) or re-entrantly from our own GLib work (g_in_uv_pump).
|
|
379
|
+
|
|
380
|
+
static gboolean g_pump_inited = FALSE;
|
|
381
|
+
static gboolean g_pump_context_acquired = FALSE;
|
|
382
|
+
static std::vector<GPollFD>* g_pump_fds = nullptr; // query scratch (reused)
|
|
383
|
+
static int g_pump_async_pending = 0; // in-flight scope=async GI callbacks
|
|
384
|
+
|
|
385
|
+
// Host-specific: mirror the queried deadline + fds into the host's wake-up
|
|
386
|
+
// primitives. Defined once per host below.
|
|
387
|
+
static void PumpArmWakeups();
|
|
388
|
+
|
|
389
|
+
// Drain every currently-ready GLib source (bounded). Each iteration is a full,
|
|
390
|
+
// self-contained GLib cycle, so readiness (fds, timers, idles) is re-evaluated
|
|
391
|
+
// natively — the host's watchers are pure wake-ups. No-op while a blocking GLib
|
|
392
|
+
// loop is dispatching (g_main_depth() > 0) or when re-entered from a nested
|
|
393
|
+
// host-loop turn inside our own dispatch (g_in_uv_pump).
|
|
394
|
+
static void PumpDrainContext() {
|
|
395
|
+
if (g_main_depth() > 0 || g_in_uv_pump) return;
|
|
396
|
+
GMainContext* ctx = g_main_context_default();
|
|
397
|
+
// The drain runs from a bare host-loop C callback — there is NO ambient V8
|
|
398
|
+
// HandleScope (unlike a blocking run(), which is entered through a
|
|
399
|
+
// JS-initiated N-API frame). Anything the dispatched sources do that creates
|
|
400
|
+
// JS handles outside its own scope (the toggle-ref bridge's
|
|
401
|
+
// napi_get_reference_value on a GTask teardown unref, most prominently) would
|
|
402
|
+
// abort with "Cannot create a handle without a HandleScope" — so provide one
|
|
403
|
+
// for the whole drain.
|
|
404
|
+
napi_handle_scope scope = nullptr;
|
|
405
|
+
if (g_loop_env != nullptr) napi_open_handle_scope(g_loop_env, &scope);
|
|
406
|
+
g_in_uv_pump = TRUE;
|
|
407
|
+
int guard = 0;
|
|
408
|
+
while (g_main_context_iteration(ctx, FALSE) && ++guard < 1000) {
|
|
409
|
+
}
|
|
410
|
+
g_in_uv_pump = FALSE;
|
|
411
|
+
if (scope != nullptr) napi_close_handle_scope(g_loop_env, scope);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// The prepare+query+check HINT pass: learn GLib's earliest-timer deadline and
|
|
415
|
+
// which fds it polls, leaving the queried set in g_pump_fds[0..n). Returns the
|
|
416
|
+
// fd count, or -1 when the pass must not run. `*timeout` is GLib's
|
|
417
|
+
// milliseconds-to-next-deadline: 0 = work due now, -1 = no deadline. The
|
|
418
|
+
// g_in_uv_pump flag is held across it so UvLoopSource stays parked (masking
|
|
419
|
+
// uv's backend fd out of the queried set — it must not be watched, and uv's own
|
|
420
|
+
// backend timeout must not feed back into the deadline hint).
|
|
421
|
+
//
|
|
422
|
+
// The CHECK is not cosmetic and must never be dropped again: a GSource may hold
|
|
423
|
+
// a LOCK between its prepare() and its check(), and leaving the context prepared
|
|
424
|
+
// deadlocks the process on the next call into that library. GDK's Wayland event
|
|
425
|
+
// source is exactly that shape — its prepare() takes libwayland's designated-reader
|
|
426
|
+
// slot (`wl_display_prepare_read_queue`) and only its check() releases it
|
|
427
|
+
// (`wl_display_read_events` / `wl_display_cancel_read`). Measured (gjsify #1145,
|
|
428
|
+
// gtk 4.22 / Fedora 44 / Mesa Vulkan): with the hint pass ending at query, the very
|
|
429
|
+
// next `gtk_window_present()` blocked forever inside `wl_display_roundtrip_queue`
|
|
430
|
+
// under `gsk_renderer_new_for_surface_full`, so the app never finished showing its
|
|
431
|
+
// window while its GDBus worker thread kept answering Peer.Ping and Introspect —
|
|
432
|
+
// a live process at ~0.5 % CPU that read as "devtools hangs". Only Wayland shows
|
|
433
|
+
// it (GdkX11's source holds nothing across the pair), only node (bun/deno pump
|
|
434
|
+
// through complete `g_main_context_iteration`s), and only once GTK was initialised
|
|
435
|
+
// at g_main_depth() == 0 — `Gio.Application.register()` before `runAsync()` does
|
|
436
|
+
// exactly that, which is why an app that only ever calls `run()` never saw it.
|
|
437
|
+
// Passing revents = 0 is the right reading: this pass did NOT poll, so no fd is
|
|
438
|
+
// reported ready and every source that took something in prepare() gives it back.
|
|
439
|
+
//
|
|
440
|
+
// No dispatch pairs with the check: g_main_context_prepare() clears
|
|
441
|
+
// pending_dispatches on entry (glib gmain.c), and PumpDrainContext() runs on the
|
|
442
|
+
// same host-loop turn, so anything this marks ready is dispatched there rather
|
|
443
|
+
// than being stranded.
|
|
444
|
+
static int PumpQueryWakeups(gint* timeout) {
|
|
445
|
+
if (!g_pump_inited || g_main_depth() > 0 || g_in_uv_pump) return -1;
|
|
446
|
+
GMainContext* ctx = g_main_context_default();
|
|
447
|
+
gint prio = 0;
|
|
448
|
+
*timeout = -1;
|
|
449
|
+
g_in_uv_pump = TRUE;
|
|
450
|
+
g_main_context_prepare(ctx, &prio);
|
|
451
|
+
int nfds = g_main_context_query(ctx, prio, timeout, g_pump_fds->data(),
|
|
452
|
+
static_cast<gint>(g_pump_fds->size()));
|
|
453
|
+
while (nfds > static_cast<int>(g_pump_fds->size())) {
|
|
454
|
+
g_pump_fds->resize(nfds);
|
|
455
|
+
nfds = g_main_context_query(ctx, prio, timeout, g_pump_fds->data(),
|
|
456
|
+
static_cast<gint>(g_pump_fds->size()));
|
|
457
|
+
}
|
|
458
|
+
// g_main_context_query fills fd + events and leaves revents untouched, so the
|
|
459
|
+
// previous pass's values would otherwise be replayed as fresh readiness.
|
|
460
|
+
for (int i = 0; i < nfds; i++) (*g_pump_fds)[i].revents = 0;
|
|
461
|
+
g_main_context_check(ctx, prio, g_pump_fds->data(), nfds);
|
|
462
|
+
g_in_uv_pump = FALSE;
|
|
463
|
+
return nfds;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Own the default context on this (the JS) thread. Required so a cross-thread
|
|
467
|
+
// g_source_attach (a completing GTask worker) signals the context's wakeup
|
|
468
|
+
// eventfd — which the host's fd watcher turns into a host-loop wake. Same-thread
|
|
469
|
+
// recursive acquisition by a later blocking g_main_loop_run() still works.
|
|
470
|
+
static void PumpAcquireContext() {
|
|
471
|
+
g_pump_fds = new std::vector<GPollFD>(16);
|
|
472
|
+
g_pump_context_acquired = g_main_context_acquire(g_main_context_default());
|
|
473
|
+
if (!g_pump_context_acquired) {
|
|
474
|
+
g_printerr("(node-gi) warning: default GMainContext is owned by another thread; "
|
|
475
|
+
"cross-thread wakeups may be delayed\n");
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
static void PumpReleaseContext() {
|
|
480
|
+
delete g_pump_fds;
|
|
481
|
+
g_pump_fds = nullptr;
|
|
482
|
+
if (g_pump_context_acquired) {
|
|
483
|
+
g_main_context_release(g_main_context_default());
|
|
484
|
+
g_pump_context_acquired = FALSE;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
#if NODE_GI_HAS_LIBUV
|
|
489
|
+
|
|
490
|
+
// ---- the libuv-driven half (Node) -------------------------------------------
|
|
491
|
+
//
|
|
492
|
+
// The inverse co-pump of UvLoopSource: Node's libuv loop drives the default GLib
|
|
493
|
+
// main context.
|
|
344
494
|
//
|
|
345
495
|
// • a uv_prepare + uv_check handle pair drains every READY GLib source once
|
|
346
496
|
// per libuv loop turn (a bounded `g_main_context_iteration(ctx, FALSE)`
|
|
@@ -384,15 +534,11 @@ struct PumpPoll {
|
|
|
384
534
|
gboolean started; // whether the watcher is currently armed (see PumpPollCb)
|
|
385
535
|
};
|
|
386
536
|
|
|
387
|
-
static gboolean g_pump_inited = FALSE;
|
|
388
|
-
static gboolean g_pump_context_acquired = FALSE;
|
|
389
537
|
static uv_prepare_t g_pump_prepare;
|
|
390
538
|
static uv_check_t g_pump_check;
|
|
391
539
|
static uv_timer_t g_pump_timer;
|
|
392
540
|
static gboolean g_pump_timer_reffed = FALSE;
|
|
393
541
|
static std::unordered_map<int, PumpPoll*>* g_pump_polls = nullptr;
|
|
394
|
-
static std::vector<GPollFD>* g_pump_fds = nullptr; // query scratch (reused)
|
|
395
|
-
static int g_pump_async_pending = 0; // in-flight scope=async GI callbacks
|
|
396
542
|
static gboolean g_pump_prepare_reffed = FALSE;
|
|
397
543
|
static gboolean g_pump_poll_warned = FALSE;
|
|
398
544
|
|
|
@@ -508,78 +654,12 @@ static gboolean SyncPumpPolls(int nfds) {
|
|
|
508
654
|
#endif // _WIN32
|
|
509
655
|
}
|
|
510
656
|
|
|
511
|
-
//
|
|
512
|
-
//
|
|
513
|
-
// natively — the uv watchers are pure wake-ups. No-op while a blocking GLib
|
|
514
|
-
// loop is dispatching (g_main_depth() > 0) or when re-entered from a nested
|
|
515
|
-
// uv_run inside our own dispatch (g_in_uv_pump).
|
|
516
|
-
static void PumpDrainContext() {
|
|
517
|
-
if (g_main_depth() > 0 || g_in_uv_pump) return;
|
|
518
|
-
GMainContext* ctx = g_main_context_default();
|
|
519
|
-
// The drain runs from a bare uv_prepare/uv_check C callback — there is NO
|
|
520
|
-
// ambient V8 HandleScope (unlike a blocking run(), which is entered through a
|
|
521
|
-
// JS-initiated N-API frame). Anything the dispatched sources do that creates
|
|
522
|
-
// JS handles outside its own scope (the toggle-ref bridge's
|
|
523
|
-
// napi_get_reference_value on a GTask teardown unref, most prominently) would
|
|
524
|
-
// abort with "Cannot create a handle without a HandleScope" — so provide one
|
|
525
|
-
// for the whole drain.
|
|
526
|
-
napi_handle_scope scope = nullptr;
|
|
527
|
-
if (g_loop_env != nullptr) napi_open_handle_scope(g_loop_env, &scope);
|
|
528
|
-
g_in_uv_pump = TRUE;
|
|
529
|
-
int guard = 0;
|
|
530
|
-
while (g_main_context_iteration(ctx, FALSE) && ++guard < 1000) {
|
|
531
|
-
}
|
|
532
|
-
g_in_uv_pump = FALSE;
|
|
533
|
-
if (scope != nullptr) napi_close_handle_scope(g_loop_env, scope);
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
// The prepare+query+check HINT pass: learn GLib's earliest-timer deadline + poll
|
|
537
|
-
// fds and mirror them into the uv timer / uv_poll watchers, so libuv's poll sleep
|
|
538
|
-
// ends exactly when GLib next has work. Runs only at g_main_depth() == 0; the
|
|
539
|
-
// g_in_uv_pump flag is held across it so UvLoopSource stays parked (masking uv's
|
|
540
|
-
// backend fd out of the queried set — it must not be uv_poll'd, and uv's own
|
|
541
|
-
// backend timeout must not feed back into the timer hint).
|
|
542
|
-
//
|
|
543
|
-
// The CHECK is not cosmetic and must never be dropped again: a GSource may hold a
|
|
544
|
-
// LOCK between its prepare() and its check(), and leaving the context prepared
|
|
545
|
-
// deadlocks the process on the next call into that library. GDK's Wayland event
|
|
546
|
-
// source is exactly that shape — its prepare() takes libwayland's designated-reader
|
|
547
|
-
// slot (`wl_display_prepare_read_queue`) and only its check() releases it
|
|
548
|
-
// (`wl_display_read_events` / `wl_display_cancel_read`). Measured (gjsify #1145,
|
|
549
|
-
// gtk 4.22 / Fedora 44 / Mesa Vulkan): with the hint pass ending at query, the very
|
|
550
|
-
// next `gtk_window_present()` blocked forever inside `wl_display_roundtrip_queue`
|
|
551
|
-
// under `gsk_renderer_new_for_surface_full`, so the app never finished showing its
|
|
552
|
-
// window while its GDBus worker thread kept answering Peer.Ping and Introspect —
|
|
553
|
-
// a live process at ~0.5 % CPU that read as "devtools hangs". Only Wayland shows
|
|
554
|
-
// it (GdkX11's source holds nothing across the pair), only node (bun/deno pump
|
|
555
|
-
// through complete `g_main_context_iteration`s), and only once GTK was initialised
|
|
556
|
-
// at g_main_depth() == 0 — `Gio.Application.register()` before `runAsync()` does
|
|
557
|
-
// exactly that, which is why an app that only ever calls `run()` never saw it.
|
|
558
|
-
// Passing revents = 0 is the right reading: this pass did NOT poll, so no fd is
|
|
559
|
-
// reported ready and every source that took something in prepare() gives it back.
|
|
657
|
+
// Mirror GLib's queried deadline + fds into the uv timer / uv_poll watchers, so
|
|
658
|
+
// libuv's poll sleep ends exactly when GLib next has work.
|
|
560
659
|
static void PumpArmWakeups() {
|
|
561
|
-
if (!g_pump_inited || g_main_depth() > 0 || g_in_uv_pump) return;
|
|
562
|
-
GMainContext* ctx = g_main_context_default();
|
|
563
|
-
gint prio = 0;
|
|
564
660
|
gint timeout = -1;
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
int nfds = g_main_context_query(ctx, prio, &timeout, g_pump_fds->data(),
|
|
568
|
-
static_cast<gint>(g_pump_fds->size()));
|
|
569
|
-
while (nfds > static_cast<int>(g_pump_fds->size())) {
|
|
570
|
-
g_pump_fds->resize(nfds);
|
|
571
|
-
nfds = g_main_context_query(ctx, prio, &timeout, g_pump_fds->data(),
|
|
572
|
-
static_cast<gint>(g_pump_fds->size()));
|
|
573
|
-
}
|
|
574
|
-
// g_main_context_query fills fd + events and leaves revents untouched, so the
|
|
575
|
-
// previous pass's values would otherwise be replayed as fresh readiness.
|
|
576
|
-
for (int i = 0; i < nfds; i++) (*g_pump_fds)[i].revents = 0;
|
|
577
|
-
// No dispatch pairs with this check: g_main_context_prepare() clears
|
|
578
|
-
// pending_dispatches on entry (glib gmain.c), and PumpDrainContext() — a full
|
|
579
|
-
// iteration — runs on the same libuv turn, so anything this marks ready is
|
|
580
|
-
// dispatched there rather than being stranded.
|
|
581
|
-
g_main_context_check(ctx, prio, g_pump_fds->data(), nfds);
|
|
582
|
-
g_in_uv_pump = FALSE;
|
|
661
|
+
const int nfds = PumpQueryWakeups(&timeout);
|
|
662
|
+
if (nfds < 0) return;
|
|
583
663
|
|
|
584
664
|
if (!SyncPumpPolls(nfds)) {
|
|
585
665
|
// Degraded wake path: poll at a short cadence instead of on readiness. This is
|
|
@@ -641,6 +721,244 @@ static void PumpCheckCb(uv_check_t* /*h*/) {
|
|
|
641
721
|
PumpArmWakeups();
|
|
642
722
|
}
|
|
643
723
|
|
|
724
|
+
static void PumpInit(uv_loop_t* loop) {
|
|
725
|
+
if (g_pump_inited) return;
|
|
726
|
+
g_pump_polls = new std::unordered_map<int, PumpPoll*>();
|
|
727
|
+
PumpAcquireContext();
|
|
728
|
+
|
|
729
|
+
uv_prepare_init(loop, &g_pump_prepare);
|
|
730
|
+
uv_prepare_start(&g_pump_prepare, PumpPrepareCb);
|
|
731
|
+
uv_unref(reinterpret_cast<uv_handle_t*>(&g_pump_prepare));
|
|
732
|
+
|
|
733
|
+
uv_check_init(loop, &g_pump_check);
|
|
734
|
+
uv_check_start(&g_pump_check, PumpCheckCb);
|
|
735
|
+
uv_unref(reinterpret_cast<uv_handle_t*>(&g_pump_check));
|
|
736
|
+
|
|
737
|
+
uv_timer_init(loop, &g_pump_timer);
|
|
738
|
+
uv_unref(reinterpret_cast<uv_handle_t*>(&g_pump_timer));
|
|
739
|
+
|
|
740
|
+
g_pump_inited = TRUE;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// Close the pump's uv handles so the loop can wind down cleanly.
|
|
744
|
+
static void PumpShutdownPlatform() {
|
|
745
|
+
uv_prepare_stop(&g_pump_prepare);
|
|
746
|
+
uv_check_stop(&g_pump_check);
|
|
747
|
+
uv_timer_stop(&g_pump_timer);
|
|
748
|
+
uv_close(reinterpret_cast<uv_handle_t*>(&g_pump_prepare), nullptr);
|
|
749
|
+
uv_close(reinterpret_cast<uv_handle_t*>(&g_pump_check), nullptr);
|
|
750
|
+
uv_close(reinterpret_cast<uv_handle_t*>(&g_pump_timer), nullptr);
|
|
751
|
+
if (g_pump_polls != nullptr) {
|
|
752
|
+
for (auto& it : *g_pump_polls) {
|
|
753
|
+
uv_poll_stop(&it.second->handle);
|
|
754
|
+
uv_close(reinterpret_cast<uv_handle_t*>(&it.second->handle), PumpPollCloseCb);
|
|
755
|
+
}
|
|
756
|
+
delete g_pump_polls;
|
|
757
|
+
g_pump_polls = nullptr;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
#elif defined(__ANDROID__)
|
|
762
|
+
|
|
763
|
+
// ---- the ALooper-driven half (Android / NativeScript) -----------------------
|
|
764
|
+
//
|
|
765
|
+
// NativeScript's V8 runtime is a Node-API host with NO libuv of its own, so the
|
|
766
|
+
// whole uv half above is compiled out (NODE_GI_HAS_LIBUV 0 — common.h explains
|
|
767
|
+
// why a mere REFERENCE to a uv symbol is fatal there, not just a call). The
|
|
768
|
+
// host's event loop is Android's own ALooper on the thread that runs JS, and the
|
|
769
|
+
// pump mirrors GLib onto it with the two halves named at the top of this
|
|
770
|
+
// section:
|
|
771
|
+
//
|
|
772
|
+
// • WAKE — every fd GLib queries is registered with ALooper_addFd, and GLib's
|
|
773
|
+
// next deadline is armed on a timerfd registered the same way. The looper
|
|
774
|
+
// then wakes for GLib I/O, for a cross-thread wakeup (the context's own
|
|
775
|
+
// eventfd, signalled by a completing GTask worker) and for GLib timers
|
|
776
|
+
// alike, instead of for a uv_poll/uv_timer.
|
|
777
|
+
// • DRAIN — a looper callback runs from the host's poll, OUTSIDE any JS frame
|
|
778
|
+
// or handle scope, so it must not touch napi or JS at all. It only posts a
|
|
779
|
+
// threadsafe function; the TSFN's call_js runs on the JS thread inside a
|
|
780
|
+
// proper handle + callback scope and does the drain plus the re-arm.
|
|
781
|
+
//
|
|
782
|
+
// No keep-alive half: Android's looper belongs to the app and outlives every
|
|
783
|
+
// pump, so there is nothing to ref or unref. g_pump_async_pending is still
|
|
784
|
+
// maintained (calls.cc does it unconditionally) and still answers
|
|
785
|
+
// mainContextHasPending(); it just holds nothing open here.
|
|
786
|
+
//
|
|
787
|
+
// NOT supported on this host: a BLOCKING GLib.MainLoop.run() / Gio.Application
|
|
788
|
+
// .run() on the UI thread. It would park Android's own looper — no input, no
|
|
789
|
+
// frames, an ANR — and there is no co-pump to borrow (UvLoopSource embeds uv's
|
|
790
|
+
// backend fd in a GSource; a looper exposes no such fd). Nothing refuses it at
|
|
791
|
+
// build or link time, since the boxed GLib.MainLoop surface is host-independent;
|
|
792
|
+
// it simply freezes the thread it is called on. An app drives GLib through this
|
|
793
|
+
// pump instead.
|
|
794
|
+
|
|
795
|
+
static ALooper* g_looper = nullptr;
|
|
796
|
+
static napi_threadsafe_function g_pump_tsfn = nullptr;
|
|
797
|
+
static int g_pump_timerfd = -1;
|
|
798
|
+
// The GLib poll fds currently registered with the looper (not the timerfd, which
|
|
799
|
+
// is added once and stays). Small by nature — a linear sweep beats a hash map.
|
|
800
|
+
static std::vector<int>* g_looper_fds = nullptr;
|
|
801
|
+
// A drain post is already in flight: one looper poll can report several fds
|
|
802
|
+
// ready for a single GLib cycle, and one drain answers all of them. Written only
|
|
803
|
+
// on the JS thread (every poster runs there), so no atomic.
|
|
804
|
+
static gboolean g_pump_posted = FALSE;
|
|
805
|
+
|
|
806
|
+
// Ask for a drain on the JS thread. Callable from a looper callback: the only
|
|
807
|
+
// napi function it touches is the non-blocking TSFN post, which is the one
|
|
808
|
+
// documented to be legal from any thread at any time.
|
|
809
|
+
static void PumpPostTsfn() {
|
|
810
|
+
if (g_pump_tsfn == nullptr || g_pump_posted) return;
|
|
811
|
+
if (napi_call_threadsafe_function(g_pump_tsfn, nullptr, napi_tsfn_nonblocking) == napi_ok) {
|
|
812
|
+
g_pump_posted = TRUE;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// A GLib poll fd became ready. Wake-only, and the wake is an EDGE for the same
|
|
817
|
+
// reason as on libuv (#1912): ALooper is level-triggered and this callback reads
|
|
818
|
+
// nothing, so an fd that STAYS ready — the context's wakeup eventfd, which
|
|
819
|
+
// block_source() re-signals on every dispatch, or a dead socket at POLLHUP —
|
|
820
|
+
// would spin the host loop at 100 % CPU. Returning 0 removes the registration;
|
|
821
|
+
// PumpArmWakeups re-adds whatever GLib still queries.
|
|
822
|
+
static int PumpLooperFdCb(int /*fd*/, int /*events*/, void* /*data*/) {
|
|
823
|
+
PumpPostTsfn();
|
|
824
|
+
return 0;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// The timerfd expired. This one KEEPS its registration (returns 1): read() is
|
|
828
|
+
// what clears the expiry, so it cannot stay spuriously ready, and an
|
|
829
|
+
// armed-again timer needs only a new itimerspec, not a new watcher.
|
|
830
|
+
static int PumpTimerFdCb(int fd, int /*events*/, void* /*data*/) {
|
|
831
|
+
uint64_t expirations = 0;
|
|
832
|
+
while (read(fd, &expirations, sizeof(expirations)) == sizeof(expirations)) {
|
|
833
|
+
}
|
|
834
|
+
PumpPostTsfn();
|
|
835
|
+
return 1;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// Register every fd GLib queries, and drop the ones it no longer does.
|
|
839
|
+
// ALooper_addFd REPLACES an existing registration for the same fd, which is how
|
|
840
|
+
// a self-removed watcher (PumpLooperFdCb returning 0) comes back.
|
|
841
|
+
static void SyncLooperFds(int nfds) {
|
|
842
|
+
std::vector<int> wanted;
|
|
843
|
+
wanted.reserve(static_cast<size_t>(nfds));
|
|
844
|
+
for (int i = 0; i < nfds; i++) {
|
|
845
|
+
const GPollFD& pfd = (*g_pump_fds)[i];
|
|
846
|
+
int events = 0;
|
|
847
|
+
if (pfd.events & (G_IO_IN | G_IO_HUP | G_IO_ERR)) events |= ALOOPER_EVENT_INPUT;
|
|
848
|
+
if (pfd.events & G_IO_OUT) events |= ALOOPER_EVENT_OUTPUT;
|
|
849
|
+
if (events == 0) continue;
|
|
850
|
+
if (ALooper_addFd(g_looper, pfd.fd, ALOOPER_POLL_CALLBACK, events, PumpLooperFdCb,
|
|
851
|
+
nullptr) == 1) {
|
|
852
|
+
wanted.push_back(pfd.fd);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
for (int fd : *g_looper_fds) {
|
|
856
|
+
if (std::find(wanted.begin(), wanted.end(), fd) == wanted.end()) {
|
|
857
|
+
ALooper_removeFd(g_looper, fd);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
*g_looper_fds = std::move(wanted);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// Mirror GLib's deadline onto the timerfd. An all-zero it_value DISARMS a
|
|
864
|
+
// timerfd rather than firing immediately, so a 0 ms deadline ("work is due now")
|
|
865
|
+
// has to be answered by posting the drain directly; that drain re-arms.
|
|
866
|
+
static void ArmTimerfd(gint timeout) {
|
|
867
|
+
if (g_pump_timerfd < 0) return;
|
|
868
|
+
if (timeout == 0) {
|
|
869
|
+
PumpPostTsfn();
|
|
870
|
+
timeout = -1;
|
|
871
|
+
}
|
|
872
|
+
struct itimerspec its = {};
|
|
873
|
+
if (timeout > 0) {
|
|
874
|
+
its.it_value.tv_sec = timeout / 1000;
|
|
875
|
+
its.it_value.tv_nsec = static_cast<long>(timeout % 1000) * 1000000L;
|
|
876
|
+
}
|
|
877
|
+
timerfd_settime(g_pump_timerfd, 0, &its, nullptr);
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
static void PumpArmWakeups() {
|
|
881
|
+
gint timeout = -1;
|
|
882
|
+
const int nfds = PumpQueryWakeups(&timeout);
|
|
883
|
+
if (nfds < 0) return;
|
|
884
|
+
SyncLooperFds(nfds);
|
|
885
|
+
ArmTimerfd(timeout);
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
// The drain, on the JS thread, inside the scopes the TSFN infra provides.
|
|
889
|
+
static void PumpTsfnCb(napi_env /*env*/, napi_value /*js_cb*/, void* /*context*/,
|
|
890
|
+
void* /*data*/) {
|
|
891
|
+
g_pump_posted = FALSE;
|
|
892
|
+
if (!g_pump_inited) return;
|
|
893
|
+
PumpDrainContext();
|
|
894
|
+
PumpArmWakeups();
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
static void PumpInit(napi_env env, ALooper* looper) {
|
|
898
|
+
if (g_pump_inited) return;
|
|
899
|
+
g_looper = looper;
|
|
900
|
+
g_looper_fds = new std::vector<int>();
|
|
901
|
+
PumpAcquireContext();
|
|
902
|
+
|
|
903
|
+
// js_func null — PumpTsfnCb IS the callback, and the TSFN infra invokes it
|
|
904
|
+
// inside a handle + callback scope, so a dispatched GLib source re-entering JS
|
|
905
|
+
// runs as a proper N-API callback. max_queue_size 1 coalesces bursts;
|
|
906
|
+
// initial_thread_count 1 keeps it alive for the env's lifetime, so a looper
|
|
907
|
+
// callback may post without acquiring first. Same shape as toggle.cc's drain.
|
|
908
|
+
napi_value name = nullptr;
|
|
909
|
+
napi_create_string_utf8(env, "node-gi:glib-pump", NAPI_AUTO_LENGTH, &name);
|
|
910
|
+
if (napi_create_threadsafe_function(env, nullptr, nullptr, name, /*max_queue_size*/ 1,
|
|
911
|
+
/*initial_thread_count*/ 1, nullptr, nullptr, nullptr,
|
|
912
|
+
PumpTsfnCb, &g_pump_tsfn) != napi_ok) {
|
|
913
|
+
g_pump_tsfn = nullptr;
|
|
914
|
+
} else {
|
|
915
|
+
// The pump must not hold the host loop open by itself (the uv_unref
|
|
916
|
+
// equivalent). Best-effort: NativeScript no-ops TSFN ref/unref, and its
|
|
917
|
+
// looper does not end anyway.
|
|
918
|
+
napi_unref_threadsafe_function(env, g_pump_tsfn);
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// NONBLOCK because PumpTimerFdCb read()s until EAGAIN; CLOEXEC so no
|
|
922
|
+
// Gio.Subprocess child inherits it.
|
|
923
|
+
g_pump_timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
|
|
924
|
+
if (g_pump_timerfd >= 0) {
|
|
925
|
+
ALooper_addFd(g_looper, g_pump_timerfd, ALOOPER_POLL_CALLBACK, ALOOPER_EVENT_INPUT,
|
|
926
|
+
PumpTimerFdCb, nullptr);
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
g_pump_inited = TRUE;
|
|
930
|
+
// Arm the first wake-ups now. The uv half gets this for free from its
|
|
931
|
+
// always-active prepare handle, which runs on the very next loop turn; here
|
|
932
|
+
// nothing would ever ask until something already woke the looper.
|
|
933
|
+
PumpArmWakeups();
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
// Unregister everything the pump put on the looper. Reached only through an env
|
|
937
|
+
// cleanup hook, which NativeScript never runs for the main env — so on the live
|
|
938
|
+
// host this is dead code, kept because the pump must not leak a watcher into a
|
|
939
|
+
// looper that outlives it on any host that DOES tear an env down.
|
|
940
|
+
static void PumpShutdownPlatform() {
|
|
941
|
+
if (g_pump_tsfn != nullptr) {
|
|
942
|
+
napi_release_threadsafe_function(g_pump_tsfn, napi_tsfn_release);
|
|
943
|
+
g_pump_tsfn = nullptr;
|
|
944
|
+
}
|
|
945
|
+
if (g_looper_fds != nullptr) {
|
|
946
|
+
for (int fd : *g_looper_fds) ALooper_removeFd(g_looper, fd);
|
|
947
|
+
delete g_looper_fds;
|
|
948
|
+
g_looper_fds = nullptr;
|
|
949
|
+
}
|
|
950
|
+
if (g_pump_timerfd >= 0) {
|
|
951
|
+
ALooper_removeFd(g_looper, g_pump_timerfd);
|
|
952
|
+
close(g_pump_timerfd);
|
|
953
|
+
g_pump_timerfd = -1;
|
|
954
|
+
}
|
|
955
|
+
g_looper = nullptr;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
#else
|
|
959
|
+
#error "no GLib auto-pump for this host: it has neither libuv nor an ALooper"
|
|
960
|
+
#endif // NODE_GI_HAS_LIBUV / __ANDROID__ (the host-specific pump half)
|
|
961
|
+
|
|
644
962
|
// pumpKick() -> void — drain ready GLib sources + re-arm the wake hints NOW.
|
|
645
963
|
// The L1 layer calls this from a `process.on('beforeExit')` hook: with only
|
|
646
964
|
// UNREF'd handles active, uv_run(UV_RUN_DEFAULT) returns without ever running
|
|
@@ -656,6 +974,14 @@ Napi::Value PumpKick(const Napi::CallbackInfo& info) {
|
|
|
656
974
|
if (g_pump_inited && static_cast<napi_env>(info.Env()) == g_loop_env) {
|
|
657
975
|
PumpDrainContext();
|
|
658
976
|
PumpArmWakeups();
|
|
977
|
+
#if !NODE_GI_HAS_LIBUV && defined(__ANDROID__)
|
|
978
|
+
// Both calls above no-op while a blocking GLib loop owns the context or we
|
|
979
|
+
// are re-entered from our own dispatch, and on this host nothing else will
|
|
980
|
+
// come back for the work: the uv half has an always-active prepare handle
|
|
981
|
+
// that retries every loop turn, the looper only wakes for something it
|
|
982
|
+
// watches. The post defers the kick to a later JS-thread turn instead.
|
|
983
|
+
PumpPostTsfn();
|
|
984
|
+
#endif
|
|
659
985
|
}
|
|
660
986
|
return info.Env().Undefined();
|
|
661
987
|
}
|
|
@@ -668,35 +994,6 @@ NodeGiPumpJsDispatchScope::NodeGiPumpJsDispatchScope() : saved_(g_in_uv_pump) {
|
|
|
668
994
|
}
|
|
669
995
|
NodeGiPumpJsDispatchScope::~NodeGiPumpJsDispatchScope() { g_in_uv_pump = saved_; }
|
|
670
996
|
|
|
671
|
-
static void PumpInit(uv_loop_t* loop) {
|
|
672
|
-
if (g_pump_inited) return;
|
|
673
|
-
g_pump_polls = new std::unordered_map<int, PumpPoll*>();
|
|
674
|
-
g_pump_fds = new std::vector<GPollFD>(16);
|
|
675
|
-
|
|
676
|
-
// Own the default context on this (the JS) thread. Required so a cross-thread
|
|
677
|
-
// g_source_attach (a completing GTask worker) signals the context's wakeup
|
|
678
|
-
// eventfd — which our uv_poll watcher turns into a libuv wake. Same-thread
|
|
679
|
-
// recursive acquisition by a later blocking g_main_loop_run() still works.
|
|
680
|
-
g_pump_context_acquired = g_main_context_acquire(g_main_context_default());
|
|
681
|
-
if (!g_pump_context_acquired) {
|
|
682
|
-
g_printerr("(node-gi) warning: default GMainContext is owned by another thread; "
|
|
683
|
-
"cross-thread wakeups may be delayed\n");
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
uv_prepare_init(loop, &g_pump_prepare);
|
|
687
|
-
uv_prepare_start(&g_pump_prepare, PumpPrepareCb);
|
|
688
|
-
uv_unref(reinterpret_cast<uv_handle_t*>(&g_pump_prepare));
|
|
689
|
-
|
|
690
|
-
uv_check_init(loop, &g_pump_check);
|
|
691
|
-
uv_check_start(&g_pump_check, PumpCheckCb);
|
|
692
|
-
uv_unref(reinterpret_cast<uv_handle_t*>(&g_pump_check));
|
|
693
|
-
|
|
694
|
-
uv_timer_init(loop, &g_pump_timer);
|
|
695
|
-
uv_unref(reinterpret_cast<uv_handle_t*>(&g_pump_timer));
|
|
696
|
-
|
|
697
|
-
g_pump_inited = TRUE;
|
|
698
|
-
}
|
|
699
|
-
|
|
700
997
|
// mainContextHasPending() -> boolean
|
|
701
998
|
//
|
|
702
999
|
// Is there JS-armed GLib work a *pumping* runtime must stay alive for? True
|
|
@@ -762,49 +1059,51 @@ Napi::Value MakePumpPendingCount(const Napi::CallbackInfo& info) {
|
|
|
762
1059
|
bool NodeGiPumpAsyncBegin(napi_env env) {
|
|
763
1060
|
if (g_loop_env != nullptr && env != g_loop_env) return false;
|
|
764
1061
|
++g_pump_async_pending;
|
|
1062
|
+
#if NODE_GI_HAS_LIBUV
|
|
765
1063
|
if (g_pump_inited && g_pump_async_pending == 1 && !g_pump_prepare_reffed) {
|
|
766
1064
|
uv_ref(reinterpret_cast<uv_handle_t*>(&g_pump_prepare));
|
|
767
1065
|
g_pump_prepare_reffed = TRUE;
|
|
768
1066
|
}
|
|
1067
|
+
#else
|
|
1068
|
+
// Nothing to hold open here (§ the ALooper half), but the work just armed may
|
|
1069
|
+
// be a SAME-THREAD source — a GLib idle or timeout added from JS — which does
|
|
1070
|
+
// not signal the context's wakeup eventfd, so no fd the looper watches
|
|
1071
|
+
// changed and the looper would sleep straight past it. Ask for a drain, which
|
|
1072
|
+
// re-arms the timerfd to the new deadline. (The uv half needs no equivalent:
|
|
1073
|
+
// its prepare handle re-queries on the very next loop turn.)
|
|
1074
|
+
//
|
|
1075
|
+
// This covers same-thread sources armed FROM JS only. One armed inside a C
|
|
1076
|
+
// library on this thread — GDK's repeating timeout is the known case, and only
|
|
1077
|
+
// track C of ADR 0104 reaches it — still waits for the next drain, because
|
|
1078
|
+
// nothing re-queries the context per looper turn the way uv's prepare does.
|
|
1079
|
+
if (g_pump_inited) PumpPostTsfn();
|
|
1080
|
+
#endif
|
|
769
1081
|
return true;
|
|
770
1082
|
}
|
|
771
1083
|
|
|
772
1084
|
void NodeGiPumpAsyncEnd() {
|
|
773
1085
|
if (g_pump_async_pending == 0) return;
|
|
774
1086
|
--g_pump_async_pending;
|
|
1087
|
+
#if NODE_GI_HAS_LIBUV
|
|
775
1088
|
if (g_pump_inited && g_pump_async_pending == 0 && g_pump_prepare_reffed) {
|
|
776
1089
|
uv_unref(reinterpret_cast<uv_handle_t*>(&g_pump_prepare));
|
|
777
1090
|
g_pump_prepare_reffed = FALSE;
|
|
778
1091
|
}
|
|
1092
|
+
#endif
|
|
779
1093
|
}
|
|
780
1094
|
|
|
781
|
-
// Env-teardown:
|
|
782
|
-
// Only the env that armed the pump (the
|
|
1095
|
+
// Env-teardown: release the pump's host handles so the host loop can wind down
|
|
1096
|
+
// cleanly, then give the context back. Only the env that armed the pump (the
|
|
1097
|
+
// main env) tears it down.
|
|
783
1098
|
void NodeGiPumpShutdown(napi_env env) {
|
|
784
1099
|
if (!g_pump_inited || env != g_loop_env) return;
|
|
785
1100
|
g_pump_inited = FALSE;
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
uv_timer_stop(&g_pump_timer);
|
|
789
|
-
uv_close(reinterpret_cast<uv_handle_t*>(&g_pump_prepare), nullptr);
|
|
790
|
-
uv_close(reinterpret_cast<uv_handle_t*>(&g_pump_check), nullptr);
|
|
791
|
-
uv_close(reinterpret_cast<uv_handle_t*>(&g_pump_timer), nullptr);
|
|
792
|
-
if (g_pump_polls != nullptr) {
|
|
793
|
-
for (auto& it : *g_pump_polls) {
|
|
794
|
-
uv_poll_stop(&it.second->handle);
|
|
795
|
-
uv_close(reinterpret_cast<uv_handle_t*>(&it.second->handle), PumpPollCloseCb);
|
|
796
|
-
}
|
|
797
|
-
delete g_pump_polls;
|
|
798
|
-
g_pump_polls = nullptr;
|
|
799
|
-
}
|
|
800
|
-
delete g_pump_fds;
|
|
801
|
-
g_pump_fds = nullptr;
|
|
802
|
-
if (g_pump_context_acquired) {
|
|
803
|
-
g_main_context_release(g_main_context_default());
|
|
804
|
-
g_pump_context_acquired = FALSE;
|
|
805
|
-
}
|
|
1101
|
+
PumpShutdownPlatform();
|
|
1102
|
+
PumpReleaseContext();
|
|
806
1103
|
}
|
|
807
1104
|
|
|
1105
|
+
#if NODE_GI_HAS_LIBUV
|
|
1106
|
+
|
|
808
1107
|
// startMainLoop() -> void
|
|
809
1108
|
// Attach the libuv-backed GSource to the default GLib main context (idempotent).
|
|
810
1109
|
// Harmless until a GLib main loop actually runs — it adds no uv handle, so it
|
|
@@ -892,4 +1191,42 @@ Napi::Value StartMainLoop(const Napi::CallbackInfo& info) {
|
|
|
892
1191
|
return env.Undefined();
|
|
893
1192
|
}
|
|
894
1193
|
|
|
1194
|
+
#elif defined(__ANDROID__)
|
|
1195
|
+
|
|
1196
|
+
// startMainLoop() -> void
|
|
1197
|
+
// Arm the ALooper-driven GLib pump on the calling thread (idempotent). There is
|
|
1198
|
+
// no uv-in-GLib bridge to attach alongside it: NativeScript's V8 host has no
|
|
1199
|
+
// libuv loop to co-pump, so the pump is the whole bridge here — which also means
|
|
1200
|
+
// a blocking GLib.MainLoop.run() has no co-pump to keep the host alive (see the
|
|
1201
|
+
// ALooper half above).
|
|
1202
|
+
Napi::Value StartMainLoop(const Napi::CallbackInfo& info) {
|
|
1203
|
+
Napi::Env env = info.Env();
|
|
1204
|
+
if (g_loop_started) return env.Undefined();
|
|
1205
|
+
|
|
1206
|
+
// The looper of the thread that called in — whichever JS thread that is. The
|
|
1207
|
+
// UI thread always has one, and so does a NativeScript Worker (its Java side
|
|
1208
|
+
// runs Looper.prepare() + Looper.loop() around the runtime, see the host's
|
|
1209
|
+
// WorkerWrapper). A Worker is in fact the better owner for GTK: GTK's Android
|
|
1210
|
+
// glue blocks the UI thread on a default-GMainContext idle (GlibContext
|
|
1211
|
+
// .blockForMain), which only returns if some OTHER thread iterates that
|
|
1212
|
+
// context. A null answer means the host called from a bare pthread that never
|
|
1213
|
+
// prepared a looper, and there is then nothing for GLib to be pumped by.
|
|
1214
|
+
ALooper* looper = ALooper_forThread();
|
|
1215
|
+
if (looper == nullptr) {
|
|
1216
|
+
Napi::Error::New(env,
|
|
1217
|
+
"no ALooper on this thread: the GLib pump needs a looper-backed "
|
|
1218
|
+
"thread (call startMainLoop from the JS thread)")
|
|
1219
|
+
.ThrowAsJavaScriptException();
|
|
1220
|
+
return env.Undefined();
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
g_loop_env = env;
|
|
1224
|
+
PumpInit(env, looper);
|
|
1225
|
+
|
|
1226
|
+
g_loop_started = TRUE;
|
|
1227
|
+
return env.Undefined();
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
#endif // NODE_GI_HAS_LIBUV / __ANDROID__ (startMainLoop)
|
|
1231
|
+
|
|
895
1232
|
} // namespace nodegi
|