@gjsify/sab-native 0.36.0 → 0.37.0
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/types/index.d.ts +69 -124
- package/package.json +8 -8
package/lib/types/index.d.ts
CHANGED
|
@@ -35,99 +35,77 @@ export interface GjsifySabNativeModule {
|
|
|
35
35
|
FdChannel: NativeFdChannelClass;
|
|
36
36
|
}
|
|
37
37
|
/**
|
|
38
|
-
* Resolve the `GjsifySabNative` GI module
|
|
39
|
-
*
|
|
38
|
+
* Resolve the `GjsifySabNative` GI module, normalising EVERY unavailable case to
|
|
39
|
+
* `null`: no `gi` at all (not GJS), a throwing property access (typelib not on
|
|
40
|
+
* `GI_TYPELIB_PATH` — the macOS/Windows case), and a value that is not the module
|
|
41
|
+
* we expect (stale, partial or shadowed typelib). The third needs the shape check:
|
|
42
|
+
* a partial value otherwise leaves `hasNativeSab()` reporting `true` and fails with
|
|
43
|
+
* an opaque `TypeError` at first use.
|
|
40
44
|
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* `null`, all of which must behave identically to the caller:
|
|
44
|
-
*
|
|
45
|
-
* 1. `gi` itself is absent — we are not on GJS at all (Node, browser).
|
|
46
|
-
* 2. The property access throws — GJS raises when the typelib is not on
|
|
47
|
-
* `GI_TYPELIB_PATH`, i.e. no prebuild for this platform. This is the
|
|
48
|
-
* macOS/Windows case.
|
|
49
|
-
* 3. The access succeeds but yields something that is not the module we
|
|
50
|
-
* expect — a stale, partial or shadowed typelib. Without the shape check
|
|
51
|
-
* a `undefined`/partial value would leave `hasNativeSab()` reporting
|
|
52
|
-
* `true` and then blow up with an opaque `TypeError` at first use; the
|
|
53
|
-
* check converts that into the same clean "unavailable" answer.
|
|
54
|
-
*
|
|
55
|
-
* @internal
|
|
45
|
+
* @internal exported so a spec can pin the degradation contract in-process
|
|
46
|
+
* (`shared-buffer.gjs.spec.ts`).
|
|
56
47
|
*/
|
|
57
48
|
export declare function resolveNativeSab(gi: Record<string, unknown> | undefined): GjsifySabNativeModule | null;
|
|
58
49
|
/** The native GjsifySabNative module, or null if not installed. */
|
|
59
50
|
export declare const nativeSab: GjsifySabNativeModule | null;
|
|
60
51
|
/**
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* platform-conditional by design: it is `true` only on Linux (see ADR 0013),
|
|
65
|
-
* and `false` on macOS, Windows, Node and the browser. Guard every use of
|
|
66
|
-
* `SharedBuffer` / `atomics` / `fdChannel` with it.
|
|
52
|
+
* THE gate for cross-process `SharedBuffer`, and platform-conditional by design:
|
|
53
|
+
* `true` only on Linux (ADR 0013), `false` on macOS, Windows, Node and the browser.
|
|
54
|
+
* Guard every use of `SharedBuffer` / `atomics` / `fdChannel` with it.
|
|
67
55
|
*/
|
|
68
56
|
export declare function hasNativeSab(): boolean;
|
|
69
57
|
/**
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* capability — instead of implying a broken install.
|
|
58
|
+
* Thrown by `SharedBuffer.create()` / `.fromFd()` when the native backend is
|
|
59
|
+
* unavailable. Says "platform-scoped capability", not "broken install".
|
|
73
60
|
*
|
|
74
61
|
* @internal exported so specs and docs can assert the exact contract text.
|
|
75
62
|
*/
|
|
76
63
|
export declare const NATIVE_SAB_UNAVAILABLE: string;
|
|
77
64
|
/**
|
|
78
|
-
* A shared-memory region backed by an anonymous memfd (
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* `SharedBuffer.fromFd()` to share the backing store.
|
|
82
|
-
*
|
|
83
|
-
* Reads and writes are explicitly little-endian regardless of host
|
|
84
|
-
* byte-order — the C shim swaps where needed (s390x / ppc64).
|
|
65
|
+
* A shared-memory region backed by an anonymous memfd and mmap(MAP_SHARED). The
|
|
66
|
+
* `.fd` may be passed to a child over a Unix-domain socket via SCM_RIGHTS; the
|
|
67
|
+
* child mmaps the same fd with `SharedBuffer.fromFd()` to share the backing store.
|
|
85
68
|
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* `Atomics` because `Atomics` rejects non-SharedArrayBuffer views.
|
|
69
|
+
* Reads and writes are little-endian regardless of host byte-order — the C shim
|
|
70
|
+
* swaps where needed (s390x / ppc64).
|
|
89
71
|
*
|
|
90
|
-
* Lifecycle: the
|
|
91
|
-
*
|
|
92
|
-
*
|
|
72
|
+
* Lifecycle: the memfd is closed and the mmap freed by the native destructor, i.e.
|
|
73
|
+
* when this instance is garbage-collected. `.close()` does NOT release the region;
|
|
74
|
+
* it only drops our reference so later access throws.
|
|
93
75
|
*/
|
|
94
76
|
export declare class SharedBuffer {
|
|
95
77
|
/** @internal */
|
|
96
78
|
private _native;
|
|
97
79
|
private constructor();
|
|
98
80
|
/**
|
|
99
|
-
* Allocate a fresh anonymous shared-memory region.
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
* @throws Error if the prebuild is not loaded, or if memfd_create/mmap fail.
|
|
81
|
+
* Allocate a fresh anonymous shared-memory region. `size` SHOULD be a multiple
|
|
82
|
+
* of the page size — smaller values work but waste a whole page per region.
|
|
83
|
+
*
|
|
84
|
+
* @throws if the prebuild is not loaded, or memfd_create/mmap fail.
|
|
104
85
|
*/
|
|
105
86
|
static create(size: number): SharedBuffer;
|
|
106
87
|
/**
|
|
107
|
-
* Map an existing shared-memory fd
|
|
108
|
-
*
|
|
88
|
+
* Map an existing shared-memory fd — typically received via SCM_RIGHTS — into
|
|
89
|
+
* this process. The fd is dup'd, so the caller keeps ownership of their copy.
|
|
109
90
|
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
* @param size MUST match the sender's view of the region size; the
|
|
113
|
-
* kernel does not check this for memfd, so a mismatch
|
|
114
|
-
* results in silent partial maps.
|
|
91
|
+
* `size` MUST match the sender's view of the region: the kernel does not check
|
|
92
|
+
* it for a memfd, so a mismatch silently produces a partial map.
|
|
115
93
|
*/
|
|
116
94
|
static fromFd(fd: number, size: number): SharedBuffer;
|
|
117
95
|
/** Region size in bytes. */
|
|
118
96
|
get byteLength(): number;
|
|
119
97
|
/**
|
|
120
|
-
* File descriptor of the backing memfd
|
|
121
|
-
*
|
|
122
|
-
* (post-spawn) so the child can call `SharedBuffer.fromFd(fd, size)`.
|
|
98
|
+
* File descriptor of the backing memfd — hand it to a child via
|
|
99
|
+
* `Gio.SubprocessLauncher.take_fd()` pre-spawn or SCM_RIGHTS post-spawn.
|
|
123
100
|
*/
|
|
124
101
|
get fd(): number;
|
|
125
102
|
/** True if this region has been released via close(). */
|
|
126
103
|
get closed(): boolean;
|
|
127
104
|
/**
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
105
|
+
* Drop this view of the region. Idempotent. NOT a release: the memfd and mmap go
|
|
106
|
+
* away with the native GObject's destructor, so this only makes later access
|
|
107
|
+
* throw rather than segfault. The backing store also survives in any other
|
|
108
|
+
* process still holding the fd mapped.
|
|
131
109
|
*/
|
|
132
110
|
close(): void;
|
|
133
111
|
getUint8(offset: number): number;
|
|
@@ -139,64 +117,39 @@ export declare class SharedBuffer {
|
|
|
139
117
|
getBigUint64LE(offset: number): bigint;
|
|
140
118
|
setBigUint64LE(offset: number, v: bigint): void;
|
|
141
119
|
/**
|
|
142
|
-
* Read a byte range
|
|
143
|
-
*
|
|
144
|
-
* `writeBytes()` to commit changes back, or `viewBytes()` for a
|
|
145
|
-
* zero-copy view.
|
|
120
|
+
* Read a byte range as a Uint8Array. ONE-TIME COPY — modifications do NOT
|
|
121
|
+
* propagate back; use `writeBytes()` to commit changes.
|
|
146
122
|
*/
|
|
147
123
|
readBytes(offset: number, length: number): Uint8Array;
|
|
148
124
|
/**
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
* `fs.writeSync`) calls into this method via duck-typing and the
|
|
153
|
-
* `viewBytes` name reads more naturally there.
|
|
154
|
-
*
|
|
155
|
-
* **NOT zero-copy in current GJS.** GJS's `byteArray.fromGBytes`
|
|
156
|
-
* (`refs/gjs/gjs/byteArray.cpp::from_gbytes_func`) allocates a fresh
|
|
157
|
-
* `JS::ArrayBuffer` and memcpy's the GBytes data into it — by design,
|
|
158
|
-
* for alignment + immutability reasons.
|
|
159
|
-
*
|
|
160
|
-
* **Why no internal fix is possible**: a true zero-copy view would need
|
|
161
|
-
* `JS::NewExternalArrayBuffer` against the mmap pointer, but that JSAPI
|
|
162
|
-
* call requires a `JSContext*` which GJS does not expose to
|
|
163
|
-
* GObject-introspected `.so` plugins. The fix has to land in GJS
|
|
164
|
-
* itself (e.g. a `byteArray.fromGBytesShared` helper). Tracked under
|
|
165
|
-
* status/upstream-patch-candidates.md.
|
|
166
|
-
*
|
|
167
|
-
* Modifications to the returned array therefore do NOT propagate back
|
|
168
|
-
* to the region — use `writeBytes()` to commit changes.
|
|
125
|
+
* Identical to `readBytes()`, under a second name because downstream tooling
|
|
126
|
+
* (`Buffer.from`, `node:crypto`'s `Hash.update`, `fs.writeSync`) duck-types into
|
|
127
|
+
* it and `viewBytes` reads naturally there.
|
|
169
128
|
*
|
|
170
|
-
*
|
|
129
|
+
* Despite the name it is NOT zero-copy, and cannot be made so from here: GJS's
|
|
130
|
+
* `byteArray.fromGBytes` (`refs/gjs/gjs/byteArray.cpp::from_gbytes_func`)
|
|
131
|
+
* allocates a fresh `JS::ArrayBuffer` and memcpys into it, deliberately, for
|
|
132
|
+
* alignment and immutability. A real view needs `JS::NewExternalArrayBuffer`
|
|
133
|
+
* over the mmap pointer, which needs a `JSContext*` that GJS does not hand to
|
|
134
|
+
* introspected `.so` plugins — so the fix belongs in GJS (a
|
|
135
|
+
* `byteArray.fromGBytesShared`). Tracked in status/upstream-patch-candidates.md.
|
|
171
136
|
*/
|
|
172
137
|
viewBytes(offset: number, length: number): Uint8Array;
|
|
173
138
|
/**
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
* `byteArray.fromGBytes`, not bypassable from a `.so` plugin without
|
|
178
|
-
* upstream patching GJS) — `buf.writeUInt32LE(...)`, `buf.subarray(...)`,
|
|
179
|
-
* `buf.toString('hex')`, `createHash().update(buf)` all work, but
|
|
180
|
-
* writes do NOT propagate back to the shared region.
|
|
139
|
+
* The bytes at `[offset, offset+length)` as a `Buffer`, so `writeUInt32LE`,
|
|
140
|
+
* `subarray`, `toString('hex')` and `createHash().update()` all work — on a
|
|
141
|
+
* COPY, per `viewBytes()`, so writes do not reach the shared region.
|
|
181
142
|
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
* `--globals auto`; for ad-hoc scripts, add an explicit
|
|
186
|
-
* `import '@gjsify/buffer/register'` at the entry point.
|
|
143
|
+
* Needs `globalThis.Buffer` registered, which `--globals auto` does for the
|
|
144
|
+
* standard CLI bundle; an ad-hoc script needs an explicit
|
|
145
|
+
* `import '@gjsify/buffer/register'`.
|
|
187
146
|
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
* `toBuffer<Buffer>()` gets the full Buffer surface.
|
|
192
|
-
*
|
|
193
|
-
* @param offset byte offset into the shared region. Defaults to 0.
|
|
194
|
-
* @param length byte length. Defaults to `byteLength - offset`.
|
|
147
|
+
* The return type is generic only to avoid a dependency on `@gjsify/buffer`; the
|
|
148
|
+
* concrete value is always a `Buffer`, so `toBuffer<Buffer>()` gets the full
|
|
149
|
+
* surface and the `Uint8Array` default is always safe.
|
|
195
150
|
*/
|
|
196
151
|
toBuffer<T extends Uint8Array = Uint8Array>(offset?: number, length?: number): T;
|
|
197
|
-
/**
|
|
198
|
-
* Write a byte range into the region. memcpy on the C side.
|
|
199
|
-
*/
|
|
152
|
+
/** Write a byte range into the region. memcpy on the C side. */
|
|
200
153
|
writeBytes(offset: number, data: Uint8Array): void;
|
|
201
154
|
/** @internal Internal escape-hatch for atomics + worker_threads transfer. */
|
|
202
155
|
get _nativeHandle(): NativeSharedBuffer;
|
|
@@ -205,10 +158,9 @@ export declare class SharedBuffer {
|
|
|
205
158
|
/**
|
|
206
159
|
* Atomic operations against a `SharedBuffer`. Memory order: SEQ_CST.
|
|
207
160
|
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
* namespace mirrors the
|
|
211
|
-
* memfd-backed regions instead.
|
|
161
|
+
* The `Atomics.*` built-ins are not an option: GJS does not expose `Atomics` at all
|
|
162
|
+
* (verified on gjs 1.88.1), and a `SharedBuffer` is not a typed-array view in any
|
|
163
|
+
* case. This namespace mirrors the common surface over the memfd-backed region.
|
|
212
164
|
*/
|
|
213
165
|
export declare const atomics: {
|
|
214
166
|
/** `[*(int32_t*)(sb+offset)] += v`, returns previous value. */
|
|
@@ -238,11 +190,11 @@ export declare const atomics: {
|
|
|
238
190
|
notify32(sb: SharedBuffer, offset: number, count: number): number;
|
|
239
191
|
};
|
|
240
192
|
/**
|
|
241
|
-
* Unix-domain socket pair + SCM_RIGHTS fd
|
|
193
|
+
* Unix-domain socket pair + SCM_RIGHTS fd transfer.
|
|
242
194
|
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
*
|
|
195
|
+
* @internal for `@gjsify/worker_threads` to wire up the cross-process SharedBuffer
|
|
196
|
+
* transfer at `Worker` spawn time; direct consumers use
|
|
197
|
+
* `Worker.postMessage(value, [sb])`.
|
|
246
198
|
*/
|
|
247
199
|
export declare const fdChannel: {
|
|
248
200
|
makePair(): {
|
|
@@ -250,23 +202,16 @@ export declare const fdChannel: {
|
|
|
250
202
|
childFd: number;
|
|
251
203
|
};
|
|
252
204
|
/**
|
|
253
|
-
* Send one fd over an open SOCK_SEQPACKET pair via SCM_RIGHTS.
|
|
254
|
-
* `
|
|
255
|
-
*
|
|
256
|
-
* makes sense for the situation).
|
|
205
|
+
* Send one fd over an open SOCK_SEQPACKET pair via SCM_RIGHTS. `false` on
|
|
206
|
+
* `sendmsg()` failure, with errno left on the calling thread for the caller
|
|
207
|
+
* to shape into an error.
|
|
257
208
|
*/
|
|
258
209
|
sendFd(socketFd: number, fdToSend: number, tag: number): boolean;
|
|
259
|
-
/**
|
|
260
|
-
* Blocking recv of one fd. Returns the received fd + tag, or null on
|
|
261
|
-
* orderly EOF.
|
|
262
|
-
*/
|
|
210
|
+
/** Blocking recv of one fd + tag, or `null` on orderly EOF. */
|
|
263
211
|
recvFd(socketFd: number): {
|
|
264
212
|
fd: number;
|
|
265
213
|
tag: number;
|
|
266
214
|
} | null;
|
|
267
|
-
/**
|
|
268
|
-
* close(2) on a fd previously created by `makePair()` (or any fd, really).
|
|
269
|
-
* Idempotent — closing an already-closed fd is fine.
|
|
270
|
-
*/
|
|
215
|
+
/** close(2) on any fd. Idempotent: the shim reports success on EBADF too. */
|
|
271
216
|
closeFd(fd: number): void;
|
|
272
217
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/sab-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.37.0",
|
|
4
4
|
"description": "Optional Vala/GObject bridge providing cross-process shared memory + atomics for @gjsify/worker_threads. Wraps Linux memfd_create(2) + mmap(MAP_SHARED) to back JS-visible SharedBuffer regions whose fds can be passed to child workers via SCM_RIGHTS over a Unix-domain socket; exposes typed accessors (getInt32LE / setUint8 / etc.) plus atomics built on __atomic_* GCC builtins and SYS_futex for wait/notify. Workaround for the stock-GJS gap where SharedArrayBuffer is unavailable (Mozilla disables the SAB constructor without COOP/COEP opts) and SAB cannot share backing stores across Gio.Subprocess workers anyway.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/esm/index.js",
|
|
@@ -68,8 +68,8 @@
|
|
|
68
68
|
"@girs/gio-2.0": "^4.1.0"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
|
-
"@gjsify/cli": "^0.
|
|
72
|
-
"@gjsify/unit": "^0.
|
|
71
|
+
"@gjsify/cli": "^0.37.0",
|
|
72
|
+
"@gjsify/unit": "^0.37.0",
|
|
73
73
|
"@ts-for-gir/cli": "^4.1.0",
|
|
74
74
|
"@types/node": "^25.9.2",
|
|
75
75
|
"typescript": "^6.0.3"
|
|
@@ -85,10 +85,10 @@
|
|
|
85
85
|
},
|
|
86
86
|
"homepage": "https://github.com/gjsify/gjsify/tree/main/packages/node/sab-native#readme",
|
|
87
87
|
"optionalDependencies": {
|
|
88
|
-
"@gjsify/sab-native-linux-arm64": "0.
|
|
89
|
-
"@gjsify/sab-native-linux-ppc64": "0.
|
|
90
|
-
"@gjsify/sab-native-linux-riscv64": "0.
|
|
91
|
-
"@gjsify/sab-native-linux-s390x": "0.
|
|
92
|
-
"@gjsify/sab-native-linux-x64": "0.
|
|
88
|
+
"@gjsify/sab-native-linux-arm64": "0.37.0",
|
|
89
|
+
"@gjsify/sab-native-linux-ppc64": "0.37.0",
|
|
90
|
+
"@gjsify/sab-native-linux-riscv64": "0.37.0",
|
|
91
|
+
"@gjsify/sab-native-linux-s390x": "0.37.0",
|
|
92
|
+
"@gjsify/sab-native-linux-x64": "0.37.0"
|
|
93
93
|
}
|
|
94
94
|
}
|