node-gtk 4.1.0 → 4.1.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 +1 -0
- package/package.json +1 -1
- package/src/callback.cc +13 -9
- package/src/gi.cc +4 -0
- package/src/gi.h +6 -0
- package/src/gobject.cc +53 -23
- package/src/gobject.h +5 -0
- package/src/toggle_queue.cc +44 -0
- package/src/toggle_queue.h +53 -0
- package/src/value.cc +12 -3
package/binding.gyp
CHANGED
package/package.json
CHANGED
package/src/callback.cc
CHANGED
|
@@ -23,6 +23,9 @@ namespace GNodeJS {
|
|
|
23
23
|
|
|
24
24
|
static guint callbackLevel = 0;
|
|
25
25
|
static GSList* notifiedCallbacks = NULL;
|
|
26
|
+
/* DestroyNotify can fire on any thread (e.g. a GTask freeing its callback
|
|
27
|
+
* data on a pool thread); the list must not race the JS thread's AsyncFree. */
|
|
28
|
+
static GMutex notifiedCallbacksLock;
|
|
26
29
|
|
|
27
30
|
static Local<Object> GetSelfInstance(GIArgument **args) {
|
|
28
31
|
return WrapperFromGObject((GObject *)args[0]->v_pointer).As<Object>();
|
|
@@ -66,26 +69,27 @@ Callback::~Callback() {
|
|
|
66
69
|
* we can't free the resources here. They'll be freed in Callback::AsyncFree.
|
|
67
70
|
*/
|
|
68
71
|
void Callback::DestroyNotify (void* user_data) {
|
|
72
|
+
g_mutex_lock (¬ifiedCallbacksLock);
|
|
69
73
|
notifiedCallbacks = g_slist_prepend (notifiedCallbacks, user_data);
|
|
74
|
+
g_mutex_unlock (¬ifiedCallbacksLock);
|
|
70
75
|
}
|
|
71
76
|
|
|
72
77
|
/**
|
|
73
78
|
* Frees the callbacks that have been destroy-notified
|
|
74
79
|
*/
|
|
75
80
|
void Callback::AsyncFree () {
|
|
76
|
-
if (
|
|
81
|
+
if (callbackLevel > 0)
|
|
77
82
|
return;
|
|
78
83
|
|
|
79
|
-
|
|
84
|
+
g_mutex_lock (¬ifiedCallbacksLock);
|
|
85
|
+
GSList* list = notifiedCallbacks;
|
|
86
|
+
notifiedCallbacks = NULL;
|
|
87
|
+
g_mutex_unlock (¬ifiedCallbacksLock);
|
|
80
88
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
delete callback;
|
|
89
|
+
for (GSList* current = list; current != NULL; current = current->next)
|
|
90
|
+
delete static_cast<Callback*>(current->data);
|
|
84
91
|
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
notifiedCallbacks = NULL;
|
|
92
|
+
g_slist_free (list);
|
|
89
93
|
}
|
|
90
94
|
|
|
91
95
|
/**
|
package/src/gi.cc
CHANGED
|
@@ -32,6 +32,8 @@ namespace GNodeJS {
|
|
|
32
32
|
|
|
33
33
|
Nan::Persistent<Object> moduleCache(Nan::New<Object>());
|
|
34
34
|
|
|
35
|
+
GThread *js_thread = NULL;
|
|
36
|
+
|
|
35
37
|
Local<Object> GetModuleCache() {
|
|
36
38
|
return Nan::New<Object>(GNodeJS::moduleCache);
|
|
37
39
|
}
|
|
@@ -526,6 +528,8 @@ NAN_METHOD(CallVFunc) {
|
|
|
526
528
|
}
|
|
527
529
|
|
|
528
530
|
void InitModule(Local<Object> exports, Local<Value> module, void *priv) {
|
|
531
|
+
GNodeJS::js_thread = g_thread_self();
|
|
532
|
+
|
|
529
533
|
GNodeJS::AsyncCallEnvironment::Initialize();
|
|
530
534
|
|
|
531
535
|
Nan::Export(exports, "Bootstrap", Bootstrap);
|
package/src/gi.h
CHANGED
|
@@ -24,6 +24,12 @@ namespace GNodeJS {
|
|
|
24
24
|
|
|
25
25
|
extern Nan::Persistent<Object> moduleCache;
|
|
26
26
|
|
|
27
|
+
/*
|
|
28
|
+
* The thread running V8/libuv (recorded at module init). V8 objects — global
|
|
29
|
+
* handles in particular — may only be touched from this thread.
|
|
30
|
+
*/
|
|
31
|
+
extern GThread *js_thread;
|
|
32
|
+
|
|
27
33
|
Local<Object> GetModuleCache();
|
|
28
34
|
|
|
29
35
|
/*
|
package/src/gobject.cc
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
#include "gi.h"
|
|
12
12
|
#include "gobject.h"
|
|
13
13
|
#include "macros.h"
|
|
14
|
+
#include "toggle_queue.h"
|
|
14
15
|
#include "util.h"
|
|
15
16
|
#include "value.h"
|
|
16
17
|
|
|
@@ -133,10 +134,28 @@ struct GObjectWrapper {
|
|
|
133
134
|
bool collected = false;
|
|
134
135
|
};
|
|
135
136
|
|
|
136
|
-
|
|
137
|
+
/* Reconcile the wrapper's persistent with the state the object's current
|
|
138
|
+
* refcount calls for. Main thread only — called inline by ToggleNotify on the
|
|
139
|
+
* JS thread, and by toggleQueue's drain for deferred off-thread notifications.
|
|
140
|
+
*
|
|
141
|
+
* Weak when the toggle ref is the only reference left: we are the last
|
|
142
|
+
* holder, so the wrapper may be collected. The two-pass weak callback's first
|
|
143
|
+
* pass runs *during* GC (before any JS/GTK code resumes) and only flips a
|
|
144
|
+
* flag, so WrapperFromGObject can tell a reclaimed wrapper from a live one
|
|
145
|
+
* and never marshals a dead handle to JS. All GObject teardown happens in the
|
|
146
|
+
* second pass — a first-pass callback may not call into GObject.
|
|
147
|
+
*
|
|
148
|
+
* Strong otherwise: something other than us holds the object, so the wrapper
|
|
149
|
+
* must stay alive until that ref is dropped again. Reviving is essential —
|
|
150
|
+
* without it a wrapper that went weak once (e.g. a freshly constructed object
|
|
151
|
+
* at refcount 1) would never become strong again when GTK takes ownership,
|
|
152
|
+
* and GC could then collect a wrapper whose GObject is still in use (notably
|
|
153
|
+
* a subclassed widget owned by GTK, losing its overridden vfuncs and instance
|
|
154
|
+
* state). */
|
|
155
|
+
void SynchronizeToggleState(GObject *gobject) {
|
|
137
156
|
void *data = g_object_get_qdata (gobject, GNodeJS::object_quark());
|
|
138
|
-
|
|
139
|
-
|
|
157
|
+
if (data == NULL)
|
|
158
|
+
return;
|
|
140
159
|
|
|
141
160
|
auto *wrapper = (GObjectWrapper *) data;
|
|
142
161
|
|
|
@@ -146,34 +165,37 @@ static void ToggleNotify(gpointer user_data, GObject *gobject, gboolean toggle_d
|
|
|
146
165
|
if (wrapper->collected)
|
|
147
166
|
return;
|
|
148
167
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
return;
|
|
168
|
+
bool only_toggle_ref =
|
|
169
|
+
g_atomic_int_get ((const gint *) &gobject->ref_count) <= 1;
|
|
170
|
+
|
|
171
|
+
if (only_toggle_ref && !wrapper->dying) {
|
|
154
172
|
wrapper->dying = true;
|
|
155
|
-
/* Two-pass weak callback: the first pass runs *during* GC (before any
|
|
156
|
-
* JS/GTK code resumes) and only flips a flag, so WrapperFromGObject can
|
|
157
|
-
* tell a reclaimed wrapper from a live one and never marshals a dead
|
|
158
|
-
* handle to JS. All GObject teardown happens in the second pass — a
|
|
159
|
-
* first-pass callback may not call into GObject. */
|
|
160
173
|
wrapper->persistent.v8::PersistentBase<Object>::SetWeak (
|
|
161
174
|
wrapper, GObjectDestroyedFirstPass, v8::WeakCallbackType::kParameter);
|
|
162
|
-
} else {
|
|
163
|
-
/* We're going from 1 ref to 2 refs: something other than us now holds
|
|
164
|
-
* the object, so the wrapper must stay alive (strong) until that ref is
|
|
165
|
-
* dropped again. Reviving here is essential — without it a wrapper that
|
|
166
|
-
* went weak once (e.g. a freshly constructed object at refcount 1) would
|
|
167
|
-
* never become strong again when GTK takes ownership, and GC could then
|
|
168
|
-
* collect a wrapper whose GObject is still in use (notably a subclassed
|
|
169
|
-
* widget owned by GTK, losing its overridden vfuncs and instance state). */
|
|
170
|
-
if (!wrapper->dying)
|
|
171
|
-
return;
|
|
175
|
+
} else if (!only_toggle_ref && wrapper->dying) {
|
|
172
176
|
wrapper->dying = false;
|
|
173
177
|
wrapper->persistent.ClearWeak ();
|
|
174
178
|
}
|
|
175
179
|
}
|
|
176
180
|
|
|
181
|
+
/* Fires synchronously on whatever thread crosses the 1<->2 refcount boundary
|
|
182
|
+
* — e.g. GLib's worker thread dropping the ref it held on a GSubprocess while
|
|
183
|
+
* waiting for the child to exit. V8 global handles may only be touched from
|
|
184
|
+
* the JS thread: a SetWeak/ClearWeak from a worker raced the scavenger's
|
|
185
|
+
* weak-handle processing and corrupted the global-handle list (fatal "Check
|
|
186
|
+
* failed: Heap::InFromPage(heap_object)" in a later scavenge, or random
|
|
187
|
+
* SIGSEGV). Off-thread notifications are deferred to the main context; the
|
|
188
|
+
* notified direction is not forwarded because the deferred reconciliation
|
|
189
|
+
* re-derives it from the refcount (see toggle_queue.h). */
|
|
190
|
+
static void ToggleNotify(gpointer user_data, GObject *gobject, gboolean toggle_down) {
|
|
191
|
+
if (G_UNLIKELY (g_thread_self () != GNodeJS::js_thread)) {
|
|
192
|
+
toggleQueue.Synchronize (gobject);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
SynchronizeToggleState (gobject);
|
|
197
|
+
}
|
|
198
|
+
|
|
177
199
|
static void AssociateGObject(Local<Object> object, GObject *gobject, GType gtype) {
|
|
178
200
|
Nan::SetInternalFieldPointer(object, 0, gobject);
|
|
179
201
|
|
|
@@ -202,6 +224,9 @@ static void AssociateGObject(Local<Object> object, GObject *gobject, GType gtype
|
|
|
202
224
|
static void GObjectFinalized(gpointer data, GObject *where_the_object_was) {
|
|
203
225
|
auto *wrapper = (GObjectWrapper *) data;
|
|
204
226
|
wrapper->gobject = NULL;
|
|
227
|
+
/* A deferred off-thread toggle may still be queued for this object; the
|
|
228
|
+
* drain must not touch freed memory. */
|
|
229
|
+
toggleQueue.Cancel (where_the_object_was);
|
|
205
230
|
}
|
|
206
231
|
|
|
207
232
|
static void GObjectConstructor(const FunctionCallbackInfo<Value> &info) {
|
|
@@ -308,6 +333,11 @@ static gboolean GObjectTeardownIdle(gpointer data) {
|
|
|
308
333
|
/* If the GObject was already finalized out from under us, GObjectFinalized
|
|
309
334
|
* cleared the pointer; there is nothing left to detach or unref. */
|
|
310
335
|
if (gobject != NULL) {
|
|
336
|
+
/* The weak ref that would cancel a queued off-thread toggle on
|
|
337
|
+
* finalize is removed below, so cancel any pending entry now — the
|
|
338
|
+
* toggle ref drop at the end may be the object's last reference. */
|
|
339
|
+
toggleQueue.Cancel (gobject);
|
|
340
|
+
|
|
311
341
|
/* Drop the weak ref first so removing the toggle ref (which may finalize
|
|
312
342
|
* the object) doesn't re-enter GObjectFinalized. */
|
|
313
343
|
g_object_weak_unref (gobject, GObjectFinalized, wrapper);
|
package/src/gobject.h
CHANGED
|
@@ -18,6 +18,11 @@ namespace GNodeJS {
|
|
|
18
18
|
MaybeLocal<Function> MakeClass (GIBaseInfo *info);
|
|
19
19
|
Local<Value> WrapperFromGObject (GObject *object);
|
|
20
20
|
GObject * GObjectFromWrapper (Local<Value> value);
|
|
21
|
+
|
|
22
|
+
/* Reconcile the wrapper's V8 persistent (weak or strong) with the GObject's
|
|
23
|
+
* current refcount. Main thread only; called inline by ToggleNotify on the JS
|
|
24
|
+
* thread and by toggleQueue's drain for deferred off-thread notifications. */
|
|
25
|
+
void SynchronizeToggleState (GObject *gobject);
|
|
21
26
|
Local<Value> GetSignalHandler (GObject *gobject, guint index);
|
|
22
27
|
Local<FunctionTemplate> GetBaseClassTemplate ();
|
|
23
28
|
MaybeLocal<Value> GetGObjectProperty (GObject * gobject, const char *prop_name);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
|
|
2
|
+
#include "toggle_queue.h"
|
|
3
|
+
#include "gobject.h"
|
|
4
|
+
|
|
5
|
+
namespace GNodeJS {
|
|
6
|
+
|
|
7
|
+
ToggleQueue toggleQueue;
|
|
8
|
+
|
|
9
|
+
void ToggleQueue::Synchronize (GObject *gobject) {
|
|
10
|
+
g_mutex_lock (&lock);
|
|
11
|
+
if (g_slist_find (objects, gobject) == NULL)
|
|
12
|
+
objects = g_slist_prepend (objects, gobject);
|
|
13
|
+
if (source == 0)
|
|
14
|
+
source = g_idle_add (ToggleQueue::Drain, this);
|
|
15
|
+
g_mutex_unlock (&lock);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
void ToggleQueue::Cancel (GObject *gobject) {
|
|
19
|
+
g_mutex_lock (&lock);
|
|
20
|
+
objects = g_slist_remove (objects, gobject);
|
|
21
|
+
g_mutex_unlock (&lock);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* Runs on the main context. The lock is held across the drain so a (buggy,
|
|
25
|
+
* over-unreffed) off-thread finalize cancelling its entry cannot race the
|
|
26
|
+
* entry being processed. SynchronizeToggleState never re-enters the queue,
|
|
27
|
+
* so this cannot deadlock. */
|
|
28
|
+
gboolean ToggleQueue::Drain (gpointer user_data) {
|
|
29
|
+
auto *self = static_cast<ToggleQueue *> (user_data);
|
|
30
|
+
|
|
31
|
+
g_mutex_lock (&self->lock);
|
|
32
|
+
GSList *objects = self->objects;
|
|
33
|
+
self->objects = NULL;
|
|
34
|
+
self->source = 0;
|
|
35
|
+
|
|
36
|
+
for (GSList *l = objects; l != NULL; l = l->next)
|
|
37
|
+
SynchronizeToggleState ((GObject *) l->data);
|
|
38
|
+
|
|
39
|
+
g_slist_free (objects);
|
|
40
|
+
g_mutex_unlock (&self->lock);
|
|
41
|
+
return G_SOURCE_REMOVE;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
}; /* GNodeJS */
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
|
|
2
|
+
#pragma once
|
|
3
|
+
|
|
4
|
+
#include <glib-object.h>
|
|
5
|
+
|
|
6
|
+
namespace GNodeJS {
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
* Defers toggle-state synchronization to the main context.
|
|
10
|
+
*
|
|
11
|
+
* "Synchronize" is meant as reconcile — bring the wrapper's V8 persistent
|
|
12
|
+
* (weak or strong) in line with the GObject's current refcount — not as
|
|
13
|
+
* synchronous execution: requests may come from any thread, and the
|
|
14
|
+
* reconciliation itself always runs later, from an idle on the main context.
|
|
15
|
+
*
|
|
16
|
+
* A toggle notification runs on whatever thread crosses the 1<->2 refcount
|
|
17
|
+
* boundary, but V8 global handles may only be touched from the JS thread
|
|
18
|
+
* (see ToggleNotify in gobject.cc). Off-thread notifications land here.
|
|
19
|
+
*
|
|
20
|
+
* The queue stores no direction: by the time the idle runs, the notified
|
|
21
|
+
* direction is stale (the refcount may have crossed the boundary again, in
|
|
22
|
+
* either order). Each drained object is instead reconciled from its current
|
|
23
|
+
* refcount (SynchronizeToggleState), which makes the deferral idempotent and
|
|
24
|
+
* ordering-insensitive.
|
|
25
|
+
*
|
|
26
|
+
* Entries hold no reference (taking one from inside a toggle handler would
|
|
27
|
+
* recursively fire the opposite toggle). A live wrapper's toggle ref keeps
|
|
28
|
+
* the object's refcount >= 1, so a queued object cannot be finalized under
|
|
29
|
+
* normal operation; as a safety net the GObjectFinalized weak callback and
|
|
30
|
+
* the wrapper teardown Cancel() any pending entry.
|
|
31
|
+
*/
|
|
32
|
+
class ToggleQueue {
|
|
33
|
+
public:
|
|
34
|
+
/* Request a deferred synchronization of the object's toggle state.
|
|
35
|
+
* Callable from any thread. */
|
|
36
|
+
void Synchronize (GObject *gobject);
|
|
37
|
+
|
|
38
|
+
/* Drop the object's pending synchronization, if any. Callable from any
|
|
39
|
+
* thread. Must be called before the object can be finalized, so the
|
|
40
|
+
* drain never touches freed memory. */
|
|
41
|
+
void Cancel (GObject *gobject);
|
|
42
|
+
|
|
43
|
+
private:
|
|
44
|
+
static gboolean Drain (gpointer user_data);
|
|
45
|
+
|
|
46
|
+
GMutex lock;
|
|
47
|
+
GSList *objects;
|
|
48
|
+
guint source;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
extern ToggleQueue toggleQueue;
|
|
52
|
+
|
|
53
|
+
}; /* GNodeJS */
|
package/src/value.cc
CHANGED
|
@@ -1293,15 +1293,24 @@ void FreeGIArgument(GITypeInfo *type_info, GIArgument *arg, GITransfer transfer,
|
|
|
1293
1293
|
if (free_elements) {
|
|
1294
1294
|
GITypeInfo *element_info = g_type_info_get_param_type(type_info, 0);
|
|
1295
1295
|
|
|
1296
|
-
|
|
1297
|
-
|
|
1296
|
+
/* Free elements in the direction they were allocated: an
|
|
1297
|
+
* OUT/EVERYTHING list transferred us one reference per element,
|
|
1298
|
+
* released here (the wrappers hold their own). An IN list was
|
|
1299
|
+
* built by V8ToGIArgument, which allocates strings but borrows
|
|
1300
|
+
* GObject/boxed pointers straight from their wrappers — freeing
|
|
1301
|
+
* with IN/NOTHING releases the strings and leaves the borrowed
|
|
1302
|
+
* pointers alone (mirrors FreeGIArgumentArray). Unreffing IN
|
|
1303
|
+
* elements stole a reference per element from their real owners
|
|
1304
|
+
* (e.g. Gdk.FileList.newFromList: the GFiles were finalized while
|
|
1305
|
+
* the GdkFileList's deep copy still pointed at them). */
|
|
1306
|
+
GITransfer element_transfer = is_in ? GI_TRANSFER_NOTHING : GI_TRANSFER_EVERYTHING;
|
|
1298
1307
|
GIArgument element_arg;
|
|
1299
1308
|
|
|
1300
1309
|
GSList* list = (GSList *)arg->v_pointer;
|
|
1301
1310
|
|
|
1302
1311
|
for (; list != NULL; list = list->next) {
|
|
1303
1312
|
element_arg.v_pointer = list->data;
|
|
1304
|
-
FreeGIArgument(element_info, &element_arg, element_transfer,
|
|
1313
|
+
FreeGIArgument(element_info, &element_arg, element_transfer, direction);
|
|
1305
1314
|
}
|
|
1306
1315
|
|
|
1307
1316
|
g_base_info_unref(element_info);
|