knitting 0.1.46 → 0.1.50
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 +274 -77
- package/map.md +9 -3
- package/package.json +2 -1
- package/prebuilds/darwin-arm64-node-127/knitting_shared_memory.node +0 -0
- package/prebuilds/darwin-arm64-node-137/knitting_shared_memory.node +0 -0
- package/prebuilds/darwin-x64-node-127/knitting_shared_memory.node +0 -0
- package/prebuilds/darwin-x64-node-137/knitting_shared_memory.node +0 -0
- package/prebuilds/linux-x64-node-127/knitting_shared_memory.node +0 -0
- package/prebuilds/linux-x64-node-137/knitting_shared_memory.node +0 -0
- package/prebuilds/win32-x64/knitting_windows_shared_memory.dll +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_shared_memory.node +0 -0
- package/prebuilds/win32-x64-node-137/knitting_shm.node +0 -0
- package/scripts/build-native-addons.ts +31 -5
- package/src/api.js +37 -13
- package/src/common/task-source.d.ts +1 -0
- package/src/common/task-source.js +1 -0
- package/src/connections/bun.d.ts +2 -0
- package/src/connections/bun.js +64 -9
- package/src/connections/deno.d.ts +2 -0
- package/src/connections/deno.js +64 -9
- package/src/connections/file-descriptor.d.ts +2 -0
- package/src/connections/file-descriptor.js +24 -2
- package/src/connections/node.d.ts +2 -1
- package/src/connections/node.js +17 -14
- package/src/connections/posix.d.ts +1 -0
- package/src/connections/posix.js +6 -0
- package/src/connections/process-shared-buffer.d.ts +3 -1
- package/src/connections/process-shared-buffer.js +17 -13
- package/src/connections/types.d.ts +2 -0
- package/src/connections/windows.d.ts +28 -0
- package/src/connections/windows.js +224 -0
- package/src/knitting_shared_memory.cc +310 -24
- package/src/knitting_windows_shared_memory.cc +156 -0
- package/src/memory/payloadCodec.js +1 -0
- package/src/runtime/pool.d.ts +1 -13
- package/src/runtime/pool.js +10 -542
- package/src/runtime/process-worker.d.ts +92 -0
- package/src/runtime/process-worker.js +670 -0
- package/src/runtime/tx-queue.d.ts +1 -1
- package/src/runtime/tx-queue.js +3 -1
- package/src/shared/abortSignal.d.ts +1 -1
- package/src/shared/abortSignal.js +10 -2
- package/src/types.d.ts +49 -3
- package/src/worker/bootstrap.d.ts +5 -0
- package/src/worker/bootstrap.js +78 -0
- package/src/worker/composable-runners.js +0 -8
- package/src/worker/loop.js +19 -154
- package/src/worker/process-worker-bootstrap.d.ts +8 -0
- package/src/worker/process-worker-bootstrap.js +159 -0
- package/src/worker/timers.js +19 -5
package/README.md
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://jsr.io/@vixeny/knitting)
|
|
4
4
|
[](https://jsr.io/@vixeny/knitting)
|
|
5
|
+
[](https://www.npmjs.com/package/knitting)
|
|
5
6
|
[](https://github.com/mimiMonads/knitting/actions/workflows/test.yml)
|
|
6
7
|
[](https://github.com/mimiMonads/knitting/actions/workflows/coverage.yml)
|
|
7
8
|
[](https://github.com/mimiMonads/knitting/actions/workflows/coverage.yml)
|
|
@@ -10,45 +11,37 @@
|
|
|
10
11
|
[](https://deno.com/)
|
|
11
12
|
[](https://bun.sh/)
|
|
12
13
|
|
|
13
|
-
Knitting is a worker
|
|
14
|
-
Deno, and Bun. Which mission is make javascript a real multicore language,
|
|
15
|
-
Thanks to its memory design, it can be from 5x to 25x faster than using `postMessages`
|
|
16
|
-
bypassing OS sockect comunnication entielly with a novel protocol written from scratch.
|
|
14
|
+
Knitting is a worker pool over a shared-memory IPC runtime for Node.js, Deno, and Bun. Our mission is to make JavaScript a multicore language with real inter-runtime communication.
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
work, runtime-isolated jobs, long-running tools, or anything that needs to move
|
|
20
|
-
speed and type felixbility.
|
|
16
|
+
Thanks to its memory design, it can be 5x to 25x faster than using `postMessages` , bypassing OS socket communication entirely with a novel protocol written from scratch.
|
|
21
17
|
|
|
18
|
+
Use it for parts of your program that should run in different environments, such as CPU-intensive tasks, small jobs, runtime-isolated tasks, custom isolation for workers in Docker or bwrap environments, long-running tools, or any processes that require speed and type flexibility.
|
|
22
19
|
|
|
23
|
-
You define a task once, spin up a pool, and call it like a normal async
|
|
24
|
-
function:
|
|
20
|
+
You define a task once, spin up a pool, and call it like a normal async function:
|
|
25
21
|
|
|
26
22
|
```ts
|
|
23
|
+
|
|
27
24
|
const result = await pool.call.resizeImage(file);
|
|
25
|
+
|
|
28
26
|
```
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
So you only have to take care of 4 things:
|
|
29
|
+
- Create a task
|
|
30
|
+
- Create a pool
|
|
31
|
+
- Call your task
|
|
32
|
+
- Terminate the pool
|
|
33
|
+
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
Under the hood, we take care of scheduling and orchestration across worker threads or separate processes, also handling signals, timeouts, life cycles, memory allocation, garbage collection, and cross-runtime memory over the processes.
|
|
35
36
|
|
|
36
|
-
- Call worker code like `pool.call.myTask(arg)`.
|
|
37
|
-
- Keep the resolved types end-to-end
|
|
38
|
-
- Choose `threads` for speed or `processes` for stronger isolation.
|
|
39
|
-
- Move supported payloads via shared memory (great for binary data).
|
|
40
|
-
- Run on Node.js, Deno, or Bun.
|
|
41
37
|
|
|
42
38
|
## Why use it?
|
|
43
39
|
|
|
44
|
-
- Easy to use: Have a
|
|
45
|
-
- Great type support: pass primitives, JSON, Promise of these and special types (typed arrays, Node
|
|
46
|
-
`Buffer`, `Envelope`, and `ProcessSharedBuffer`).
|
|
40
|
+
- Easy to use: Have a multithreaded environment or process with few lines of code.
|
|
41
|
+
- Great type support: pass primitives, JSON, Promise of these, and special types (typed arrays, `Node Buffer`, `Envelope`, and `ProcessSharedBuffer`).
|
|
47
42
|
- Runtime flexibility: the same API across Node.js, Deno, and Bun.
|
|
48
|
-
- Worker choices: use threads for fast pools
|
|
49
|
-
|
|
50
|
-
- All out-of-the-box expierince: strict-by-default permissions, payload-size limits, task
|
|
51
|
-
timeouts, abort-aware tasks, and worker hard timeouts.
|
|
43
|
+
- Worker choices: use threads for fast pools or processes for stronger isolation.
|
|
44
|
+
- All out-of-the-box experiences: strict-by-default permissions, payload-size limits, task timeouts, abort-aware tasks, and worker hard timeouts.
|
|
52
45
|
|
|
53
46
|
## Requirements
|
|
54
47
|
|
|
@@ -56,27 +49,18 @@ uses shared memory for transport.
|
|
|
56
49
|
- Deno 2+
|
|
57
50
|
- Bun 1+
|
|
58
51
|
|
|
59
|
-
Process workers and `ProcessSharedBuffer` use the native shared-memory layer.
|
|
60
|
-
On Linux and macOS, build it before running process/shared-memory tests.
|
|
61
|
-
If you're only using thread workers, you can typically ignore this and use
|
|
62
|
-
Knitting without building native addons.
|
|
63
|
-
|
|
64
52
|
## Install
|
|
65
53
|
|
|
66
|
-
|
|
67
|
-
npm install knitting
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
Via JSR's npm compatibility:
|
|
54
|
+
From npm:
|
|
71
55
|
|
|
72
56
|
```bash
|
|
73
|
-
|
|
57
|
+
npm install knitting
|
|
74
58
|
```
|
|
75
59
|
|
|
76
60
|
For Deno projects:
|
|
77
61
|
|
|
78
62
|
```bash
|
|
79
|
-
deno add jsr:@vixeny/knitting
|
|
63
|
+
deno add --npm jsr:@vixeny/knitting
|
|
80
64
|
```
|
|
81
65
|
|
|
82
66
|
## Quick Start
|
|
@@ -301,12 +285,37 @@ Common options you might tweak:
|
|
|
301
285
|
| `payload` | Shared payload-buffer settings: `mode`, `payloadInitialBytes`, `payloadMaxByteLength`, and `maxPayloadBytes`. |
|
|
302
286
|
| `abortSignalCapacity` | Number of shared abort slots available to abort-aware calls. |
|
|
303
287
|
| `worker.resolveAfterFinishingAll` | Let submitted calls finish before shutdown resolves. |
|
|
288
|
+
| `worker.bootstrap` | Privileged async hook imported and awaited before task modules load. |
|
|
304
289
|
| `worker.hardTimeoutMs` | Force pool shutdown when a task exceeds this many milliseconds. |
|
|
305
290
|
| `worker.runtime` | Choose `"thread"` or `"process"` workers. |
|
|
291
|
+
| `worker.processSharedMemory` | Process-worker memory discovery: `"inherit"` by default on POSIX, or `"named"` for wrappers/containers that cannot preserve fd 0. |
|
|
306
292
|
| `permission` | Runtime permission policy for workers. |
|
|
307
293
|
| `debug` | Enable extra diagnostics. |
|
|
308
294
|
| `source` | Worker source override for advanced runtimes. |
|
|
309
295
|
|
|
296
|
+
### Worker bootstrap
|
|
297
|
+
|
|
298
|
+
Use `worker.bootstrap` when a worker needs privileged setup before task modules
|
|
299
|
+
are imported. The bootstrap module is imported once per worker, and its selected
|
|
300
|
+
export is awaited before Knitting imports task definitions.
|
|
301
|
+
|
|
302
|
+
```ts
|
|
303
|
+
const pool = createPool({
|
|
304
|
+
worker: {
|
|
305
|
+
bootstrap: {
|
|
306
|
+
href: "./worker-bootstrap.ts",
|
|
307
|
+
name: "setup",
|
|
308
|
+
data: { env: "worker-only" },
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
})({ add });
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Bootstrap code runs with worker startup privileges, so keep it trusted. It is a
|
|
315
|
+
good place to remove environment variables, install runtime guards, open shared
|
|
316
|
+
memory metadata, or prepare globals that task modules should see at import time.
|
|
317
|
+
Bootstrap is worker-only and cannot be combined with the inline host lane.
|
|
318
|
+
|
|
310
319
|
## Worker Runtimes
|
|
311
320
|
|
|
312
321
|
By default, workers use runtime-local threads where possible (the lowest
|
|
@@ -326,14 +335,15 @@ const pool = createPool({
|
|
|
326
335
|
threads: 2,
|
|
327
336
|
worker: {
|
|
328
337
|
runtime: "process",
|
|
329
|
-
processRuntime: "
|
|
338
|
+
processRuntime: "deno",
|
|
330
339
|
},
|
|
331
340
|
})({ add });
|
|
332
341
|
```
|
|
333
342
|
|
|
334
|
-
`processRuntime` can be `"node"`, `"deno"`, or `"bun"
|
|
335
|
-
`processCommandPrefix` when workers need to be
|
|
336
|
-
as a package manager, container command, or
|
|
343
|
+
`processRuntime` can be `"node"`, `"deno"`, or `"bun"` and defaults to
|
|
344
|
+
`"deno"`. You can also provide a `processCommandPrefix` when workers need to be
|
|
345
|
+
launched through a wrapper such as a package manager, container command, or
|
|
346
|
+
runtime shim.
|
|
337
347
|
|
|
338
348
|
That prefix is also useful for sandbox and resource-control tools. The one
|
|
339
349
|
important detail is that process workers receive their shared-memory handle on
|
|
@@ -341,6 +351,68 @@ stdin, which is file descriptor 0. Wrappers that leave stdin alone usually work;
|
|
|
341
351
|
wrappers that replace, close, or proxy stdin without passing the fd through will
|
|
342
352
|
stop the worker from booting.
|
|
343
353
|
|
|
354
|
+
For wrappers that cannot preserve fd 0, use named process-worker memory instead.
|
|
355
|
+
The worker process must share the same OS IPC namespace as the host so it can
|
|
356
|
+
reopen the named mapping.
|
|
357
|
+
|
|
358
|
+
```ts
|
|
359
|
+
const pool = createPool({
|
|
360
|
+
threads: 2,
|
|
361
|
+
worker: {
|
|
362
|
+
runtime: "process",
|
|
363
|
+
processRuntime: "node",
|
|
364
|
+
processSharedMemory: {
|
|
365
|
+
mode: "named",
|
|
366
|
+
namePrefix: "knit_worker",
|
|
367
|
+
},
|
|
368
|
+
processCommandPrefix: [
|
|
369
|
+
// The prefix runs before Knitting appends:
|
|
370
|
+
// node --no-warnings --experimental-transform-types <worker-file>
|
|
371
|
+
"docker",
|
|
372
|
+
"run",
|
|
373
|
+
// Remove the container when the worker exits.
|
|
374
|
+
"--rm",
|
|
375
|
+
// Required for named POSIX shared memory across host/container.
|
|
376
|
+
"--ipc=host",
|
|
377
|
+
// The worker imports the same files as the host, at the same path.
|
|
378
|
+
"-v",
|
|
379
|
+
`${process.cwd()}:${process.cwd()}`,
|
|
380
|
+
"-w",
|
|
381
|
+
process.cwd(),
|
|
382
|
+
// Forward Knitting's process-worker boot metadata into the container.
|
|
383
|
+
"-e",
|
|
384
|
+
"KNITTING_PROCESS_WORKER",
|
|
385
|
+
"-e",
|
|
386
|
+
"KNITTING_PROCESS_WORKER_BOOT",
|
|
387
|
+
"knitting-node-worker",
|
|
388
|
+
],
|
|
389
|
+
},
|
|
390
|
+
})({ add });
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
### Windows process workers
|
|
394
|
+
|
|
395
|
+
On Windows, Knitting automatically uses named shared memory for the
|
|
396
|
+
process-worker control channel. You do not need to set
|
|
397
|
+
`processSharedMemory: "named"` yourself — the runtime detects Windows and
|
|
398
|
+
forces it.
|
|
399
|
+
|
|
400
|
+
```ts
|
|
401
|
+
// Works on Windows without extra options.
|
|
402
|
+
const pool = createPool({
|
|
403
|
+
threads: 2,
|
|
404
|
+
worker: {
|
|
405
|
+
runtime: "process",
|
|
406
|
+
processRuntime: "node",
|
|
407
|
+
},
|
|
408
|
+
})({ add });
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
If you also pass `ProcessSharedBuffer` payloads to Docker workers running on
|
|
412
|
+
Windows, create the payload buffer with `mode: "create"` and a name, and add
|
|
413
|
+
`--ipc=host` to the Docker prefix — the pool-level control channel is already
|
|
414
|
+
named, but the payload buffer needs its own name so the container can reopen it.
|
|
415
|
+
|
|
344
416
|
When the goal is isolation, define the worker code with `importTask()` instead
|
|
345
417
|
of importing the task function directly into the host. That keeps the code you
|
|
346
418
|
want to isolate out of the host process; only the worker imports and runs it.
|
|
@@ -436,6 +508,41 @@ shouldn't) cross the boundary:
|
|
|
436
508
|
- Objects with behavior that depends on prototypes, getters, setters, or hidden
|
|
437
509
|
process-local state.
|
|
438
510
|
|
|
511
|
+
### Envelope
|
|
512
|
+
|
|
513
|
+
`Envelope` pairs a JSON-serializable header with a binary `ArrayBuffer`
|
|
514
|
+
payload. Use it when a call needs both structured metadata and raw bytes in a
|
|
515
|
+
single argument.
|
|
516
|
+
|
|
517
|
+
```ts
|
|
518
|
+
import { Envelope, createPool, isMain, task } from "knitting";
|
|
519
|
+
|
|
520
|
+
export const processImage = task<
|
|
521
|
+
Envelope<{ format: string }>,
|
|
522
|
+
Envelope<{ width: number; height: number }>
|
|
523
|
+
>({
|
|
524
|
+
f: (envelope) => {
|
|
525
|
+
const pixels = new Uint8Array(envelope.payload);
|
|
526
|
+
// ... process pixels
|
|
527
|
+
return new Envelope({ width: 800, height: 600 }, pixels.buffer);
|
|
528
|
+
},
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
if (isMain) {
|
|
532
|
+
const pool = createPool({ threads: 2 })({ processImage });
|
|
533
|
+
|
|
534
|
+
try {
|
|
535
|
+
const buffer = new ArrayBuffer(1024);
|
|
536
|
+
const result = await pool.call.processImage(
|
|
537
|
+
new Envelope({ format: "png" }, buffer),
|
|
538
|
+
);
|
|
539
|
+
console.log(result.header); // { width: 800, height: 600 }
|
|
540
|
+
} finally {
|
|
541
|
+
await pool.shutdown();
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
```
|
|
545
|
+
|
|
439
546
|
If a payload is large, set `payload.maxPayloadBytes` deliberately and prefer
|
|
440
547
|
binary/shared-memory shapes over deeply nested objects.
|
|
441
548
|
|
|
@@ -446,7 +553,10 @@ memory. Use it when two workers or processes need to see the same bytes without
|
|
|
446
553
|
copying the whole payload for every call.
|
|
447
554
|
|
|
448
555
|
```ts
|
|
449
|
-
import {
|
|
556
|
+
import {
|
|
557
|
+
getDefaultProcessSharedBufferPrimitives,
|
|
558
|
+
ProcessSharedBuffer,
|
|
559
|
+
} from "knitting/process-shared-buffer";
|
|
450
560
|
import { createPool, isMain, task } from "knitting";
|
|
451
561
|
|
|
452
562
|
export const readFirstCell = task<ProcessSharedBuffer, number>({
|
|
@@ -455,13 +565,14 @@ export const readFirstCell = task<ProcessSharedBuffer, number>({
|
|
|
455
565
|
|
|
456
566
|
if (isMain) {
|
|
457
567
|
const pool = createPool({ threads: 1 })({ readFirstCell });
|
|
458
|
-
const
|
|
568
|
+
const primitives = getDefaultProcessSharedBufferPrimitives();
|
|
569
|
+
const shared = ProcessSharedBuffer.create(64, primitives);
|
|
459
570
|
|
|
460
571
|
try {
|
|
461
572
|
Atomics.store(shared.view(Int32Array), 0, 42);
|
|
462
573
|
console.log(await pool.call.readFirstCell(shared));
|
|
463
574
|
} finally {
|
|
464
|
-
shared.close();
|
|
575
|
+
shared.descriptor.mapping?.close?.();
|
|
465
576
|
await pool.shutdown();
|
|
466
577
|
}
|
|
467
578
|
}
|
|
@@ -482,19 +593,24 @@ programs do not accidentally inherit them.
|
|
|
482
593
|
|
|
483
594
|
### Named channels for independent processes
|
|
484
595
|
|
|
485
|
-
|
|
486
|
-
|
|
596
|
+
Use a named channel when two processes need to find the same shared memory
|
|
597
|
+
without inheriting an fd from each other. One process creates the channel by
|
|
598
|
+
name; the other opens that same name.
|
|
487
599
|
|
|
488
600
|
```ts
|
|
489
|
-
import {
|
|
601
|
+
import {
|
|
602
|
+
getDefaultProcessSharedBufferPrimitives,
|
|
603
|
+
ProcessSharedBuffer,
|
|
604
|
+
} from "knitting/process-shared-buffer";
|
|
490
605
|
|
|
491
606
|
const name = "knitting-demo-channel";
|
|
607
|
+
const primitives = getDefaultProcessSharedBufferPrimitives();
|
|
492
608
|
|
|
493
609
|
const owner = ProcessSharedBuffer.create({
|
|
494
610
|
name,
|
|
495
611
|
size: 64,
|
|
496
612
|
mode: "create",
|
|
497
|
-
});
|
|
613
|
+
}, primitives);
|
|
498
614
|
|
|
499
615
|
try {
|
|
500
616
|
Atomics.store(owner.view(Int32Array), 0, 7);
|
|
@@ -503,46 +619,121 @@ try {
|
|
|
503
619
|
name,
|
|
504
620
|
size: 64,
|
|
505
621
|
mode: "open",
|
|
506
|
-
});
|
|
622
|
+
}, primitives);
|
|
507
623
|
|
|
508
624
|
try {
|
|
509
625
|
console.log(Atomics.load(peer.view(Int32Array), 0));
|
|
510
626
|
} finally {
|
|
511
|
-
peer.close();
|
|
627
|
+
peer.descriptor.mapping?.close?.();
|
|
512
628
|
}
|
|
513
629
|
} finally {
|
|
514
|
-
owner.close();
|
|
515
|
-
|
|
630
|
+
owner.descriptor.mapping?.close?.();
|
|
631
|
+
primitives.unlinkSharedMemory?.(name);
|
|
516
632
|
}
|
|
517
633
|
```
|
|
518
634
|
|
|
519
|
-
Use `"create"`
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
635
|
+
Use `"create"` on the owner side and `"open"` on the peer side. The name is the
|
|
636
|
+
thing that grants access, so generate a hard-to-guess name and keep it private.
|
|
637
|
+
When you are done, close the mappings and unlink the name where the runtime
|
|
638
|
+
supports it.
|
|
639
|
+
|
|
640
|
+
### Sending `ProcessSharedBuffer` to Docker workers
|
|
641
|
+
|
|
642
|
+
Docker process workers can receive a `ProcessSharedBuffer`, but it needs to be
|
|
643
|
+
named. The default anonymous form is fd-backed and private to the parent-child
|
|
644
|
+
process path; Docker does not inherit that fd in a way the worker can reopen.
|
|
645
|
+
|
|
646
|
+
Use a named buffer for the payload and named process-worker memory for the pool:
|
|
647
|
+
|
|
648
|
+
```ts
|
|
649
|
+
import { createPool, isMain, task } from "knitting";
|
|
650
|
+
import {
|
|
651
|
+
getDefaultProcessSharedBufferPrimitives,
|
|
652
|
+
ProcessSharedBuffer,
|
|
653
|
+
} from "knitting/process-shared-buffer";
|
|
654
|
+
|
|
655
|
+
export const readCounter = task<ProcessSharedBuffer, number>({
|
|
656
|
+
f: (shared) => Atomics.load(shared.view(Int32Array), 0),
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
if (isMain) {
|
|
660
|
+
const cwd = process.cwd();
|
|
661
|
+
const name = `knitting-docker-counter-${process.pid}`;
|
|
662
|
+
const primitives = getDefaultProcessSharedBufferPrimitives();
|
|
663
|
+
const shared = ProcessSharedBuffer.create({
|
|
664
|
+
mode: "create",
|
|
665
|
+
name,
|
|
666
|
+
size: 64,
|
|
667
|
+
}, primitives);
|
|
668
|
+
|
|
669
|
+
const pool = createPool({
|
|
670
|
+
threads: 1,
|
|
671
|
+
worker: {
|
|
672
|
+
runtime: "process",
|
|
673
|
+
processRuntime: "node",
|
|
674
|
+
processSharedMemory: "named",
|
|
675
|
+
processCommandPrefix: [
|
|
676
|
+
// Knitting appends the actual Node worker command after this prefix.
|
|
677
|
+
"docker",
|
|
678
|
+
"run",
|
|
679
|
+
// Named shared memory needs a shared IPC namespace.
|
|
680
|
+
"--ipc=host",
|
|
681
|
+
// Mount the project so the container can import the worker module.
|
|
682
|
+
"-v",
|
|
683
|
+
`${cwd}:${cwd}`,
|
|
684
|
+
"-w",
|
|
685
|
+
cwd,
|
|
686
|
+
// Pass Knitting's boot payload through Docker.
|
|
687
|
+
"-e",
|
|
688
|
+
"KNITTING_PROCESS_WORKER",
|
|
689
|
+
"-e",
|
|
690
|
+
"KNITTING_PROCESS_WORKER_BOOT",
|
|
691
|
+
"node:24-trixie-slim",
|
|
692
|
+
],
|
|
693
|
+
},
|
|
694
|
+
permission: "unsafe",
|
|
695
|
+
})({ readCounter });
|
|
696
|
+
|
|
697
|
+
try {
|
|
698
|
+
Atomics.store(shared.view(Int32Array), 0, 42);
|
|
699
|
+
console.log(await pool.call.readCounter(shared));
|
|
700
|
+
} finally {
|
|
701
|
+
await pool.shutdown();
|
|
702
|
+
shared.descriptor.mapping?.close?.();
|
|
703
|
+
primitives.unlinkSharedMemory?.(name);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
There are three moving parts:
|
|
709
|
+
|
|
710
|
+
- `processSharedMemory: "named"` lets the Docker worker find Knitting's control
|
|
711
|
+
channel.
|
|
712
|
+
- `ProcessSharedBuffer.create({ mode: "create", name, size })` makes the
|
|
713
|
+
payload buffer reopenable by name.
|
|
714
|
+
- `--ipc=host` lets the container see the same POSIX shared-memory namespace.
|
|
715
|
+
|
|
716
|
+
This is same-host communication. It is fast because both sides map the same
|
|
717
|
+
bytes, but it is not a network transport and it deliberately shares IPC with the
|
|
718
|
+
container. Use names like capabilities: generate them, keep them private, and
|
|
719
|
+
unlink them when the shared memory is no longer needed.
|
|
525
720
|
|
|
526
721
|
### Current support
|
|
527
722
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
| Deno 2+ on Linux/macOS, runtime-supported arch | Supported | Supported | Supported | Uses Deno FFI into libc; allow FFI permission when permissions are enabled. |
|
|
543
|
-
| Deno 2+ on Windows | Supported | Not supported | Not supported | Current Deno backend is POSIX-only. |
|
|
544
|
-
| Bun 1+ on Linux/macOS, runtime-supported arch | Supported | Supported | Supported | Uses Bun FFI into libc. |
|
|
545
|
-
| Bun 1+ on Windows | Supported | Not supported | Not supported | Current Bun backend is POSIX-only. |
|
|
723
|
+
Knitting supports Node.js 22+, Deno 2+, and Bun 1+ on Linux, macOS, and
|
|
724
|
+
Windows.
|
|
725
|
+
|
|
726
|
+
Thread workers work without native pieces. Process workers and
|
|
727
|
+
`ProcessSharedBuffer` use the platform's shared-memory APIs. Release packages
|
|
728
|
+
include the native prebuilds needed for the supported Node targets and Windows
|
|
729
|
+
FFI path; if you are developing locally on a new Node ABI or architecture, run:
|
|
730
|
+
|
|
731
|
+
```bash
|
|
732
|
+
bun run build:native
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
For Deno projects with permissions enabled, allow FFI when using process
|
|
736
|
+
workers or `ProcessSharedBuffer`.
|
|
546
737
|
|
|
547
738
|
## Runtime Safety
|
|
548
739
|
|
|
@@ -604,7 +795,7 @@ Build the package:
|
|
|
604
795
|
bun run build
|
|
605
796
|
```
|
|
606
797
|
|
|
607
|
-
Build the native shared-memory addon
|
|
798
|
+
Build the native shared-memory addon/prebuild for the current platform:
|
|
608
799
|
|
|
609
800
|
```bash
|
|
610
801
|
bun run build:native
|
|
@@ -625,6 +816,12 @@ Emit JSON benchmark results:
|
|
|
625
816
|
./run.sh --json
|
|
626
817
|
```
|
|
627
818
|
|
|
819
|
+
Compare inherited-fd and named-shared-memory process-worker startup:
|
|
820
|
+
|
|
821
|
+
```bash
|
|
822
|
+
node --no-warnings --experimental-transform-types bench/startup.ts --named-process-shm
|
|
823
|
+
```
|
|
824
|
+
|
|
628
825
|
For a file-by-file orientation, see [map.md](./map.md).
|
|
629
826
|
|
|
630
827
|
## License
|
package/map.md
CHANGED
|
@@ -33,8 +33,9 @@ The core flow is:
|
|
|
33
33
|
|
|
34
34
|
- `build.ts`: Bundles `knitting.ts` to `out/` with Bun for a Node ESM target.
|
|
35
35
|
- `scripts/build-native-addons.ts`: Compiles the native Node addons into
|
|
36
|
-
`build/Release/` on Linux and
|
|
37
|
-
flags,
|
|
36
|
+
`build/Release/` on Linux, macOS, and Windows. It finds Node headers/libs,
|
|
37
|
+
splits user flags, builds the shared-memory and futex addons, and emits the
|
|
38
|
+
Windows FFI DLL used by Deno and Bun.
|
|
38
39
|
- `run.sh`: Runs every top-level benchmark in `bench/` across Node, Deno, and
|
|
39
40
|
Bun. `--json` writes JSON result files.
|
|
40
41
|
|
|
@@ -159,10 +160,15 @@ The core flow is:
|
|
|
159
160
|
- `src/connections/deno.ts`: Deno FFI implementation for POSIX shared memory.
|
|
160
161
|
- `src/connections/posix.ts`: POSIX constants, shared-memory naming, libc path
|
|
161
162
|
detection, and close-on-exec helpers.
|
|
163
|
+
- `src/connections/windows.ts`: Windows FFI loader and named file-mapping
|
|
164
|
+
primitives used by Deno and Bun.
|
|
162
165
|
- `src/knitting_shared_memory.cc`: Native Node addon for shared-memory create,
|
|
163
|
-
map, unlink, and descriptor operations.
|
|
166
|
+
map, unlink, and descriptor operations, including Windows named mappings.
|
|
164
167
|
- `src/knitting_shm.cc`: Native Node addon for futex/wait helpers used by parked
|
|
165
168
|
workers.
|
|
169
|
+
- `src/knitting_windows_shared_memory.cc`: Runtime-neutral Windows DLL exports
|
|
170
|
+
for creating, opening, mapping, and closing named shared-memory objects from
|
|
171
|
+
FFI runtimes.
|
|
166
172
|
|
|
167
173
|
## Permissions
|
|
168
174
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "knitting",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.50",
|
|
4
4
|
"description": "Shared-memory IPC runtime for Node.js, Deno, and Bun.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"map.md",
|
|
31
31
|
"LICENSE",
|
|
32
32
|
"prebuilds/**/*.node",
|
|
33
|
+
"prebuilds/**/*.dll",
|
|
33
34
|
"scripts/build-native-addons.ts",
|
|
34
35
|
"src/**/*.cc",
|
|
35
36
|
"src/**/*.d.ts",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -70,11 +70,6 @@ const nodeInfo = JSON.parse(runCapture(nodeBinary, [
|
|
|
70
70
|
"JSON.stringify({arch:process.arch,execPath:process.execPath,modules:process.versions.modules,nodedir:process.config.variables.nodedir||null,platform:process.platform,version:process.versions.node})",
|
|
71
71
|
])) as NodeInfo;
|
|
72
72
|
const isWindows = nodeInfo.platform === "win32";
|
|
73
|
-
if (isWindows) {
|
|
74
|
-
throw new Error(
|
|
75
|
-
"Knitting native shared-memory addons are supported on Linux and macOS only",
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
73
|
const cacheRoot = join(
|
|
79
74
|
resolve(
|
|
80
75
|
Bun.env.KNITTING_NODE_CACHE_DIR ??
|
|
@@ -238,11 +233,25 @@ const addons = [
|
|
|
238
233
|
output: "build/Release/knitting_shm.node",
|
|
239
234
|
},
|
|
240
235
|
];
|
|
236
|
+
const ffiLibraries = isWindows
|
|
237
|
+
? [
|
|
238
|
+
{
|
|
239
|
+
name: "knitting_windows_shared_memory",
|
|
240
|
+
source: "src/knitting_windows_shared_memory.cc",
|
|
241
|
+
output: "build/Release/knitting_windows_shared_memory.dll",
|
|
242
|
+
},
|
|
243
|
+
]
|
|
244
|
+
: [];
|
|
241
245
|
const prebuildDir = join(
|
|
242
246
|
root,
|
|
243
247
|
"prebuilds",
|
|
244
248
|
`${nodeInfo.platform}-${nodeInfo.arch}-node-${nodeInfo.modules}`,
|
|
245
249
|
);
|
|
250
|
+
const ffiPrebuildDir = join(
|
|
251
|
+
root,
|
|
252
|
+
"prebuilds",
|
|
253
|
+
`${nodeInfo.platform}-${nodeInfo.arch}`,
|
|
254
|
+
);
|
|
246
255
|
|
|
247
256
|
console.log(`Using Node: ${nodeInfo.execPath}`);
|
|
248
257
|
console.log(`Using Node module ABI: ${nodeInfo.modules}`);
|
|
@@ -282,6 +291,23 @@ for (const addon of addons) {
|
|
|
282
291
|
copiedPrebuilds.push(prebuildPath);
|
|
283
292
|
}
|
|
284
293
|
|
|
294
|
+
for (const library of ffiLibraries) {
|
|
295
|
+
const outputPath = join(root, library.output);
|
|
296
|
+
run(cxx, [
|
|
297
|
+
...compileFlags,
|
|
298
|
+
join(root, library.source),
|
|
299
|
+
"/link",
|
|
300
|
+
`/OUT:${outputPath}`,
|
|
301
|
+
...extraLdFlags,
|
|
302
|
+
]);
|
|
303
|
+
builtAddons.push(library.output);
|
|
304
|
+
|
|
305
|
+
mkdirSync(ffiPrebuildDir, { recursive: true });
|
|
306
|
+
const prebuildPath = join(ffiPrebuildDir, `${library.name}.dll`);
|
|
307
|
+
copyFileSync(outputPath, prebuildPath);
|
|
308
|
+
copiedPrebuilds.push(prebuildPath);
|
|
309
|
+
}
|
|
310
|
+
|
|
285
311
|
console.log(
|
|
286
312
|
`Built ${builtAddons.length} native addon${
|
|
287
313
|
builtAddons.length === 1 ? "" : "s"
|