knitting 0.1.70 → 0.1.73
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 +101 -10
- package/knitting.browser.d.ts +3 -1
- package/knitting.browser.js +1 -1
- package/knitting.d.ts +3 -1
- package/knitting.js +2 -1
- package/package.json +8 -3
- package/prebuilds/win32-x64/knitting_windows_shared_memory.dll +0 -0
- package/prebuilds/win32-x64-node-127/knitting_buffer_pointer.node +0 -0
- package/prebuilds/win32-x64-node-127/knitting_doorbell.node +0 -0
- package/prebuilds/win32-x64-node-127/knitting_shared_memory.node +0 -0
- package/prebuilds/win32-x64-node-127/knitting_shm.node +0 -0
- package/prebuilds/win32-x64-node-137/knitting_buffer_pointer.node +0 -0
- package/prebuilds/win32-x64-node-137/knitting_doorbell.node +0 -0
- package/prebuilds/win32-x64-node-137/knitting_shared_memory.node +0 -0
- package/prebuilds/win32-x64-node-137/knitting_shm.node +0 -0
- package/src/api.js +58 -34
- package/src/connections/node-addons.js +11 -1
- package/src/debug/gate.js +1 -1
- package/src/debug/handle.d.ts +6 -1
- package/src/debug/handle.js +14 -6
- package/src/error.d.ts +9 -0
- package/src/error.js +16 -2
- package/src/memory/lock.d.ts +48 -6
- package/src/memory/lock.js +350 -142
- package/src/memory/payloadCodec.js +31 -11
- package/src/permission/protocol.d.ts +1 -0
- package/src/permission/protocol.js +8 -3
- package/src/runtime/dispatcher.d.ts +6 -1
- package/src/runtime/dispatcher.js +24 -8
- package/src/runtime/inline-executor.js +2 -1
- package/src/runtime/pool.d.ts +13 -4
- package/src/runtime/pool.js +107 -47
- package/src/runtime/process-worker.js +10 -1
- package/src/runtime/tx-queue.d.ts +2 -1
- package/src/runtime/tx-queue.js +11 -0
- package/src/runtime/worker-common.d.ts +7 -0
- package/src/runtime/worker-common.js +28 -2
- package/src/types.d.ts +35 -11
- package/src/worker/loop.js +17 -4
- package/src/worker/safety/index.d.ts +1 -1
- package/src/worker/safety/index.js +1 -1
- package/src/worker/safety/process.d.ts +2 -0
- package/src/worker/safety/process.js +8 -1
- package/src/worker/safety/startup.js +11 -6
- package/src/worker/timers.js +25 -3
package/README.md
CHANGED
|
@@ -104,6 +104,9 @@ if (isMain) {
|
|
|
104
104
|
}
|
|
105
105
|
```
|
|
106
106
|
|
|
107
|
+
The examples use `using`, which Node.js 22 cannot parse: there, write
|
|
108
|
+
`const pool = ...` and call `await pool.shutdown()` when you are done.
|
|
109
|
+
|
|
107
110
|
Use the `isMain` guard when a module can be loaded by both the host and its
|
|
108
111
|
workers. Export tasks at module scope so Knitting can find them, then create and
|
|
109
112
|
use the pool only from the main program.
|
|
@@ -153,8 +156,8 @@ if (isMain) {
|
|
|
153
156
|
```
|
|
154
157
|
|
|
155
158
|
`using` starts pool shutdown when the scope exits and does not wait for it. Use
|
|
156
|
-
`await pool.shutdown()` when you need to wait for
|
|
157
|
-
delay.
|
|
159
|
+
`await using pool = ...` or `await pool.shutdown()` when you need to wait for
|
|
160
|
+
shutdown; only `shutdown()` takes a shutdown delay.
|
|
158
161
|
|
|
159
162
|
Deno 2+, Bun 1+, and Node.js 24+ parse `using` natively. Node.js 22 does not: it
|
|
160
163
|
has `Symbol.dispose`, but the declaration itself is a `SyntaxError`, and Node's
|
|
@@ -474,7 +477,11 @@ Common options you might tweak:
|
|
|
474
477
|
| `permission` | Runtime permission policy for workers. |
|
|
475
478
|
| `host.dispatcher` | Experimental host dispatcher topology: `"per-thread"` or `"serial-channel"`. |
|
|
476
479
|
| `host.steal` | Shared-submit work stealing for compatible multi-worker thread/process pools; enabled by default. Set `false` to use private submit lanes. |
|
|
477
|
-
| `
|
|
480
|
+
| `host.stealRegionLanes` | Submit lanes claimed per stealing handshake (a power of two). Smaller regions are fairer for expensive tasks; wider regions amortise arbitration for cheap ones. |
|
|
481
|
+
| `host.stealClaim` | Claim discipline: `"ticket"` (default) or `"dekker"`. Also settable with `KNITTING_STEAL_CLAIM`; an unrecognised value is rejected. |
|
|
482
|
+
| `host.doorbell` | Wait for completion notifications instead of polling an empty return mailbox; enabled by default where supported. Set `false` to force polling. |
|
|
483
|
+
| `host.nativeDoorbell` | Opt into Node's native `uv_async_t` completion bridge for thread workers. Off by default; ignored when `host.doorbell` is `false`. |
|
|
484
|
+
| `debug` | Enable diagnostics (`host`, `globals`, `signals`, `imports`, `lifecycle`, `steal`) or use `KNITTING_DEBUG`. |
|
|
478
485
|
| `source` | Worker source override for advanced runtimes. |
|
|
479
486
|
|
|
480
487
|
### Worker bootstrap
|
|
@@ -530,6 +537,65 @@ protocol limit also fall back. Set `host: { steal: false }` or
|
|
|
530
537
|
where arbitration has nothing to rebalance. `host: { steal: true }` or
|
|
531
538
|
`KNITTING_STEAL=1` forces it for an otherwise compatible pool.
|
|
532
539
|
|
|
540
|
+
Two options tune the arbitration itself, and both only apply to a stealing pool:
|
|
541
|
+
|
|
542
|
+
- `host.stealRegionLanes` is how many submit lanes one handshake claims (a power
|
|
543
|
+
of two). **A region is a batch**: a wide region amortises arbitration best for
|
|
544
|
+
cheap tasks, but it also lets one worker claim work the others could have run
|
|
545
|
+
in parallel. Set it to `1` (or a small value) when per-task cost dominates
|
|
546
|
+
arbitration cost. The default is the widest region the lane budget allows.
|
|
547
|
+
- `host.stealClaim` selects the claim discipline: `"ticket"` (the default)
|
|
548
|
+
claims in publication order from one monotonic counter; `"dekker"` gives each
|
|
549
|
+
consumer its own intent slot and requires at least one region per consumer.
|
|
550
|
+
Set `KNITTING_STEAL_CLAIM=ticket` or `KNITTING_STEAL_CLAIM=dekker` to select
|
|
551
|
+
it from the environment; the explicit option wins. **An unrecognised value is
|
|
552
|
+
an error, not a fallback**: a typo, or a `cas-mask` setting left over from
|
|
553
|
+
when that discipline existed, fails at pool creation rather than quietly
|
|
554
|
+
running a discipline you did not choose.
|
|
555
|
+
|
|
556
|
+
The ticket discipline uses a 64-bit claim head and a wrapping 32-bit
|
|
557
|
+
publication tail. The head CAS validates the tail snapshot: at most 32 tickets
|
|
558
|
+
can be pending, so unsigned subtraction recovers the distance across a tail wrap
|
|
559
|
+
without ever recycling a claim identity. `stealRegionLanes` caps the number of
|
|
560
|
+
tickets one claim takes, and its default width is unchanged from Dekker's.
|
|
561
|
+
Claims are ordered, but workers may decode or complete later claims first.
|
|
562
|
+
Ticket identities never wrap: the queue fails closed if a claim would exhaust
|
|
563
|
+
the signed 64-bit sequence.
|
|
564
|
+
|
|
565
|
+
Ticket is the default while it is under evaluation, so ordinary runs exercise
|
|
566
|
+
it; `"dekker"` remains explicitly selectable and is unchanged. The two differ in
|
|
567
|
+
how they behave after a fatal fault. Dekker releases the region and lets a peer
|
|
568
|
+
finish the rest. **Ticket fails closed**: a fatal decoder or worker failure
|
|
569
|
+
closes the shared queue, rejects outstanding calls, and rejects every future
|
|
570
|
+
call, so that pool has to be shut down and replaced. Claimed work is not
|
|
571
|
+
replayed, because a failed decoder may already have consumed payload state.
|
|
572
|
+
Ordinary task promise rejections stay task-local under both.
|
|
573
|
+
|
|
574
|
+
### Completion doorbells
|
|
575
|
+
|
|
576
|
+
By default the host waits to be told a result is ready instead of repeatedly
|
|
577
|
+
polling an empty return mailbox. `host.doorbell` is enabled wherever a wake path
|
|
578
|
+
exists, and each runtime uses the one it has:
|
|
579
|
+
|
|
580
|
+
- Node and Bun thread workers use `Atomics.waitAsync`.
|
|
581
|
+
- Deno uses a thread-safe FFI callback, because its `waitAsync` does not wake an
|
|
582
|
+
idle event loop. It is skipped when FFI permission is unavailable.
|
|
583
|
+
- Process workers use a process-local completion transport, since Atomics
|
|
584
|
+
waiters are per-isolate and cannot be rung from another process.
|
|
585
|
+
- Anything unsupported or denied falls back to the portable polling path.
|
|
586
|
+
|
|
587
|
+
`host.nativeDoorbell: true` additionally opts Node thread workers into the
|
|
588
|
+
native `uv_async_t` bridge from the `knitting_doorbell` addon. It is off by
|
|
589
|
+
default, does not apply to process workers, and is ignored entirely when
|
|
590
|
+
`host.doorbell` is `false`.
|
|
591
|
+
Node thread permissions must allow native addons for this bridge; otherwise
|
|
592
|
+
Knitting uses the portable wake path.
|
|
593
|
+
|
|
594
|
+
Set `host: { doorbell: false }` to force polling — useful for controlled
|
|
595
|
+
comparisons, and for pools that oversubscribe the machine. A doorbell only makes
|
|
596
|
+
progress when the host gets scheduled, so once workers occupy every core a wake
|
|
597
|
+
has to preempt one.
|
|
598
|
+
|
|
533
599
|
### Useful tuning options
|
|
534
600
|
|
|
535
601
|
- Increase `threads` for parallel CPU-heavy work.
|
|
@@ -795,7 +861,8 @@ const pool = createPool({
|
|
|
795
861
|
|
|
796
862
|
## Permissions
|
|
797
863
|
|
|
798
|
-
Knitting defaults to a strict worker permission policy
|
|
864
|
+
Knitting defaults to a strict worker permission policy where the selected
|
|
865
|
+
runtime supports it:
|
|
799
866
|
|
|
800
867
|
```ts
|
|
801
868
|
permission: { mode: "strict", allowImport: true }
|
|
@@ -853,16 +920,33 @@ backward compatible and produce a once-per-runtime warning.
|
|
|
853
920
|
cannot be represented. When a wrapper or cross-runtime host hides the target
|
|
854
921
|
Node version, Knitting uses the conservative Node 22/24 capability set.
|
|
855
922
|
|
|
856
|
-
These compatibility checks currently cover process workers.
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
923
|
+
These compatibility checks currently cover process workers. Node thread
|
|
924
|
+
workers receive their resolved Node permission flags, and Knitting fails pool
|
|
925
|
+
creation if Node cannot apply them. Deno thread workers inherit the creator's
|
|
926
|
+
permissions because Knitting does not yet set Deno's worker-specific permission
|
|
927
|
+
options, which are unstable and gated by `--unstable-worker-options`; Bun
|
|
928
|
+
thread workers do not have a matching permission mechanism here.
|
|
929
|
+
For cross-runtime permission enforcement, use a process worker with a runtime
|
|
930
|
+
that supports the restrictions you need; Deno has the broadest coverage in the
|
|
931
|
+
table above. Runtime permissions are guardrails, not the only security boundary
|
|
932
|
+
for hostile code.
|
|
860
933
|
|
|
861
934
|
The top-level `ffi` permission is the explicit cross-runtime native-code
|
|
862
935
|
capability. On Node it enables both native addons and `node:ffi`; Node's
|
|
863
936
|
`--allow-ffi` permission is currently unrestricted. The legacy/runtime-specific
|
|
864
937
|
`node.allowAddons` and `node.allowFfi` switches are independent—enabling addons
|
|
865
|
-
does not silently enable FFI.
|
|
938
|
+
does not silently enable FFI. Node thread workers deny addon loading by default
|
|
939
|
+
under the strict policy. Set `permission.node.allowAddons: true` when a task
|
|
940
|
+
needs an addon-backed feature on addon-backed Node versions: `SharedArrayBuffer`
|
|
941
|
+
arguments, returns and `Envelope` bodies, `ProcessSharedBuffer`, and
|
|
942
|
+
`BufferReference`. Node 26 maps the same pointers through `node:ffi`, so there
|
|
943
|
+
the switch is `permission.node.allowFfi`; top-level `ffi: true` grants both.
|
|
944
|
+
Without it a Node thread worker cannot map those pointers: the worker crashes
|
|
945
|
+
and later calls on it fail. Either switch lets task code load native code
|
|
946
|
+
generally, so use it only for trusted tasks. Two paths degrade instead of
|
|
947
|
+
failing when addon loading is denied: large returns are copied rather than
|
|
948
|
+
moved, and the optional native completion doorbell falls back to the portable
|
|
949
|
+
wake path.
|
|
866
950
|
|
|
867
951
|
Node process workers are a transport exception: Knitting needs `--allow-addons`
|
|
868
952
|
on Node 22/24 or `--allow-ffi` on Node 26 to map their shared memory. Deno
|
|
@@ -883,6 +967,13 @@ Knitting aims to make the safer path the default:
|
|
|
883
967
|
- Shutdown can stop immediately or wait for submitted work with
|
|
884
968
|
`worker.resolveAfterFinishingAll`.
|
|
885
969
|
|
|
970
|
+
When Knitting itself rejects a call, the reason is a `KnittingError` with a
|
|
971
|
+
`code`: `KNT_ERROR_0`–`KNT_ERROR_3` for an argument the host cannot encode, and
|
|
972
|
+
`WORKER_STARTUP_FAILED`, `WORKER_CRASHED`, `WORKER_EXITED` or `THREAD_CLOSED`
|
|
973
|
+
when the worker behind the call is gone. Calls to a dead worker reject at once
|
|
974
|
+
instead of staying pending. A return value the worker cannot encode still
|
|
975
|
+
rejects with the bare `KNT_ERROR_n` string.
|
|
976
|
+
|
|
886
977
|
That said, workers still run code. If you treat tasks like plugins, keep
|
|
887
978
|
permissions tight, keep named shared-memory names hard to guess, and avoid
|
|
888
979
|
passing broad capabilities into worker code.
|
|
@@ -1248,7 +1339,7 @@ ownership move on thread workers:
|
|
|
1248
1339
|
|
|
1249
1340
|
| Runtime | Result ownership |
|
|
1250
1341
|
| --- | --- |
|
|
1251
|
-
| Node 22/24 with the addon | The host co-owns the V8 backing store: zero byte copies. |
|
|
1342
|
+
| Node 22/24 with the addon | The host co-owns the V8 backing store: zero byte copies. Under the strict permission policy this needs `permission.node.allowAddons`; otherwise it takes the one-private-copy fallback. |
|
|
1252
1343
|
| Deno and Bun | The host makes one private copy before the worker releases its pin. |
|
|
1253
1344
|
| Older Node backend | The same one-private-copy fallback. |
|
|
1254
1345
|
|
package/knitting.browser.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { workerMainLoop } from "./src/worker/loop.ts";
|
|
2
2
|
import { createPool, importTask, isMain, setModuleUrl, task } from "./src/api.ts";
|
|
3
3
|
import { Envelope } from "./src/common/envelope.ts";
|
|
4
|
+
import { KnittingError } from "./src/error.ts";
|
|
4
5
|
import { isNumericArray, NumericArray } from "./src/connections/numeric-array.ts";
|
|
5
|
-
export { createPool as createPool, Envelope as Envelope, importTask as importTask, isMain as isMain, isNumericArray as isNumericArray, NumericArray as NumericArray, setModuleUrl as setModuleUrl, task as task, workerMainLoop as workerMainLoop, };
|
|
6
|
+
export { createPool as createPool, Envelope as Envelope, importTask as importTask, isMain as isMain, isNumericArray as isNumericArray, KnittingError as KnittingError, NumericArray as NumericArray, setModuleUrl as setModuleUrl, task as task, workerMainLoop as workerMainLoop, };
|
|
6
7
|
export type { EnvelopeBody as EnvelopeBody, EnvelopeHeader as EnvelopeHeader, } from "./src/common/envelope.ts";
|
|
8
|
+
export type { KnittingErrorCode } from "./src/error.ts";
|