@gjsify/sab-native 0.22.0 → 0.23.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/README.md +22 -0
- package/lib/esm/index.js +1 -1
- package/lib/types/index.d.ts +37 -1
- package/package.json +11 -4
package/README.md
CHANGED
|
@@ -4,6 +4,28 @@ Optional native Vala bridge providing cross-process shared memory and atomics fo
|
|
|
4
4
|
|
|
5
5
|
Part of the [gjsify](https://github.com/gjsify/gjsify) project — Node.js and Web APIs for GJS (GNOME JavaScript).
|
|
6
6
|
|
|
7
|
+
## Platform support
|
|
8
|
+
|
|
9
|
+
**Cross-process `SharedBuffer` is Linux-only today.** This is a property of the package, not of your installation — see [ADR 0013](../../../docs/adr/0013-sab-native-platform-scope.md) for the full reasoning.
|
|
10
|
+
|
|
11
|
+
| Platform | Cross-process `SharedBuffer` | Notes |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| Linux `x86_64`, `aarch64`, `ppc64`, `s390x`, `riscv64` | ✅ Supported | `memfd_create(2)` + `mmap(MAP_SHARED)`, `SYS_futex` (non-private flavour, so waits match across processes), `SCM_RIGHTS`. Prebuilds ship for all five architectures. |
|
|
14
|
+
| macOS | ❌ Not yet | Planned, design fixed: `shm_open` + `ftruncate` + immediate `shm_unlink` for the region, unchanged `SCM_RIGHTS`, and `os_sync_wait_on_address` / `os_sync_wake_by_address_*` with the `…_SHARED` flag for wait/notify. Requires **macOS 14.4+**. Blocked on a macOS prebuild CI job. |
|
|
15
|
+
| Windows | ❌ Not supported | Blocked twice over: GJS itself does not run on Windows, and Windows has no cross-process address-keyed wait — `WaitOnAddress`/`WakeByAddressSingle` are process-local by documentation, so `wait32`/`notify32` would need a different contract (named kernel objects). Revisit only via a new ADR. |
|
|
16
|
+
| Node.js / browser | ❌ n/a | GJS-only package (`runtimes.node`/`.browser` = `none`). |
|
|
17
|
+
|
|
18
|
+
### What happens where it is unsupported
|
|
19
|
+
|
|
20
|
+
There is exactly one gate, and it never crashes:
|
|
21
|
+
|
|
22
|
+
- `hasNativeSab()` returns `false`. **Always check it first.**
|
|
23
|
+
- `SharedBuffer.create()` / `SharedBuffer.fromFd()` throw an `Error` naming the platform scope (not a broken install).
|
|
24
|
+
- `nativeSab` and `fdChannel` are `null`.
|
|
25
|
+
- `@gjsify/worker_threads` keeps working in full — `Worker`, `postMessage`, `MessageChannel`/`MessagePort` and `transferList` for `ArrayBuffer`/`MessagePort` are all unaffected. Only `SharedBuffer` itself is unavailable, because the `SCM_RIGHTS` side-channel is simply never created.
|
|
26
|
+
|
|
27
|
+
A stale or partially-loaded typelib is treated as unavailable too, so `hasNativeSab()` can never report `true` for a module that would then fail mid-call.
|
|
28
|
+
|
|
7
29
|
## Installation
|
|
8
30
|
|
|
9
31
|
This package is loaded automatically by `@gjsify/worker_threads` when the prebuild is present. Install it explicitly to enable `SharedBuffer` cross-process transfers:
|
package/lib/esm/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import"./_virtual/_rolldown/runtime.js";const e=globalThis;
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";const e=globalThis;function resolveNativeSab(e){if(!e)return null;let t;try{t=e.GjsifySabNative}catch{return null}if(!t||typeof t!=`object`)return null;let n=t;return typeof n.SharedBuffer?.create!=`function`||typeof n.FdChannel?.make_pair!=`function`?null:n}const t=resolveNativeSab(e.imports?.gi),n=t;function hasNativeSab(){return t!==null}const r="@gjsify/sab-native: the native backend is not available on this platform. Cross-process SharedBuffer is currently Linux-only (memfd_create + futex + SCM_RIGHTS); prebuilds ship for linux-{x86_64,aarch64,ppc64,s390x,riscv64} only — macOS support is planned and Windows is unsupported (see docs/adr/0013-sab-native-platform-scope.md). Guard with hasNativeSab() before using SharedBuffer. On Linux, a missing prebuild can be built locally with `gjsify workspace @gjsify/sab-native build:prebuilds`.";var i=class SharedBuffer{_native;constructor(e){this._native=e}static create(e){if(!t)throw Error(r);if(!Number.isInteger(e)||e<=0)throw TypeError(`SharedBuffer.create: size must be a positive integer`);let n=t.SharedBuffer.create(e);if(!n)throw Error(`memfd_create or mmap failed`);return new SharedBuffer(n)}static fromFd(e,n){if(!t)throw Error(r);if(!Number.isInteger(e)||e<0)throw TypeError(`SharedBuffer.fromFd: fd must be a non-negative integer`);if(!Number.isInteger(n)||n<=0)throw TypeError(`SharedBuffer.fromFd: size must be a positive integer`);let i=t.SharedBuffer.from_fd(e,n);if(!i)throw Error(`mmap failed`);return new SharedBuffer(i)}get byteLength(){return this._assertOpen().byte_length}get fd(){return this._assertOpen().fd}get closed(){return this._native===null}close(){this._native=null}getUint8(e){return this._assertOpen().get_u8(e)}setUint8(e,t){this._assertOpen().set_u8(e,t&255)}getUint32LE(e){return this._assertOpen().get_u32_le(e)}setUint32LE(e,t){this._assertOpen().set_u32_le(e,t>>>0)}getInt32LE(e){return this._assertOpen().get_i32_le(e)}setInt32LE(e,t){this._assertOpen().set_i32_le(e,t|0)}getBigUint64LE(e){return this._assertOpen().get_u64_le(e)}setBigUint64LE(e,t){this._assertOpen().set_u64_le(e,t)}readBytes(t,n){let r=this._assertOpen().read_bytes(t,n),i=e.imports?.byteArray;if(i&&typeof i.fromGBytes==`function`){let e=i.fromGBytes(r);return new Uint8Array(e)}return new Uint8Array(r.toArray())}viewBytes(e,t){return this.readBytes(e,t)}toBuffer(t=0,n){let r=n??this.byteLength-t,i=this.viewBytes(t,r),a=e.Buffer;if(typeof a?.from!=`function`)throw Error(`SharedBuffer.toBuffer: globalThis.Buffer is not registered. Import "@gjsify/buffer/register" or rely on --globals auto.`);return a.from(i.buffer,i.byteOffset,i.byteLength)}writeBytes(t,n){let r=e.imports?.byteArray,i;if(r&&typeof r.toGBytes==`function`)i=r.toGBytes(n);else{let t=e.imports?.gi?.GLib;i=t?.Bytes?new t.Bytes(n):n}this._assertOpen().write_bytes(t,i)}get _nativeHandle(){return this._assertOpen()}_assertOpen(){if(!this._native)throw Error(`SharedBuffer has been closed`);return this._native}};const a={add32(e,t,n){return e._nativeHandle.atomic_add_i32(t,n|0)},sub32(e,t,n){return e._nativeHandle.atomic_sub_i32(t,n|0)},load32(e,t){return e._nativeHandle.atomic_load_i32(t)},store32(e,t,n){e._nativeHandle.atomic_store_i32(t,n|0)},exchange32(e,t,n){return e._nativeHandle.atomic_xchg_i32(t,n|0)},compareExchange32(e,t,n,r){return e._nativeHandle.atomic_cmpxchg_i32(t,n|0,r|0)},wait32(e,t,n,r){let i=e._nativeHandle.futex_wait(t,n|0,r|0);if(i===0)return`ok`;if(i===-1)return`not-equal`;if(i===-2)return`timed-out`;if(i===-3)return`interrupted`;throw Error(`futex_wait returned errno ${-i}`)},notify32(e,t,n){return e._nativeHandle.futex_wake(t,n|0)}},o=t?{makePair(){let[e,n,r]=t.FdChannel.make_pair();if(!e)throw Error(`socketpair() failed`);return{parentFd:n,childFd:r}},sendFd(e,n,r){return t.FdChannel.send_fd(e,n,r>>>0)},recvFd(e){let[n,r]=t.FdChannel.recv_fd(e);if(n===0)return null;if(n<0)throw Error(`recvmsg failed`);return{fd:n,tag:r>>>0}},closeFd(e){t.FdChannel.close_fd(e)}}:null;export{r as NATIVE_SAB_UNAVAILABLE,i as SharedBuffer,a as atomics,o as fdChannel,hasNativeSab,n as nativeSab,resolveNativeSab};
|
package/lib/types/index.d.ts
CHANGED
|
@@ -34,10 +34,46 @@ export interface GjsifySabNativeModule {
|
|
|
34
34
|
SharedBuffer: NativeSharedBufferClass;
|
|
35
35
|
FdChannel: NativeFdChannelClass;
|
|
36
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Resolve the `GjsifySabNative` GI module out of a GJS `imports.gi` object,
|
|
39
|
+
* normalising **every** unavailable case to `null`.
|
|
40
|
+
*
|
|
41
|
+
* Exported so the degradation contract can be pinned by a spec without a
|
|
42
|
+
* second process (see `shared-buffer.gjs.spec.ts`). Three ways this returns
|
|
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
|
|
56
|
+
*/
|
|
57
|
+
export declare function resolveNativeSab(gi: Record<string, unknown> | undefined): GjsifySabNativeModule | null;
|
|
37
58
|
/** The native GjsifySabNative module, or null if not installed. */
|
|
38
59
|
export declare const nativeSab: GjsifySabNativeModule | null;
|
|
39
|
-
/**
|
|
60
|
+
/**
|
|
61
|
+
* Returns true when the GjsifySabNative native library is available.
|
|
62
|
+
*
|
|
63
|
+
* This is THE gate for cross-process `SharedBuffer` support and it is
|
|
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.
|
|
67
|
+
*/
|
|
40
68
|
export declare function hasNativeSab(): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Message thrown by `SharedBuffer.create()` / `.fromFd()` when the native
|
|
71
|
+
* backend is unavailable. Names the actual situation — a platform-scoped
|
|
72
|
+
* capability — instead of implying a broken install.
|
|
73
|
+
*
|
|
74
|
+
* @internal exported so specs and docs can assert the exact contract text.
|
|
75
|
+
*/
|
|
76
|
+
export declare const NATIVE_SAB_UNAVAILABLE: string;
|
|
41
77
|
/**
|
|
42
78
|
* A shared-memory region backed by an anonymous memfd (Linux) and
|
|
43
79
|
* mmap(MAP_SHARED). The `.fd` may be passed to a child process via
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/sab-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.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",
|
|
@@ -19,6 +19,13 @@
|
|
|
19
19
|
"src/vala"
|
|
20
20
|
],
|
|
21
21
|
"gjsify": {
|
|
22
|
+
"platforms": [
|
|
23
|
+
"linux-x86_64",
|
|
24
|
+
"linux-aarch64",
|
|
25
|
+
"linux-ppc64",
|
|
26
|
+
"linux-s390x",
|
|
27
|
+
"linux-riscv64"
|
|
28
|
+
],
|
|
22
29
|
"prebuilds": "prebuilds",
|
|
23
30
|
"runtimes": {
|
|
24
31
|
"gjs": "polyfill",
|
|
@@ -37,7 +44,7 @@
|
|
|
37
44
|
"build:gjsify": "gjsify build --library 'src/ts/**/*.{ts,js}' --exclude 'src/ts/**/*.spec.{mts,ts}' 'src/ts/test.{mts,ts}'",
|
|
38
45
|
"build:meson": "gjsify run init:meson && meson compile -C build",
|
|
39
46
|
"build:types": "gjsify tsc",
|
|
40
|
-
"build:prebuilds": "gjsify run build:meson &&
|
|
47
|
+
"build:prebuilds": "gjsify run build:meson && node ../../../scripts/stage-prebuild.mjs .",
|
|
41
48
|
"build:gir-types": "ts-for-gir generate --externalDeps --allowMissingDeps --girDirectories=./prebuilds/linux-x86_64 --girDirectories=/usr/share/gir-1.0 --modules=GjsifySabNative-1.0 --outdir=src/ts --npmScope=@girs --package=false --ignoreVersionConflicts=true",
|
|
42
49
|
"build:test": "gjsify run build:test:gjs",
|
|
43
50
|
"build:test:gjs": "gjsify build src/ts/test.mts --app gjs --outfile test.gjs.mjs",
|
|
@@ -63,8 +70,8 @@
|
|
|
63
70
|
"@girs/gio-2.0": "^4.1.0"
|
|
64
71
|
},
|
|
65
72
|
"devDependencies": {
|
|
66
|
-
"@gjsify/cli": "^0.
|
|
67
|
-
"@gjsify/unit": "^0.
|
|
73
|
+
"@gjsify/cli": "^0.23.0",
|
|
74
|
+
"@gjsify/unit": "^0.23.0",
|
|
68
75
|
"@ts-for-gir/cli": "^4.1.0",
|
|
69
76
|
"@types/node": "^25.9.2",
|
|
70
77
|
"typescript": "^6.0.3"
|