@gjsify/worker_threads 0.3.12 → 0.3.14
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/lib/esm/index.js +103 -121
- package/lib/esm/message-port.js +121 -116
- package/lib/esm/worker.js +214 -226
- package/package.json +7 -7
package/lib/esm/index.js
CHANGED
|
@@ -1,138 +1,120 @@
|
|
|
1
1
|
import { MessagePort } from "./message-port.js";
|
|
2
2
|
import { Worker } from "./worker.js";
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
|
|
4
|
+
//#region src/index.ts
|
|
5
5
|
const _ctx = globalThis.__gjsify_worker_context;
|
|
6
6
|
const isMainThread = !_ctx;
|
|
7
7
|
const parentPort = _ctx?.parentPort ?? null;
|
|
8
8
|
const workerData = _ctx?.workerData ?? null;
|
|
9
9
|
const threadId = _ctx?.threadId ?? 0;
|
|
10
10
|
const resourceLimits = {};
|
|
11
|
-
const SHARE_ENV =
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
const _broadcastRegistry =
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
const _environmentData =
|
|
11
|
+
const SHARE_ENV = Symbol("worker_threads.SHARE_ENV");
|
|
12
|
+
var MessageChannel = class {
|
|
13
|
+
port1;
|
|
14
|
+
port2;
|
|
15
|
+
constructor() {
|
|
16
|
+
this.port1 = new MessagePort();
|
|
17
|
+
this.port2 = new MessagePort();
|
|
18
|
+
this.port1._otherPort = this.port2;
|
|
19
|
+
this.port2._otherPort = this.port1;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
const _broadcastRegistry = new Map();
|
|
23
|
+
var BroadcastChannel = class {
|
|
24
|
+
name;
|
|
25
|
+
_closed = false;
|
|
26
|
+
onmessage = null;
|
|
27
|
+
onmessageerror = null;
|
|
28
|
+
constructor(name) {
|
|
29
|
+
this.name = String(name);
|
|
30
|
+
if (!_broadcastRegistry.has(this.name)) {
|
|
31
|
+
_broadcastRegistry.set(this.name, new Set());
|
|
32
|
+
}
|
|
33
|
+
_broadcastRegistry.get(this.name).add(this);
|
|
34
|
+
}
|
|
35
|
+
postMessage(message) {
|
|
36
|
+
if (this._closed) {
|
|
37
|
+
throw new Error("BroadcastChannel is closed");
|
|
38
|
+
}
|
|
39
|
+
const channels = _broadcastRegistry.get(this.name);
|
|
40
|
+
if (!channels) return;
|
|
41
|
+
for (const channel of channels) {
|
|
42
|
+
if (channel !== this && !channel._closed) {
|
|
43
|
+
const cloned = structuredClone(message);
|
|
44
|
+
Promise.resolve().then(() => {
|
|
45
|
+
if (!channel._closed && channel.onmessage) {
|
|
46
|
+
channel.onmessage({ data: cloned });
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
close() {
|
|
53
|
+
if (this._closed) return;
|
|
54
|
+
this._closed = true;
|
|
55
|
+
const channels = _broadcastRegistry.get(this.name);
|
|
56
|
+
if (channels) {
|
|
57
|
+
channels.delete(this);
|
|
58
|
+
if (channels.size === 0) {
|
|
59
|
+
_broadcastRegistry.delete(this.name);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
this.onmessage = null;
|
|
63
|
+
this.onmessageerror = null;
|
|
64
|
+
}
|
|
65
|
+
addEventListener(type, listener) {
|
|
66
|
+
if (type === "message") {
|
|
67
|
+
this.onmessage = listener;
|
|
68
|
+
} else if (type === "messageerror") {
|
|
69
|
+
this.onmessageerror = listener;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
removeEventListener(type, _listener) {
|
|
73
|
+
if (type === "message") this.onmessage = null;
|
|
74
|
+
else if (type === "messageerror") this.onmessageerror = null;
|
|
75
|
+
}
|
|
76
|
+
dispatchEvent(_event) {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
const _environmentData = new Map();
|
|
81
81
|
function setEnvironmentData(key, value) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
82
|
+
if (value === undefined) {
|
|
83
|
+
_environmentData.delete(key);
|
|
84
|
+
} else {
|
|
85
|
+
_environmentData.set(key, value);
|
|
86
|
+
}
|
|
87
87
|
}
|
|
88
88
|
function getEnvironmentData(key) {
|
|
89
|
-
|
|
89
|
+
return _environmentData.get(key);
|
|
90
90
|
}
|
|
91
91
|
function receiveMessageOnPort(port) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
function markAsUntransferable(_object) {
|
|
96
|
-
}
|
|
97
|
-
function markAsUncloneable(_object) {
|
|
92
|
+
if (!port._hasQueuedMessages) return undefined;
|
|
93
|
+
return { message: port._dequeueMessage() };
|
|
98
94
|
}
|
|
95
|
+
function markAsUntransferable(_object) {}
|
|
96
|
+
function markAsUncloneable(_object) {}
|
|
99
97
|
function moveMessagePortToContext(port, _context) {
|
|
100
|
-
|
|
98
|
+
return port;
|
|
101
99
|
}
|
|
102
|
-
var
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
};
|
|
120
|
-
export {
|
|
121
|
-
BroadcastChannel,
|
|
122
|
-
MessageChannel,
|
|
123
|
-
MessagePort,
|
|
124
|
-
SHARE_ENV,
|
|
125
|
-
Worker,
|
|
126
|
-
index_default as default,
|
|
127
|
-
getEnvironmentData,
|
|
128
|
-
isMainThread,
|
|
129
|
-
markAsUncloneable,
|
|
130
|
-
markAsUntransferable,
|
|
131
|
-
moveMessagePortToContext,
|
|
132
|
-
parentPort,
|
|
133
|
-
receiveMessageOnPort,
|
|
134
|
-
resourceLimits,
|
|
135
|
-
setEnvironmentData,
|
|
136
|
-
threadId,
|
|
137
|
-
workerData
|
|
100
|
+
var src_default = {
|
|
101
|
+
isMainThread,
|
|
102
|
+
parentPort,
|
|
103
|
+
workerData,
|
|
104
|
+
threadId,
|
|
105
|
+
resourceLimits,
|
|
106
|
+
SHARE_ENV,
|
|
107
|
+
Worker,
|
|
108
|
+
MessageChannel,
|
|
109
|
+
MessagePort,
|
|
110
|
+
BroadcastChannel,
|
|
111
|
+
setEnvironmentData,
|
|
112
|
+
getEnvironmentData,
|
|
113
|
+
receiveMessageOnPort,
|
|
114
|
+
markAsUntransferable,
|
|
115
|
+
markAsUncloneable,
|
|
116
|
+
moveMessagePortToContext
|
|
138
117
|
};
|
|
118
|
+
|
|
119
|
+
//#endregion
|
|
120
|
+
export { BroadcastChannel, MessageChannel, MessagePort, SHARE_ENV, Worker, src_default as default, getEnvironmentData, isMainThread, markAsUncloneable, markAsUntransferable, moveMessagePortToContext, parentPort, receiveMessageOnPort, resourceLimits, setEnvironmentData, threadId, workerData };
|
package/lib/esm/message-port.js
CHANGED
|
@@ -1,118 +1,123 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
2
|
+
|
|
3
|
+
//#region src/message-port.ts
|
|
4
|
+
var MessagePort = class extends EventEmitter {
|
|
5
|
+
_started = false;
|
|
6
|
+
_closed = false;
|
|
7
|
+
_messageQueue = [];
|
|
8
|
+
/** @internal Linked port for in-process communication */
|
|
9
|
+
_otherPort = null;
|
|
10
|
+
/** @internal Maps addEventListener listeners to their internal wrappers */
|
|
11
|
+
_aeWrappers = new Map();
|
|
12
|
+
start() {
|
|
13
|
+
if (this._started || this._closed) return;
|
|
14
|
+
this._started = true;
|
|
15
|
+
this._drainQueue();
|
|
16
|
+
}
|
|
17
|
+
close() {
|
|
18
|
+
if (this._closed) return;
|
|
19
|
+
this._closed = true;
|
|
20
|
+
const other = this._otherPort;
|
|
21
|
+
this._otherPort = null;
|
|
22
|
+
if (other) other._otherPort = null;
|
|
23
|
+
this.emit("close");
|
|
24
|
+
this.removeAllListeners();
|
|
25
|
+
}
|
|
26
|
+
postMessage(value, _transferList) {
|
|
27
|
+
if (this._closed) return;
|
|
28
|
+
const target = this._otherPort;
|
|
29
|
+
if (!target) return;
|
|
30
|
+
let cloned;
|
|
31
|
+
try {
|
|
32
|
+
cloned = structuredClone(value);
|
|
33
|
+
} catch (err) {
|
|
34
|
+
this.emit("messageerror", err instanceof Error ? err : new Error("Could not clone message"));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
target._receiveMessage(cloned);
|
|
38
|
+
}
|
|
39
|
+
ref() {
|
|
40
|
+
return this;
|
|
41
|
+
}
|
|
42
|
+
unref() {
|
|
43
|
+
return this;
|
|
44
|
+
}
|
|
45
|
+
_receiveMessage(message) {
|
|
46
|
+
if (this._closed) return;
|
|
47
|
+
if (!this._started) {
|
|
48
|
+
this._messageQueue.push(message);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
this._dispatchMessage(message);
|
|
52
|
+
}
|
|
53
|
+
get _hasQueuedMessages() {
|
|
54
|
+
return this._messageQueue.length > 0;
|
|
55
|
+
}
|
|
56
|
+
_dequeueMessage() {
|
|
57
|
+
return this._messageQueue.shift();
|
|
58
|
+
}
|
|
59
|
+
_drainQueue() {
|
|
60
|
+
while (this._messageQueue.length > 0) {
|
|
61
|
+
this._dispatchMessage(this._messageQueue.shift());
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
_dispatchMessage(message) {
|
|
65
|
+
Promise.resolve().then(() => {
|
|
66
|
+
if (!this._closed) {
|
|
67
|
+
this.emit("message", message);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Web-compatible addEventListener. Wraps message data in a MessageEvent-like
|
|
73
|
+
* object `{ data, type }` before calling the listener.
|
|
74
|
+
* Requires explicit `port.start()` call (unlike `on('message')` which auto-starts).
|
|
75
|
+
*/
|
|
76
|
+
addEventListener(type, listener) {
|
|
77
|
+
if (!listener) return;
|
|
78
|
+
if (type === "message") {
|
|
79
|
+
const wrapper = (data) => {
|
|
80
|
+
listener({
|
|
81
|
+
data,
|
|
82
|
+
type: "message"
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
this._aeWrappers.set(listener, wrapper);
|
|
86
|
+
super.on("message", wrapper);
|
|
87
|
+
} else {
|
|
88
|
+
super.on(type, listener);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
removeEventListener(type, listener) {
|
|
92
|
+
if (!listener) return;
|
|
93
|
+
if (type === "message") {
|
|
94
|
+
const wrapper = this._aeWrappers.get(listener);
|
|
95
|
+
if (wrapper) {
|
|
96
|
+
super.off("message", wrapper);
|
|
97
|
+
this._aeWrappers.delete(listener);
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
super.off(type, listener);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
on(event, listener) {
|
|
104
|
+
super.on(event, listener);
|
|
105
|
+
if (event === "message" && !this._started) {
|
|
106
|
+
this.start();
|
|
107
|
+
}
|
|
108
|
+
return this;
|
|
109
|
+
}
|
|
110
|
+
addListener(event, listener) {
|
|
111
|
+
return this.on(event, listener);
|
|
112
|
+
}
|
|
113
|
+
once(event, listener) {
|
|
114
|
+
super.once(event, listener);
|
|
115
|
+
if (event === "message" && !this._started) {
|
|
116
|
+
this.start();
|
|
117
|
+
}
|
|
118
|
+
return this;
|
|
119
|
+
}
|
|
118
120
|
};
|
|
121
|
+
|
|
122
|
+
//#endregion
|
|
123
|
+
export { MessagePort };
|
package/lib/esm/worker.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
1
2
|
import Gio from "@girs/gio-2.0";
|
|
2
3
|
import GLib from "@girs/glib-2.0";
|
|
3
|
-
|
|
4
|
+
|
|
5
|
+
//#region src/worker.ts
|
|
4
6
|
let _nextThreadId = 1;
|
|
5
7
|
const _encoder = new TextEncoder();
|
|
6
|
-
const BOOTSTRAP_CODE =
|
|
8
|
+
const BOOTSTRAP_CODE = `\
|
|
9
|
+
import GLib from 'gi://GLib';
|
|
7
10
|
import Gio from 'gi://Gio';
|
|
8
11
|
|
|
9
12
|
const loop = new GLib.MainLoop(null, false);
|
|
@@ -88,228 +91,213 @@ try {
|
|
|
88
91
|
loop.run();
|
|
89
92
|
`;
|
|
90
93
|
const BOOTSTRAP_BYTES = _encoder.encode(BOOTSTRAP_CODE);
|
|
91
|
-
class Worker extends EventEmitter {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
try {
|
|
298
|
-
this._bootstrapFile.delete(null);
|
|
299
|
-
} catch {
|
|
300
|
-
}
|
|
301
|
-
this._bootstrapFile = null;
|
|
302
|
-
}
|
|
303
|
-
if (this._stdinPipe) {
|
|
304
|
-
try {
|
|
305
|
-
this._stdinPipe.close(null);
|
|
306
|
-
} catch {
|
|
307
|
-
}
|
|
308
|
-
this._stdinPipe = null;
|
|
309
|
-
}
|
|
310
|
-
this._subprocess = null;
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
export {
|
|
314
|
-
Worker
|
|
94
|
+
var Worker = class Worker extends EventEmitter {
|
|
95
|
+
threadId;
|
|
96
|
+
resourceLimits;
|
|
97
|
+
_subprocess = null;
|
|
98
|
+
_stdinPipe = null;
|
|
99
|
+
_exited = false;
|
|
100
|
+
_bootstrapFile = null;
|
|
101
|
+
constructor(filename, options) {
|
|
102
|
+
super();
|
|
103
|
+
this.threadId = _nextThreadId++;
|
|
104
|
+
this.resourceLimits = options?.resourceLimits || {};
|
|
105
|
+
const isEval = options?.eval === true;
|
|
106
|
+
const resolvedFilename = Worker._resolveFilename(filename, isEval);
|
|
107
|
+
const tmpDir = GLib.get_tmp_dir();
|
|
108
|
+
const bootstrapPath = `${tmpDir}/gjsify-worker-${this.threadId}-${Date.now()}.mjs`;
|
|
109
|
+
this._bootstrapFile = Gio.File.new_for_path(bootstrapPath);
|
|
110
|
+
try {
|
|
111
|
+
this._bootstrapFile.replace_contents(BOOTSTRAP_BYTES, null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, null);
|
|
112
|
+
} catch (err) {
|
|
113
|
+
throw new Error(`Failed to create worker bootstrap: ${err instanceof Error ? err.message : err}`);
|
|
114
|
+
}
|
|
115
|
+
const launcher = new Gio.SubprocessLauncher({ flags: Gio.SubprocessFlags.STDIN_PIPE | Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_PIPE });
|
|
116
|
+
if (options?.env && typeof options.env === "object") {
|
|
117
|
+
for (const [key, value] of Object.entries(options.env)) {
|
|
118
|
+
launcher.setenv(key, String(value), true);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
this._subprocess = launcher.spawnv([
|
|
123
|
+
"gjs",
|
|
124
|
+
"-m",
|
|
125
|
+
bootstrapPath
|
|
126
|
+
]);
|
|
127
|
+
} catch (err) {
|
|
128
|
+
this._cleanup();
|
|
129
|
+
throw new Error(`Failed to spawn worker: ${err instanceof Error ? err.message : err}`);
|
|
130
|
+
}
|
|
131
|
+
this._stdinPipe = this._subprocess.get_stdin_pipe();
|
|
132
|
+
const stdoutPipe = this._subprocess.get_stdout_pipe();
|
|
133
|
+
if (!isEval) {
|
|
134
|
+
const filePath = resolvedFilename.startsWith("file://") ? resolvedFilename.slice(7) : resolvedFilename;
|
|
135
|
+
const file = Gio.File.new_for_path(filePath);
|
|
136
|
+
if (!file.query_exists(null)) {
|
|
137
|
+
this._cleanup();
|
|
138
|
+
const err = new Error(`Cannot find module '${filePath}'`);
|
|
139
|
+
err.code = "ERR_MODULE_NOT_FOUND";
|
|
140
|
+
Promise.resolve().then(() => {
|
|
141
|
+
this.emit("error", err);
|
|
142
|
+
this._exited = true;
|
|
143
|
+
this.emit("exit", 1);
|
|
144
|
+
});
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const initData = JSON.stringify({
|
|
149
|
+
threadId: this.threadId,
|
|
150
|
+
workerData: options?.workerData ?? null,
|
|
151
|
+
eval: isEval,
|
|
152
|
+
filename: isEval ? undefined : resolvedFilename,
|
|
153
|
+
code: isEval ? resolvedFilename : undefined
|
|
154
|
+
}) + "\n";
|
|
155
|
+
try {
|
|
156
|
+
this._stdinPipe.write_all(_encoder.encode(initData), null);
|
|
157
|
+
} catch (err) {
|
|
158
|
+
this._cleanup();
|
|
159
|
+
throw new Error(`Failed to send init data: ${err instanceof Error ? err.message : err}`);
|
|
160
|
+
}
|
|
161
|
+
if (stdoutPipe) {
|
|
162
|
+
const dataStream = Gio.DataInputStream.new(stdoutPipe);
|
|
163
|
+
this._readMessages(dataStream);
|
|
164
|
+
}
|
|
165
|
+
const stderrPipe = this._subprocess.get_stderr_pipe();
|
|
166
|
+
if (stderrPipe) {
|
|
167
|
+
this._readStderr(Gio.DataInputStream.new(stderrPipe));
|
|
168
|
+
}
|
|
169
|
+
this._subprocess.wait_async(null, () => {
|
|
170
|
+
this._onExit();
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
postMessage(value, _transferList) {
|
|
174
|
+
if (this._exited || !this._stdinPipe) return;
|
|
175
|
+
try {
|
|
176
|
+
const line = JSON.stringify({
|
|
177
|
+
type: "message",
|
|
178
|
+
data: value
|
|
179
|
+
}) + "\n";
|
|
180
|
+
this._stdinPipe.write_all(_encoder.encode(line), null);
|
|
181
|
+
} catch {}
|
|
182
|
+
}
|
|
183
|
+
terminate() {
|
|
184
|
+
if (this._exited) return Promise.resolve(0);
|
|
185
|
+
const exitPromise = new Promise((resolve) => {
|
|
186
|
+
this.once("exit", (code) => resolve(code));
|
|
187
|
+
});
|
|
188
|
+
try {
|
|
189
|
+
if (this._stdinPipe) {
|
|
190
|
+
const msg = JSON.stringify({ type: "terminate" }) + "\n";
|
|
191
|
+
this._stdinPipe.write_all(_encoder.encode(msg), null);
|
|
192
|
+
}
|
|
193
|
+
} catch {}
|
|
194
|
+
setTimeout(() => {
|
|
195
|
+
if (!this._exited && this._subprocess) {
|
|
196
|
+
this._subprocess.force_exit();
|
|
197
|
+
}
|
|
198
|
+
}, 500);
|
|
199
|
+
return exitPromise;
|
|
200
|
+
}
|
|
201
|
+
ref() {
|
|
202
|
+
return this;
|
|
203
|
+
}
|
|
204
|
+
unref() {
|
|
205
|
+
return this;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Resolve a worker filename to an absolute path or file:// URL.
|
|
209
|
+
*
|
|
210
|
+
* - URL instances → href string
|
|
211
|
+
* - file:// URLs → kept as-is
|
|
212
|
+
* - Absolute paths → converted to file:// URL
|
|
213
|
+
* - Relative paths (./foo, ../bar) → resolved relative to cwd, converted to file:// URL
|
|
214
|
+
* - eval mode → returned as-is (it's code, not a path)
|
|
215
|
+
*/
|
|
216
|
+
static _resolveFilename(filename, isEval) {
|
|
217
|
+
if (isEval) return String(filename);
|
|
218
|
+
if (filename instanceof URL) return filename.href;
|
|
219
|
+
const str = String(filename);
|
|
220
|
+
if (str.startsWith("file://") || str.startsWith("http://") || str.startsWith("https://")) {
|
|
221
|
+
return str;
|
|
222
|
+
}
|
|
223
|
+
if (str.startsWith("/")) {
|
|
224
|
+
return "file://" + str;
|
|
225
|
+
}
|
|
226
|
+
if (str.startsWith("./") || str.startsWith("../") || !str.includes("/")) {
|
|
227
|
+
const cwd = GLib.get_current_dir();
|
|
228
|
+
const resolved = GLib.build_filenamev([cwd, str]);
|
|
229
|
+
const file = Gio.File.new_for_path(resolved);
|
|
230
|
+
const canonical = file.get_path();
|
|
231
|
+
return "file://" + (canonical || resolved);
|
|
232
|
+
}
|
|
233
|
+
return "file://" + str;
|
|
234
|
+
}
|
|
235
|
+
_readMessages(dataStream) {
|
|
236
|
+
dataStream.read_line_async(GLib.PRIORITY_DEFAULT, null, (_source, result) => {
|
|
237
|
+
try {
|
|
238
|
+
const [line] = dataStream.read_line_finish_utf8(result);
|
|
239
|
+
if (line === null) return;
|
|
240
|
+
const msg = JSON.parse(line);
|
|
241
|
+
switch (msg.type) {
|
|
242
|
+
case "online":
|
|
243
|
+
this.emit("online");
|
|
244
|
+
break;
|
|
245
|
+
case "message":
|
|
246
|
+
this.emit("message", msg.data);
|
|
247
|
+
break;
|
|
248
|
+
case "error": {
|
|
249
|
+
const err = new Error(msg.message);
|
|
250
|
+
if (msg.stack) err.stack = msg.stack;
|
|
251
|
+
this.emit("error", err);
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
this._readMessages(dataStream);
|
|
256
|
+
} catch {}
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
_stderrChunks = [];
|
|
260
|
+
_readStderr(dataStream) {
|
|
261
|
+
dataStream.read_line_async(GLib.PRIORITY_DEFAULT, null, (_source, result) => {
|
|
262
|
+
try {
|
|
263
|
+
const [line] = dataStream.read_line_finish_utf8(result);
|
|
264
|
+
if (line === null) {
|
|
265
|
+
if (this._stderrChunks.length > 0) {
|
|
266
|
+
const stderrText = this._stderrChunks.join("\n");
|
|
267
|
+
if (this.listenerCount("error") === 0) {
|
|
268
|
+
this.emit("error", new Error(stderrText));
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
this._stderrChunks.push(line);
|
|
274
|
+
this._readStderr(dataStream);
|
|
275
|
+
} catch {}
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
_onExit() {
|
|
279
|
+
if (this._exited) return;
|
|
280
|
+
this._exited = true;
|
|
281
|
+
const exitCode = this._subprocess?.get_if_exited() ? this._subprocess.get_exit_status() : 1;
|
|
282
|
+
this._cleanup();
|
|
283
|
+
this.emit("exit", exitCode);
|
|
284
|
+
}
|
|
285
|
+
_cleanup() {
|
|
286
|
+
if (this._bootstrapFile) {
|
|
287
|
+
try {
|
|
288
|
+
this._bootstrapFile.delete(null);
|
|
289
|
+
} catch {}
|
|
290
|
+
this._bootstrapFile = null;
|
|
291
|
+
}
|
|
292
|
+
if (this._stdinPipe) {
|
|
293
|
+
try {
|
|
294
|
+
this._stdinPipe.close(null);
|
|
295
|
+
} catch {}
|
|
296
|
+
this._stdinPipe = null;
|
|
297
|
+
}
|
|
298
|
+
this._subprocess = null;
|
|
299
|
+
}
|
|
315
300
|
};
|
|
301
|
+
|
|
302
|
+
//#endregion
|
|
303
|
+
export { Worker };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/worker_threads",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.14",
|
|
4
4
|
"description": "Node.js worker_threads module for Gjs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "lib/esm/index.js",
|
|
@@ -30,15 +30,15 @@
|
|
|
30
30
|
"worker_threads"
|
|
31
31
|
],
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"@gjsify/cli": "^0.3.
|
|
34
|
-
"@gjsify/node-globals": "^0.3.
|
|
35
|
-
"@gjsify/unit": "^0.3.
|
|
33
|
+
"@gjsify/cli": "^0.3.14",
|
|
34
|
+
"@gjsify/node-globals": "^0.3.14",
|
|
35
|
+
"@gjsify/unit": "^0.3.14",
|
|
36
36
|
"@types/node": "^25.6.0",
|
|
37
37
|
"typescript": "^6.0.3"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@girs/gio-2.0": "
|
|
41
|
-
"@girs/glib-2.0": "
|
|
42
|
-
"@gjsify/events": "^0.3.
|
|
40
|
+
"@girs/gio-2.0": "2.88.0-4.0.0-rc.9",
|
|
41
|
+
"@girs/glib-2.0": "2.88.0-4.0.0-rc.9",
|
|
42
|
+
"@gjsify/events": "^0.3.14"
|
|
43
43
|
}
|
|
44
44
|
}
|