@gjsify/worker_threads 0.4.11 → 0.4.13
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/message-port.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import"./_virtual/_rolldown/runtime.js";import{
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import{isSharedBuffer as e}from"./sab-transfer.js";import{EventEmitter as t}from"node:events";function isTransferredPortPlaceholder(e){return typeof e==`object`&&!!e&&e.__gjsifyTransferredPort===!0}var n=class MessagePort extends t{_started=!1;_closed=!1;_detached=!1;_messageQueue=[];_otherPort=null;_aeWrappers=new Map;start(){this._started||this._closed||(this._started=!0,this._drainQueue())}close(){if(this._closed)return;this._closed=!0;let e=this._otherPort;this._otherPort=null,e&&(e._otherPort=null),this.emit(`close`),this.removeAllListeners()}postMessage(t,n){if(this._closed)return;let r=this._otherPort;if(!r)return;let i=[],a=[];if(n&&n.length>0){let t=new Set;for(let r of n){if(t.has(r))throw createDataCloneError(`Transfer list contains duplicate entries`);if(t.add(r),r instanceof MessagePort){if(r===this)throw createDataCloneError(`Cannot transfer source port`);if(r._closed||r._detached)throw createDataCloneError(`MessagePort in transfer list is already detached`);a.push(r);continue}let o=Object.prototype.toString.call(r).slice(8,-1);if(o===`ArrayBuffer`){let e=r;if(e.detached===!0)throw createDataCloneError(`ArrayBuffer in transfer list is detached`);i.push(e);continue}if(o===`SharedArrayBuffer`)throw createDataCloneError(`SharedArrayBuffer cannot appear in transfer list (it is shared, not transferred)`);if(!e(r))throw createDataCloneError(`Value at index ${n.indexOf(r)} of transfer list is not transferable`)}}let o=a.slice(),s=t;a.length>0&&(s=substitutePortsWithPlaceholders(t,a));let c;try{c=structuredClone(s,{transfer:i.length>0?i:void 0})}catch(e){this.emit(`messageerror`,e instanceof Error?e:Error(`Could not clone message`));return}let l=c;if(o.length>0){let e=o.map(e=>{let t=e._otherPort,n=e._messageQueue.slice();e._messageQueue.length=0,e._otherPort=null,e._detached=!0,e._closed=!0;let r=new MessagePort;r._otherPort=t,t&&(t._otherPort=r);for(let e of n)r._messageQueue.push(e);return r});l=replacePlaceholdersWithPorts(c,e)}r._receiveMessage(l)}ref(){return this}unref(){return this}_receiveMessage(e){if(!this._closed){if(!this._started){this._messageQueue.push(e);return}this._dispatchMessage(e)}}get _hasQueuedMessages(){return this._messageQueue.length>0}_dequeueMessage(){return this._messageQueue.shift()}get _wasTransferred(){return this._detached}_drainQueue(){for(;this._messageQueue.length>0;)this._dispatchMessage(this._messageQueue.shift())}_dispatchMessage(e){Promise.resolve().then(()=>{this._closed||this.emit(`message`,e)})}addEventListener(e,t){if(t)if(e===`message`){let wrapper=e=>{t({data:e,type:`message`})};this._aeWrappers.set(t,wrapper),super.on(`message`,wrapper)}else super.on(e,t)}removeEventListener(e,t){if(t)if(e===`message`){let e=this._aeWrappers.get(t);e&&(super.off(`message`,e),this._aeWrappers.delete(t))}else super.off(e,t)}on(e,t){return super.on(e,t),e===`message`&&!this._started&&this.start(),this}addListener(e,t){return this.on(e,t)}once(e,t){return super.once(e,t),e===`message`&&!this._started&&this.start(),this}};function createDataCloneError(e){let t=globalThis.DOMException;if(typeof t==`function`){let n=new t(e,`DataCloneError`);if(n.code===void 0)try{Object.defineProperty(n,`code`,{value:25,configurable:!0})}catch{}return n}let n=Error(e);return n.name=`DataCloneError`,n.code=25,n}function substitutePortsWithPlaceholders(e,t){let r=new Map;for(let e=0;e<t.length;e++)r.set(t[e],e);function walk(e,t){if(typeof e!=`object`||!e)return e;if(e instanceof n){let t=r.get(e);return t===void 0?e:{__gjsifyTransferredPort:!0,index:t}}if(t.has(e))return t.get(e);if(Array.isArray(e)){let n=[];t.set(e,n);for(let r=0;r<e.length;r++)r in e&&(n[r]=walk(e[r],t));return n}if(Object.prototype.toString.call(e).slice(8,-1)===`Object`){let n={};t.set(e,n);for(let r of Object.keys(e))n[r]=walk(e[r],t);return n}return e}return walk(e,new Map)}function replacePlaceholdersWithPorts(e,t){function walk(e,n){if(typeof e!=`object`||!e)return e;if(isTransferredPortPlaceholder(e))return t[e.index];if(n.has(e))return n.get(e);if(Array.isArray(e)){n.set(e,e);for(let t=0;t<e.length;t++)t in e&&(e[t]=walk(e[t],n));return e}if(Object.prototype.toString.call(e).slice(8,-1)===`Object`){n.set(e,e);for(let t of Object.keys(e))e[t]=walk(e[t],n);return e}return e}return walk(e,new Map)}export{n as MessagePort};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";function isSharedBufferPlaceholder(e){return typeof e==`object`&&!!e&&typeof e.__sab==`number`&&typeof e.size==`number`}function isSharedBuffer(e){return typeof e==`object`&&!!e&&e.constructor?.name===`SharedBuffer`}function extractSharedBuffers(e,t){let n=[],r=new Map,i=t;function walk(e,t){if(typeof e!=`object`||!e)return e;if(isSharedBuffer(e)){let t=r.get(e);return t===void 0&&(t=i++,r.set(e,t),n.push({tag:t,buffer:e})),{__sab:t,size:e.byteLength}}if(t.has(e))return t.get(e);if(Array.isArray(e)){let n=[];t.set(e,n);for(let r=0;r<e.length;r++)r in e&&(n[r]=walk(e[r],t));return n}if(Object.prototype.toString.call(e).slice(8,-1)===`Object`){let n={};t.set(e,n);for(let r of Object.keys(e))n[r]=walk(e[r],t);return n}return e}return{value:walk(e,new Map),table:n,nextTag:i}}function materializeSharedBuffers(e,t){function walk(e,n){if(typeof e!=`object`||!e)return e;if(isSharedBufferPlaceholder(e))return t(e.__sab,e.size);if(n.has(e))return n.get(e);if(Array.isArray(e)){n.set(e,e);for(let t=0;t<e.length;t++)t in e&&(e[t]=walk(e[t],n));return e}if(Object.prototype.toString.call(e).slice(8,-1)===`Object`){n.set(e,e);for(let t of Object.keys(e))e[t]=walk(e[t],n);return e}return e}return walk(e,new Map)}export{extractSharedBuffers,isSharedBuffer,isSharedBufferPlaceholder,materializeSharedBuffers};
|
package/lib/esm/worker.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import"./_virtual/_rolldown/runtime.js";import{
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import{extractSharedBuffers as e}from"./sab-transfer.js";import{EventEmitter as t}from"node:events";import n from"@girs/gio-2.0";import r from"@girs/glib-2.0";import{fdChannel as i}from"@gjsify/sab-native";let a=1;const o=new TextEncoder,s=o.encode(`import GLib from 'gi://GLib';
|
|
2
2
|
import Gio from 'gi://Gio';
|
|
3
3
|
|
|
4
4
|
const loop = new GLib.MainLoop(null, false);
|
|
@@ -6,15 +6,114 @@ const stdinStream = Gio.UnixInputStream.new(0, false);
|
|
|
6
6
|
const dataIn = Gio.DataInputStream.new(stdinStream);
|
|
7
7
|
const stdoutStream = Gio.UnixOutputStream.new(1, false);
|
|
8
8
|
|
|
9
|
+
const _encoder = new TextEncoder();
|
|
10
|
+
|
|
9
11
|
function send(obj) {
|
|
10
12
|
const line = JSON.stringify(obj) + '\\n';
|
|
11
13
|
stdoutStream.write_all(_encoder.encode(line), null);
|
|
12
14
|
}
|
|
13
15
|
|
|
16
|
+
// Try to load the sab-native typelib synchronously. Absent prebuild
|
|
17
|
+
// is fine — only fails the receive path when a SharedBuffer actually
|
|
18
|
+
// crosses the wire.
|
|
19
|
+
let SabNative = null;
|
|
20
|
+
try { SabNative = imports.gi.GjsifySabNative; } catch (_) { /* prebuild missing */ }
|
|
21
|
+
|
|
14
22
|
// Read init data (first line, blocking)
|
|
15
23
|
const [initLine] = dataIn.read_line_utf8(null);
|
|
16
24
|
const init = JSON.parse(initLine);
|
|
17
25
|
|
|
26
|
+
// fd → SharedBuffer cache keyed by tag. Filled by drainSabFds() right
|
|
27
|
+
// before materialise() walks the parsed JSON.
|
|
28
|
+
const sabFds = new Map();
|
|
29
|
+
|
|
30
|
+
// Count how many SharedBuffer placeholders sit in a parsed JSON value.
|
|
31
|
+
// The exact count is what we pass to drainSabFds().
|
|
32
|
+
function countSabPlaceholders(value) {
|
|
33
|
+
if (value === null || typeof value !== 'object') return 0;
|
|
34
|
+
if (typeof value.__sab === 'number' && typeof value.size === 'number') return 1;
|
|
35
|
+
if (Array.isArray(value)) {
|
|
36
|
+
let n = 0;
|
|
37
|
+
for (const v of value) n += countSabPlaceholders(v);
|
|
38
|
+
return n;
|
|
39
|
+
}
|
|
40
|
+
if (Object.prototype.toString.call(value) === '[object Object]') {
|
|
41
|
+
let n = 0;
|
|
42
|
+
for (const v of Object.values(value)) n += countSabPlaceholders(v);
|
|
43
|
+
return n;
|
|
44
|
+
}
|
|
45
|
+
return 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Synchronously drain exactly @count SCM_RIGHTS messages from fd 3,
|
|
49
|
+
// indexing each received fd by its sender-encoded tag. Called once per
|
|
50
|
+
// incoming JSON message that contains __sab placeholders. recv_fd
|
|
51
|
+
// blocks at most until the kernel has the message — and the sender
|
|
52
|
+
// always sends fds before writing to stdin, so by the time we get
|
|
53
|
+
// here every fd is already buffered.
|
|
54
|
+
function drainSabFds(count) {
|
|
55
|
+
if (!SabNative || count <= 0) return;
|
|
56
|
+
for (let i = 0; i < count; i++) {
|
|
57
|
+
const [fd, tag] = SabNative.FdChannel.recv_fd(3);
|
|
58
|
+
if (fd <= 0) throw new Error('FdChannel.recv_fd returned ' + fd + ' while draining ' + count + ' fds');
|
|
59
|
+
sabFds.set(tag, fd);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function materialise(value) {
|
|
64
|
+
if (value === null || typeof value !== 'object') return value;
|
|
65
|
+
if (typeof value.__sab === 'number' && typeof value.size === 'number') {
|
|
66
|
+
const fd = sabFds.get(value.__sab);
|
|
67
|
+
if (fd === undefined) throw new Error('SharedBuffer placeholder \\'' + value.__sab + '\\' arrived before its fd');
|
|
68
|
+
if (!SabNative) throw new Error('SharedBuffer placeholder arrived but @gjsify/sab-native typelib not loaded');
|
|
69
|
+
const native = SabNative.SharedBuffer.from_fd(fd, value.size);
|
|
70
|
+
sabFds.delete(value.__sab);
|
|
71
|
+
return makeSharedBuffer(native);
|
|
72
|
+
}
|
|
73
|
+
if (Array.isArray(value)) {
|
|
74
|
+
for (let i = 0; i < value.length; i++) value[i] = materialise(value[i]);
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
if (Object.prototype.toString.call(value) === '[object Object]') {
|
|
78
|
+
for (const k of Object.keys(value)) value[k] = materialise(value[k]);
|
|
79
|
+
return value;
|
|
80
|
+
}
|
|
81
|
+
return value;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function makeSharedBuffer(native) {
|
|
85
|
+
return {
|
|
86
|
+
get byteLength() { return native.byte_length; },
|
|
87
|
+
get fd() { return native.fd; },
|
|
88
|
+
getUint8(off) { return native.get_u8(off); },
|
|
89
|
+
setUint8(off, v) { native.set_u8(off, v); },
|
|
90
|
+
getInt32LE(off) { return native.get_i32_le(off); },
|
|
91
|
+
setInt32LE(off, v) { native.set_i32_le(off, v); },
|
|
92
|
+
getUint32LE(off) { return native.get_u32_le(off); },
|
|
93
|
+
setUint32LE(off, v) { native.set_u32_le(off, v); },
|
|
94
|
+
getUint64LE(off) { return native.get_u64_le(off); },
|
|
95
|
+
setUint64LE(off, v) { native.set_u64_le(off, v); },
|
|
96
|
+
readBytes(off, len) {
|
|
97
|
+
const bytes = native.read_bytes(off, len);
|
|
98
|
+
const data = bytes.get_data();
|
|
99
|
+
return data ? new Uint8Array(data) : new Uint8Array(0);
|
|
100
|
+
},
|
|
101
|
+
writeBytes(off, data) { native.write_bytes(off, new GLib.Bytes(data)); },
|
|
102
|
+
get _nativeHandle() { return native; },
|
|
103
|
+
get constructor() { return { name: 'SharedBuffer' }; },
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Drain SharedBuffer fds attached to the init line (workerData may carry
|
|
108
|
+
// SharedBuffer instances) before materialise() runs against it.
|
|
109
|
+
if (init.sabSocketFd === 3) {
|
|
110
|
+
try {
|
|
111
|
+
drainSabFds(countSabPlaceholders(init.workerData));
|
|
112
|
+
} catch (err) {
|
|
113
|
+
send({ type: 'error', message: 'init fd drain failed: ' + (err && err.message ? err.message : err), stack: '' });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
18
117
|
// Simplified EventEmitter for parentPort
|
|
19
118
|
const _listeners = new Map();
|
|
20
119
|
const parentPort = {
|
|
@@ -45,7 +144,7 @@ const parentPort = {
|
|
|
45
144
|
globalThis.__gjsify_worker_context = {
|
|
46
145
|
isMainThread: false,
|
|
47
146
|
parentPort,
|
|
48
|
-
workerData: init.workerData ?? null,
|
|
147
|
+
workerData: materialise(init.workerData ?? null),
|
|
49
148
|
threadId: init.threadId ?? 0,
|
|
50
149
|
};
|
|
51
150
|
|
|
@@ -58,10 +157,20 @@ function readNext() {
|
|
|
58
157
|
const [line] = source.read_line_finish_utf8(result);
|
|
59
158
|
if (line === null) { loop.quit(); return; }
|
|
60
159
|
const msg = JSON.parse(line);
|
|
61
|
-
if (msg.type === 'message')
|
|
160
|
+
if (msg.type === 'message') {
|
|
161
|
+
// Drain fds for any SharedBuffer placeholders BEFORE materialise.
|
|
162
|
+
// recv_fd is synchronous but the sender always wrote the SCM_RIGHTS
|
|
163
|
+
// messages before writing this stdin line, so they're already
|
|
164
|
+
// buffered.
|
|
165
|
+
if (init.sabSocketFd === 3) drainSabFds(countSabPlaceholders(msg.data));
|
|
166
|
+
parentPort.emit('message', materialise(msg.data));
|
|
167
|
+
}
|
|
62
168
|
else if (msg.type === 'terminate') { send({ type: 'exit', code: 1 }); loop.quit(); return; }
|
|
63
169
|
readNext();
|
|
64
|
-
} catch
|
|
170
|
+
} catch (err) {
|
|
171
|
+
send({ type: 'error', message: 'bootstrap message error: ' + (err && err.message ? err.message : err), stack: err && err.stack || '' });
|
|
172
|
+
loop.quit();
|
|
173
|
+
}
|
|
65
174
|
});
|
|
66
175
|
}
|
|
67
176
|
readNext();
|
|
@@ -71,7 +180,7 @@ try {
|
|
|
71
180
|
if (init.eval) {
|
|
72
181
|
const AsyncFn = Object.getPrototypeOf(async function(){}).constructor;
|
|
73
182
|
await new AsyncFn('parentPort', 'workerData', 'threadId', init.code)(
|
|
74
|
-
parentPort,
|
|
183
|
+
parentPort, globalThis.__gjsify_worker_context.workerData, init.threadId
|
|
75
184
|
);
|
|
76
185
|
} else {
|
|
77
186
|
await import(init.filename);
|
|
@@ -81,8 +190,8 @@ try {
|
|
|
81
190
|
}
|
|
82
191
|
|
|
83
192
|
loop.run();
|
|
84
|
-
`);var
|
|
85
|
-
`;try{this._stdinPipe.write_all(
|
|
86
|
-
`;this._stdinPipe.write_all(
|
|
87
|
-
`;this._stdinPipe.write_all(
|
|
88
|
-
`);this.listenerCount(`error`)===0&&this.emit(`error`,Error(e))}return}this._stderrChunks.push(t),this._readStderr(e)}catch{}})}_onExit(){if(this._exited)return;this._exited=!0;let e=this._subprocess?.get_if_exited()?this._subprocess.get_exit_status():1;this._cleanup(),this.emit(`exit`,e)}_cleanup(){if(this._bootstrapFile){try{this._bootstrapFile.delete(null)}catch{}this._bootstrapFile=null}if(this._stdinPipe){try{this._stdinPipe.close(null)}catch{}this._stdinPipe=null}this._subprocess=null}};export{
|
|
193
|
+
`);var c=class Worker extends t{threadId;resourceLimits;_subprocess=null;_stdinPipe=null;_exited=!1;_bootstrapFile=null;_sabSocketFd=-1;_sabNextTag=0;constructor(e,t){super(),this.threadId=a++,this.resourceLimits=t?.resourceLimits||{};let c=t?.eval===!0,l=Worker._resolveFilename(e,c),u=`${r.get_tmp_dir()}/gjsify-worker-${this.threadId}-${Date.now()}.mjs`;this._bootstrapFile=n.File.new_for_path(u);try{this._bootstrapFile.replace_contents(s,null,!1,n.FileCreateFlags.REPLACE_DESTINATION,null)}catch(e){throw Error(`Failed to create worker bootstrap: ${e instanceof Error?e.message:e}`)}let d=new n.SubprocessLauncher({flags:n.SubprocessFlags.STDIN_PIPE|n.SubprocessFlags.STDOUT_PIPE|n.SubprocessFlags.STDERR_PIPE});if(t?.env&&typeof t.env==`object`)for(let[e,n]of Object.entries(t.env))d.setenv(e,String(n),!0);let f=-1;if(i){let e=i.makePair();e&&(this._sabSocketFd=e.parentFd,f=e.childFd,d.take_fd(f,3))}try{this._subprocess=d.spawnv([`gjs`,`-m`,u])}catch(e){throw this._cleanup(),Error(`Failed to spawn worker: ${e instanceof Error?e.message:e}`)}this._stdinPipe=this._subprocess.get_stdin_pipe();let p=this._subprocess.get_stdout_pipe();if(!c){let e=l.startsWith(`file://`)?l.slice(7):l;if(!n.File.new_for_path(e).query_exists(null)){this._cleanup();let t=Error(`Cannot find module '${e}'`);t.code=`ERR_MODULE_NOT_FOUND`,Promise.resolve().then(()=>{this.emit(`error`,t),this._exited=!0,this.emit(`exit`,1)});return}}let m=this._serializeWithSabTransfer(t?.workerData??null),h=JSON.stringify({threadId:this.threadId,workerData:m,eval:c,filename:c?void 0:l,code:c?l:void 0,sabSocketFd:this._sabSocketFd===-1?-1:3})+`
|
|
194
|
+
`;try{this._stdinPipe.write_all(o.encode(h),null)}catch(e){throw this._cleanup(),Error(`Failed to send init data: ${e instanceof Error?e.message:e}`)}if(p){let e=n.DataInputStream.new(p);this._readMessages(e)}let g=this._subprocess.get_stderr_pipe();g&&this._readStderr(n.DataInputStream.new(g)),this._subprocess.wait_async(null,()=>{this._onExit()})}postMessage(e,t){if(!(this._exited||!this._stdinPipe))try{let t=this._serializeWithSabTransfer(e),n=JSON.stringify({type:`message`,data:t})+`
|
|
195
|
+
`;this._stdinPipe.write_all(o.encode(n),null)}catch{}}_serializeWithSabTransfer(t){if(!i||this._sabSocketFd===-1)return t;let{value:n,table:r,nextTag:a}=e(t,this._sabNextTag);this._sabNextTag=a;for(let{tag:e,buffer:t}of r)if(!i.sendFd(this._sabSocketFd,t.fd,e))throw Error(`Failed to send SharedBuffer fd over worker side-channel (tag ${e})`);return n}terminate(){if(this._exited)return Promise.resolve(0);let e=new Promise(e=>{this.once(`exit`,t=>e(t))});try{if(this._stdinPipe){let e=JSON.stringify({type:`terminate`})+`
|
|
196
|
+
`;this._stdinPipe.write_all(o.encode(e),null)}}catch{}return setTimeout(()=>{!this._exited&&this._subprocess&&this._subprocess.force_exit()},500),e}ref(){return this}unref(){return this}static _resolveFilename(e,t){if(t)return String(e);if(e instanceof URL)return e.href;let i=String(e);if(i.startsWith(`file://`)||i.startsWith(`http://`)||i.startsWith(`https://`))return i;if(i.startsWith(`/`))return`file://`+i;if(i.startsWith(`./`)||i.startsWith(`../`)||!i.includes(`/`)){let e=r.get_current_dir(),t=r.build_filenamev([e,i]);return`file://`+(n.File.new_for_path(t).get_path()||t)}return`file://`+i}_readMessages(e){e.read_line_async(r.PRIORITY_DEFAULT,null,(t,n)=>{try{let[t]=e.read_line_finish_utf8(n);if(t===null)return;let r=JSON.parse(t);switch(r.type){case`online`:this.emit(`online`);break;case`message`:this.emit(`message`,r.data);break;case`error`:{let e=Error(r.message);r.stack&&(e.stack=r.stack),this.emit(`error`,e);break}}this._readMessages(e)}catch{}})}_stderrChunks=[];_readStderr(e){e.read_line_async(r.PRIORITY_DEFAULT,null,(t,n)=>{try{let[t]=e.read_line_finish_utf8(n);if(t===null){if(this._stderrChunks.length>0){let e=this._stderrChunks.join(`
|
|
197
|
+
`);this.listenerCount(`error`)===0&&this.emit(`error`,Error(e))}return}this._stderrChunks.push(t),this._readStderr(e)}catch{}})}_onExit(){if(this._exited)return;this._exited=!0;let e=this._subprocess?.get_if_exited()?this._subprocess.get_exit_status():1;this._cleanup(),this.emit(`exit`,e)}_cleanup(){if(this._bootstrapFile){try{this._bootstrapFile.delete(null)}catch{}this._bootstrapFile=null}if(this._stdinPipe){try{this._stdinPipe.close(null)}catch{}this._stdinPipe=null}if(this._sabSocketFd!==-1&&i?.closeFd){try{i.closeFd(this._sabSocketFd)}catch{}this._sabSocketFd=-1}this._subprocess=null}};export{c as Worker};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { SharedBuffer } from '@gjsify/sab-native';
|
|
2
|
+
/**
|
|
3
|
+
* Placeholder for a SharedBuffer instance that has been transferred
|
|
4
|
+
* cross-process. The receiver reconstructs a SharedBuffer from the
|
|
5
|
+
* incoming fd indexed by `__sab` (the tag the sender attached to the
|
|
6
|
+
* SCM_RIGHTS message), with the original byteLength.
|
|
7
|
+
*/
|
|
8
|
+
export interface SharedBufferPlaceholder {
|
|
9
|
+
readonly __sab: number;
|
|
10
|
+
readonly size: number;
|
|
11
|
+
}
|
|
12
|
+
export declare function isSharedBufferPlaceholder(value: unknown): value is SharedBufferPlaceholder;
|
|
13
|
+
/**
|
|
14
|
+
* Detect a SharedBuffer instance without holding a hard runtime reference
|
|
15
|
+
* to the constructor — keeps the check resilient when sab-native's
|
|
16
|
+
* prebuild is unavailable (the import resolves but the class never gets
|
|
17
|
+
* instantiated) and avoids paying the typelib load just to typecheck a
|
|
18
|
+
* message payload.
|
|
19
|
+
*/
|
|
20
|
+
export declare function isSharedBuffer(value: unknown): value is SharedBuffer;
|
|
21
|
+
/**
|
|
22
|
+
* Walk `value`, replace every SharedBuffer instance with a placeholder
|
|
23
|
+
* carrying a freshly-allocated tag, and return the substituted tree
|
|
24
|
+
* alongside the table of (tag, SharedBuffer) pairs the caller must send
|
|
25
|
+
* over the SCM_RIGHTS side-channel before the JSON message itself.
|
|
26
|
+
*
|
|
27
|
+
* The walker handles plain objects + arrays. Other tagged types
|
|
28
|
+
* (Map, Set, …) are passed through unchanged — same constraint Node
|
|
29
|
+
* applies to its in-built clone walker.
|
|
30
|
+
*
|
|
31
|
+
* Tags start at `startTag` and increment per discovery. Callers should
|
|
32
|
+
* thread a per-Worker sequence counter so tags stay unique within a
|
|
33
|
+
* single FdChannel pair's lifetime (4 G tags = 2³² = plenty).
|
|
34
|
+
*/
|
|
35
|
+
export declare function extractSharedBuffers(value: unknown, startTag: number): {
|
|
36
|
+
value: unknown;
|
|
37
|
+
table: {
|
|
38
|
+
tag: number;
|
|
39
|
+
buffer: SharedBuffer;
|
|
40
|
+
}[];
|
|
41
|
+
nextTag: number;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Receiver-side counterpart: walk a tree that may contain
|
|
45
|
+
* SharedBufferPlaceholder leaves and replace each with the corresponding
|
|
46
|
+
* SharedBuffer reconstructed from the fdMap. Mutates the input in place
|
|
47
|
+
* (callers always work on a freshly-parsed JSON tree, so this avoids an
|
|
48
|
+
* extra copy).
|
|
49
|
+
*
|
|
50
|
+
* If a placeholder references a tag that is not yet in `fdMap`, the
|
|
51
|
+
* caller should buffer the message and retry once the recv-loop fills
|
|
52
|
+
* the missing entry. In the current protocol the sender always
|
|
53
|
+
* sends fds before the JSON line, so the map is populated by the time
|
|
54
|
+
* the JSON arrives — but the bootstrap layer is responsible for that
|
|
55
|
+
* ordering.
|
|
56
|
+
*/
|
|
57
|
+
export declare function materializeSharedBuffers(value: unknown, resolveTag: (tag: number, size: number) => SharedBuffer): unknown;
|
package/lib/types/worker.d.ts
CHANGED
|
@@ -19,8 +19,24 @@ export declare class Worker extends EventEmitter {
|
|
|
19
19
|
private _stdinPipe;
|
|
20
20
|
private _exited;
|
|
21
21
|
private _bootstrapFile;
|
|
22
|
+
/** Parent-side end of the SCM_RIGHTS side-channel for SharedBuffer fds.
|
|
23
|
+
* -1 when sab-native's prebuild isn't loaded → SharedBuffer transfer is
|
|
24
|
+
* unavailable but everything else works. Closed in `_cleanup()`. */
|
|
25
|
+
private _sabSocketFd;
|
|
26
|
+
/** Per-Worker sequence counter for SharedBuffer transfer tags. Resets
|
|
27
|
+
* to 0 at spawn; each postMessage call increments it by the number of
|
|
28
|
+
* unique SharedBuffer instances in the value tree. */
|
|
29
|
+
private _sabNextTag;
|
|
22
30
|
constructor(filename: string | URL, options?: WorkerOptions);
|
|
23
31
|
postMessage(value: unknown, _transferList?: unknown[]): void;
|
|
32
|
+
/**
|
|
33
|
+
* Walk `value` for SharedBuffer instances, ship each fd over the
|
|
34
|
+
* parent-side end of the FdChannel socketpair, return the placeholder-
|
|
35
|
+
* substituted tree ready to JSON-serialise. No-op (returns value
|
|
36
|
+
* untouched) when sab-native's prebuild isn't loaded or the side-
|
|
37
|
+
* channel was never opened.
|
|
38
|
+
*/
|
|
39
|
+
private _serializeWithSabTransfer;
|
|
24
40
|
terminate(): Promise<number>;
|
|
25
41
|
ref(): this;
|
|
26
42
|
unref(): this;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/worker_threads",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.13",
|
|
4
4
|
"description": "Node.js worker_threads module for Gjs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "lib/esm/index.js",
|
|
@@ -33,15 +33,16 @@
|
|
|
33
33
|
"worker_threads"
|
|
34
34
|
],
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@gjsify/cli": "^0.4.
|
|
37
|
-
"@gjsify/node-globals": "^0.4.
|
|
38
|
-
"@gjsify/unit": "^0.4.
|
|
36
|
+
"@gjsify/cli": "^0.4.13",
|
|
37
|
+
"@gjsify/node-globals": "^0.4.13",
|
|
38
|
+
"@gjsify/unit": "^0.4.13",
|
|
39
39
|
"@types/node": "^25.6.2",
|
|
40
40
|
"typescript": "^6.0.3"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@girs/gio-2.0": "2.88.0-4.0.0-rc.15",
|
|
44
44
|
"@girs/glib-2.0": "2.88.0-4.0.0-rc.15",
|
|
45
|
-
"@gjsify/events": "^0.4.
|
|
45
|
+
"@gjsify/events": "^0.4.13",
|
|
46
|
+
"@gjsify/sab-native": "^0.4.13"
|
|
46
47
|
}
|
|
47
48
|
}
|