@amritk/nish-x86_64-linux 0.29.0 → 0.31.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/INSTALL.md +3 -3
- package/bin/nish +0 -0
- package/docs/AI.md +71 -4
- package/package.json +1 -1
- package/runtime/core/host.d.ts +2 -0
- package/runtime/core/host.mjs +9 -0
- package/runtime/nish.d.ts +48 -2
- package/runtime/nish.h +31 -3
- package/runtime/nish.mjs +36 -0
- package/runtime/runtime-net.c +32 -3
- package/runtime/runtime-os.c +33 -1
- package/runtime/runtime-region.c +243 -0
- package/runtime/runtime.c +19 -2
- package/scripts/build.sh +19 -6
- package/std/README.md +10 -7
- package/std/net/{http-client.ts → fetch.ts} +17 -17
- package/std/net/handler.ts +1 -1
- package/std/net/{http1-client.ts → http1-client-codec.ts} +276 -47
- package/std/net/{http1.ts → http1-codec.ts} +252 -49
- package/std/net/http1-server.ts +72 -13
- package/std/net/outbound.ts +2 -2
- package/std/net/postgres-carrier.ts +985 -0
- package/std/net/postgres.ts +680 -53
- package/std/net/prefork.ts +149 -13
- package/std/net/router.ts +1 -1
- package/std/net/upstream.ts +759 -0
- package/std/net/websocket.ts +5 -5
- package/std/region.ts +95 -0
package/std/net/prefork.ts
CHANGED
|
@@ -51,6 +51,26 @@
|
|
|
51
51
|
* gone longer than that. With `restart` off nothing is signalled; a worker sees
|
|
52
52
|
* its peer's exit as a hang-up.
|
|
53
53
|
*
|
|
54
|
+
* **Listeners and pinning.** With `listenPort` set (0 or above), the
|
|
55
|
+
* supervisor binds one `SO_REUSEPORT` listener per worker itself, in index
|
|
56
|
+
* order, keeps them for its whole run and hands listener `k` to every start of
|
|
57
|
+
* worker `k` after the channel ends; `preforkListener(args)` answers it in the
|
|
58
|
+
* worker (`--nish-prefork-listener=`, before the channels flag), so a worker
|
|
59
|
+
* serves that descriptor instead of binding its own. Port 0 binds the first
|
|
60
|
+
* listener anywhere and the rest to its port. With `pin` on as well, each
|
|
61
|
+
* listener steers by CPU (`tcpListenFlags`' flag 4): the kernel hands a
|
|
62
|
+
* connection that arrived on CPU `k` to listener `k`, and a worker that is
|
|
63
|
+
* restarted takes the same listener, so the order holds across restarts,
|
|
64
|
+
* which it would not if each worker bound its own. `pin` alone makes each
|
|
65
|
+
* worker call `setCpuAffinity(k)` before `preforkWith` answers it, so worker
|
|
66
|
+
* `k` runs on CPU `k` only; a worker the kernel refuses (CPU `k` outside the
|
|
67
|
+
* process's mask) says so on stderr and runs unpinned. More workers than
|
|
68
|
+
* `availableParallelism()` is refused with a panic, since a worker past the
|
|
69
|
+
* last CPU would be pinned nowhere and, steered, handed nothing. Pinning and
|
|
70
|
+
* steering are Linux's: elsewhere the supervisor says once that the workers
|
|
71
|
+
* run unpinned, and the listeners are plain `SO_REUSEPORT` ones. Both are off
|
|
72
|
+
* by default.
|
|
73
|
+
*
|
|
54
74
|
* **How many.** `NISH_WORKERS` when it is a positive whole number, else
|
|
55
75
|
* `PreforkConfig.workers` when that is positive, else
|
|
56
76
|
* `availableParallelism()` on Linux and 1 elsewhere: Darwin shares a
|
|
@@ -83,10 +103,12 @@ import {
|
|
|
83
103
|
NET_UNIX_SEQPACKET,
|
|
84
104
|
NET_UNIX_STREAM,
|
|
85
105
|
netClose,
|
|
106
|
+
netLocalPort,
|
|
86
107
|
netRead,
|
|
87
108
|
pollAdd,
|
|
88
109
|
pollCreate,
|
|
89
110
|
pollWait,
|
|
111
|
+
tcpListenFlags,
|
|
90
112
|
unixSocketPair,
|
|
91
113
|
} from "nish:net"
|
|
92
114
|
import {
|
|
@@ -96,6 +118,7 @@ import {
|
|
|
96
118
|
monotonicNanos,
|
|
97
119
|
platform,
|
|
98
120
|
readSignal,
|
|
121
|
+
setCpuAffinity,
|
|
99
122
|
signalFd,
|
|
100
123
|
spawn,
|
|
101
124
|
spawnWithFds,
|
|
@@ -111,6 +134,12 @@ export const PREFORK_WORKER_FLAG: string = "--nish-prefork-worker="
|
|
|
111
134
|
* for none.
|
|
112
135
|
*/
|
|
113
136
|
export const PREFORK_CHANNELS_FLAG: string = "--nish-prefork-channels="
|
|
137
|
+
/**
|
|
138
|
+
* The argument before the channels flag, or before the worker flag when there
|
|
139
|
+
* is none, when the supervisor holds the listeners: the worker's listener's
|
|
140
|
+
* descriptor.
|
|
141
|
+
*/
|
|
142
|
+
export const PREFORK_LISTENER_FLAG: string = "--nish-prefork-listener="
|
|
114
143
|
|
|
115
144
|
/** What `preforkWith` answers in the supervisor once its workers are gone. */
|
|
116
145
|
export const PREFORK_STOPPED: i32 = -1
|
|
@@ -127,6 +156,8 @@ export const PREFORK_WORKERS_EXITED: i32 = -3
|
|
|
127
156
|
|
|
128
157
|
/** The supervisor's numbers; every one is checked by `preforkWith`. */
|
|
129
158
|
export class PreforkConfig {
|
|
159
|
+
/** The address the listeners of `listenPort` bind. */
|
|
160
|
+
listenHost: string = "0.0.0.0"
|
|
130
161
|
/** Workers to start; 0 takes `NISH_WORKERS` or the platform's default. */
|
|
131
162
|
workers: i32 = 0
|
|
132
163
|
/** How often, in milliseconds, the supervisor reaps when nothing wakes it. */
|
|
@@ -139,6 +170,23 @@ export class PreforkConfig {
|
|
|
139
170
|
quickExitMs: i32 = 1000
|
|
140
171
|
/** How long the workers get to exit after SIGTERM before SIGKILL. */
|
|
141
172
|
graceMs: i32 = 5000
|
|
173
|
+
/** The worker every other worker gets a socketpair to; -1 for none. */
|
|
174
|
+
serviceWorker: i32 = -1
|
|
175
|
+
/**
|
|
176
|
+
* What every channel is: `NET_UNIX_STREAM` (1), or `NET_UNIX_SEQPACKET` (2),
|
|
177
|
+
* which keeps each write one message but only Linux has. Stream is the
|
|
178
|
+
* default because it works on Darwin too.
|
|
179
|
+
*/
|
|
180
|
+
channelKind: i32 = 1
|
|
181
|
+
/**
|
|
182
|
+
* The port the supervisor binds one listener per worker on, 0 for any; -1,
|
|
183
|
+
* the default, leaves each worker to bind its own.
|
|
184
|
+
*/
|
|
185
|
+
listenPort: i32 = -1
|
|
186
|
+
/** Their backlog. */
|
|
187
|
+
listenBacklog: i32 = 1024
|
|
188
|
+
/** Their `tcpListenFlags` beyond `SO_REUSEPORT`, which is always set: 0, or 2 for `TCP_NODELAY`. */
|
|
189
|
+
listenFlags: i32 = 0
|
|
142
190
|
/**
|
|
143
191
|
* Start a worker that exited again. Off, a worker that exits stays gone,
|
|
144
192
|
* and the supervisor returns once none is left.
|
|
@@ -146,14 +194,14 @@ export class PreforkConfig {
|
|
|
146
194
|
restart: boolean = true
|
|
147
195
|
/** Give each worker a socketpair to the supervisor (`preforkSupervise`). */
|
|
148
196
|
supervisorChannels: boolean = false
|
|
149
|
-
/** The worker every other worker gets a socketpair to; -1 for none. */
|
|
150
|
-
serviceWorker: i32 = -1
|
|
151
197
|
/**
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
198
|
+
* Pin worker `k` to CPU `k` (`setCpuAffinity`), and with `listenPort`, steer
|
|
199
|
+
* each connection to the listener of the CPU that received it. Linux only;
|
|
200
|
+
* elsewhere the workers run unpinned. `k` is the CPU number, not the `k`-th
|
|
201
|
+
* allowed CPU: under a mask that skips CPU `k`, worker `k` runs unpinned and
|
|
202
|
+
* a connection received past the listeners falls back to the kernel's hash.
|
|
155
203
|
*/
|
|
156
|
-
|
|
204
|
+
pin: boolean = false
|
|
157
205
|
}
|
|
158
206
|
|
|
159
207
|
/** True when `s` is one or more ASCII digits and nothing else. */
|
|
@@ -206,8 +254,8 @@ export const preforkIndex = (args: string[]): i32 => {
|
|
|
206
254
|
}
|
|
207
255
|
|
|
208
256
|
/**
|
|
209
|
-
* `args` (`process.argv`) without the worker flag and the channels
|
|
210
|
-
* it, the same in every process.
|
|
257
|
+
* `args` (`process.argv`) without the worker flag and the channels and
|
|
258
|
+
* listener flags before it, the same in every process.
|
|
211
259
|
*/
|
|
212
260
|
export const preforkArgs = (args: string[]): string[] => {
|
|
213
261
|
let keep: i32 = toI32(args.length)
|
|
@@ -216,6 +264,9 @@ export const preforkArgs = (args: string[]): string[] => {
|
|
|
216
264
|
if (keep > 1 && args[keep - 1].startsWith(PREFORK_CHANNELS_FLAG)) {
|
|
217
265
|
keep = keep - 1
|
|
218
266
|
}
|
|
267
|
+
if (keep > 1 && args[keep - 1].startsWith(PREFORK_LISTENER_FLAG)) {
|
|
268
|
+
keep = keep - 1
|
|
269
|
+
}
|
|
219
270
|
}
|
|
220
271
|
const out: string[] = []
|
|
221
272
|
for (let i: i32 = 0; i < toI32(args.length); i++) {
|
|
@@ -258,6 +309,21 @@ export const preforkServiceChannel = (args: string[]): i32 => preforkChannel(arg
|
|
|
258
309
|
export const preforkServiceChannelOf = (args: string[], index: i32): i32 =>
|
|
259
310
|
index < 0 || index > 2147483645 ? -1 : preforkChannel(args, 2 + index)
|
|
260
311
|
|
|
312
|
+
/** In a worker, the listener the supervisor bound for it (`listenPort`); -1 when none. */
|
|
313
|
+
export const preforkListener = (args: string[]): i32 => {
|
|
314
|
+
if (preforkIndex(args) < 0) {
|
|
315
|
+
return -1
|
|
316
|
+
}
|
|
317
|
+
for (let back: i32 = 2; back <= 3 && back < toI32(args.length); back++) {
|
|
318
|
+
const flag: string = args[toI32(args.length) - back]
|
|
319
|
+
if (flag.startsWith(PREFORK_LISTENER_FLAG)) {
|
|
320
|
+
const digits: string = flag.substring(PREFORK_LISTENER_FLAG.length)
|
|
321
|
+
return preforkDigits(digits) ? parseInt(digits) : -1
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return -1
|
|
325
|
+
}
|
|
326
|
+
|
|
261
327
|
/** Milliseconds on the monotonic clock. */
|
|
262
328
|
const preforkNowMs = (): i64 => monotonicNanos() / 1000000
|
|
263
329
|
|
|
@@ -298,6 +364,8 @@ class PreforkSupervisor {
|
|
|
298
364
|
service: i32
|
|
299
365
|
/** The group is being stopped, to be started again once none of it is left. */
|
|
300
366
|
cascading: boolean = false
|
|
367
|
+
/** Worker i's listener at i, held for the whole run; empty without `listenPort`. */
|
|
368
|
+
listeners: i32[]
|
|
301
369
|
|
|
302
370
|
constructor(config: PreforkConfig, args: string[], count: i32, loop: i32) {
|
|
303
371
|
this.config = config
|
|
@@ -305,6 +373,7 @@ class PreforkSupervisor {
|
|
|
305
373
|
this.service = config.serviceWorker
|
|
306
374
|
this.toService = []
|
|
307
375
|
this.fromWorkers = []
|
|
376
|
+
this.listeners = []
|
|
308
377
|
this.args = preforkArgs(args)
|
|
309
378
|
const self: string | null = platform === "linux" ? realpathSync("/proc/self/exe") : null
|
|
310
379
|
this.program = self !== null ? self : this.args[0]
|
|
@@ -359,7 +428,12 @@ class PreforkSupervisor {
|
|
|
359
428
|
for (let i: i32 = 1; i < toI32(this.args.length); i++) {
|
|
360
429
|
argv.push(this.args[i])
|
|
361
430
|
}
|
|
362
|
-
|
|
431
|
+
const channels: boolean = fds.length > 0
|
|
432
|
+
if (this.listeners.length > 0) {
|
|
433
|
+
fds.push(this.listeners[index])
|
|
434
|
+
argv.push(`${PREFORK_LISTENER_FLAG}${2 + fds.length}`)
|
|
435
|
+
}
|
|
436
|
+
if (channels) {
|
|
363
437
|
argv.push(`${PREFORK_CHANNELS_FLAG}${fields.join(",")}`)
|
|
364
438
|
}
|
|
365
439
|
argv.push(`${PREFORK_WORKER_FLAG}${index}`)
|
|
@@ -576,6 +650,37 @@ class PreforkSupervisor {
|
|
|
576
650
|
return true
|
|
577
651
|
}
|
|
578
652
|
|
|
653
|
+
/**
|
|
654
|
+
* One listener per worker, in index order, which is the order steering
|
|
655
|
+
* counts CPUs in; false, with the ones made closed, when one cannot bind.
|
|
656
|
+
*/
|
|
657
|
+
listen(steer: boolean): boolean {
|
|
658
|
+
const config: PreforkConfig = this.config
|
|
659
|
+
const flags: i32 = config.listenFlags | 1 | (steer ? 4 : 0)
|
|
660
|
+
let port: i32 = config.listenPort
|
|
661
|
+
for (let i: i32 = 0; i < toI32(this.slots.length); i++) {
|
|
662
|
+
const fd: i32 = tcpListenFlags(config.listenHost, port, config.listenBacklog, flags)
|
|
663
|
+
if (fd < 0) {
|
|
664
|
+
console.error(`nish/net/prefork: cannot listen on ${config.listenHost}:${port} (${fd})`)
|
|
665
|
+
this.closeListeners()
|
|
666
|
+
return false
|
|
667
|
+
}
|
|
668
|
+
this.listeners.push(fd)
|
|
669
|
+
if (port === 0) {
|
|
670
|
+
port = netLocalPort(fd)
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
return true
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/** Close the listeners `listen` made. */
|
|
677
|
+
closeListeners(): void {
|
|
678
|
+
for (let i: i32 = 0; i < toI32(this.listeners.length); i++) {
|
|
679
|
+
netClose(this.listeners[i])
|
|
680
|
+
}
|
|
681
|
+
this.listeners = []
|
|
682
|
+
}
|
|
683
|
+
|
|
579
684
|
/** Send `sig` to every running worker. */
|
|
580
685
|
signal(sig: i32): void {
|
|
581
686
|
for (let i: i32 = 0; i < toI32(this.slots.length); i++) {
|
|
@@ -644,6 +749,15 @@ const preforkInvalid = (config: PreforkConfig): string => {
|
|
|
644
749
|
if (config.serviceWorker < -1) {
|
|
645
750
|
return "serviceWorker below -1"
|
|
646
751
|
}
|
|
752
|
+
if (config.listenPort < -1 || config.listenPort > 65535) {
|
|
753
|
+
return "listenPort not -1 or 0..65535"
|
|
754
|
+
}
|
|
755
|
+
if (config.listenBacklog <= 0) {
|
|
756
|
+
return "listenBacklog not above 0"
|
|
757
|
+
}
|
|
758
|
+
if (config.listenFlags !== 0 && config.listenFlags !== 2) {
|
|
759
|
+
return "listenFlags not 0 or 2"
|
|
760
|
+
}
|
|
647
761
|
return ""
|
|
648
762
|
}
|
|
649
763
|
|
|
@@ -679,8 +793,11 @@ export const preforkWith = (args: string[], config: PreforkConfig): i32 => {
|
|
|
679
793
|
* supervisor closes it after a hang-up, when the worker is reaped and when it
|
|
680
794
|
* stops, and a restarted worker comes with a new one. Panics, besides, when
|
|
681
795
|
* `serviceWorker` is not below the worker count or the count is above 64 with
|
|
682
|
-
* a service worker, the most `spawnWithFds` hands one
|
|
683
|
-
* `
|
|
796
|
+
* a service worker (63 with `listenPort`), the most `spawnWithFds` hands one
|
|
797
|
+
* child; when `pin`, on Linux, asks for more workers than
|
|
798
|
+
* `availableParallelism()`; and, through `preforkInvalid`, on seqpacket
|
|
799
|
+
* channels off Linux. A listener that cannot bind answers
|
|
800
|
+
* `PREFORK_SPAWN_FAILED` before any worker starts.
|
|
684
801
|
*/
|
|
685
802
|
export const preforkSupervise = <S>(
|
|
686
803
|
args: string[],
|
|
@@ -692,13 +809,27 @@ export const preforkSupervise = <S>(
|
|
|
692
809
|
if (invalid !== "") {
|
|
693
810
|
panic(`nish/net/prefork: ${invalid}`)
|
|
694
811
|
}
|
|
812
|
+
const pin: boolean = config.pin && platform === "linux"
|
|
695
813
|
const index: i32 = preforkIndex(args)
|
|
696
814
|
if (index >= 0) {
|
|
815
|
+
const pinned: i32 = pin ? setCpuAffinity(index) : 0
|
|
816
|
+
if (pinned < 0) {
|
|
817
|
+
console.error(`nish/net/prefork: worker ${index} runs unpinned (${pinned})`)
|
|
818
|
+
}
|
|
697
819
|
return index
|
|
698
820
|
}
|
|
699
821
|
const count: i32 = preforkWorkerCount(config.workers, getenv("NISH_WORKERS"))
|
|
700
|
-
|
|
701
|
-
|
|
822
|
+
// A held listener is one more descriptor for every worker, the service one too.
|
|
823
|
+
const fdMax: i32 = config.listenPort >= 0 ? 63 : 64
|
|
824
|
+
if (config.serviceWorker >= count || (config.serviceWorker >= 0 && count > fdMax)) {
|
|
825
|
+
panic(`nish/net/prefork: serviceWorker not below ${count} workers, or above ${fdMax} workers`)
|
|
826
|
+
}
|
|
827
|
+
const cpus: i32 = pin ? availableParallelism() : 0
|
|
828
|
+
if (pin && count > cpus) {
|
|
829
|
+
panic(`nish/net/prefork: pin with ${count} workers on ${cpus} CPUs`)
|
|
830
|
+
}
|
|
831
|
+
if (config.pin && !pin) {
|
|
832
|
+
console.error("nish/net/prefork: pinning needs Linux; the workers run unpinned")
|
|
702
833
|
}
|
|
703
834
|
const signals: i32 = signalFd()
|
|
704
835
|
const loop: i32 = pollCreate()
|
|
@@ -712,6 +843,10 @@ export const preforkSupervise = <S>(
|
|
|
712
843
|
return PREFORK_SPAWN_FAILED
|
|
713
844
|
}
|
|
714
845
|
const supervisor = new PreforkSupervisor(config, args, count, loop)
|
|
846
|
+
if (config.listenPort >= 0 && !supervisor.listen(pin)) {
|
|
847
|
+
netClose(loop)
|
|
848
|
+
return PREFORK_SPAWN_FAILED
|
|
849
|
+
}
|
|
715
850
|
// Token 0 is the signal pipe, token i + 1 worker i's channel.
|
|
716
851
|
const ready: i32[] = new Array<i32>(2 * count + 2)
|
|
717
852
|
let result: i32 = PREFORK_STOPPED
|
|
@@ -748,6 +883,7 @@ export const preforkSupervise = <S>(
|
|
|
748
883
|
}
|
|
749
884
|
}
|
|
750
885
|
supervisor.stop(loop, signals, ready)
|
|
886
|
+
supervisor.closeListeners()
|
|
751
887
|
netClose(loop)
|
|
752
888
|
return result
|
|
753
889
|
}
|
package/std/net/router.ts
CHANGED
|
@@ -69,7 +69,7 @@ export const ROUTER_BAD_ESCAPE: i32 = -10
|
|
|
69
69
|
/** `routerDecode`: the decoded bytes do not fit `out`; nothing was written. */
|
|
70
70
|
export const ROUTER_NO_ROOM: i32 = -11
|
|
71
71
|
|
|
72
|
-
/** The longest method `add` takes, as `nish/net/http1`'s `HTTP1_MAX_METHOD`. */
|
|
72
|
+
/** The longest method `add` takes, as `nish/net/http1-codec`'s `HTTP1_MAX_METHOD`. */
|
|
73
73
|
const ROUTER_MAX_METHOD: i32 = 32
|
|
74
74
|
/** No node or route: an `i32` spelled once, so that `--number-mode f64` does not read a bare `-1` as a double. */
|
|
75
75
|
const ROUTER_NONE: i32 = -1
|