@amritk/nish-x86_64-linux 0.28.0 → 0.30.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 +8 -5
- package/bin/nish +0 -0
- package/docs/AI.md +114 -24
- package/package.json +1 -1
- package/runtime/core/host.d.ts +2 -1
- package/runtime/core/host.mjs +4 -0
- package/runtime/nish.d.ts +127 -0
- package/runtime/nish.h +94 -5
- package/runtime/nish.mjs +18 -4
- package/runtime/runtime-net.c +386 -5
- package/runtime/runtime-os.c +59 -8
- package/runtime/runtime-parallel.c +60 -5
- package/runtime/runtime-region.c +231 -0
- package/runtime/runtime-sandbox.c +43 -34
- package/runtime/runtime-shm.c +541 -0
- package/runtime/runtime.c +838 -792
- package/scripts/build.sh +55 -12
- package/std/README.md +12 -3
- package/std/collections.ts +5 -5
- package/std/crypto/hmac.ts +305 -2
- package/std/crypto/jwt.ts +1661 -0
- package/std/json-typed.ts +34 -0
- package/std/json.ts +35 -6
- package/std/net/handler.ts +1348 -0
- package/std/net/http-client.ts +97 -16
- package/std/net/http-compress.ts +54 -12
- package/std/net/http1-client.ts +270 -41
- package/std/net/http1-server.ts +145 -32
- package/std/net/http1.ts +274 -51
- package/std/net/outbound.ts +409 -0
- package/std/net/postgres-carrier.ts +985 -0
- package/std/net/postgres.ts +297 -4
- package/std/net/prefork.ts +344 -20
- package/std/net/upstream.ts +759 -0
- package/std/secret.ts +1 -1
- package/std/sync/ring.ts +587 -0
- package/std/sync/shm.ts +284 -0
- package/std/term-rate.ts +13 -1
- package/std/threads.ts +35 -3
- /package/runtime/{LICENSE-ryu → LICENSE-zmij} +0 -0
package/std/net/prefork.ts
CHANGED
|
@@ -31,7 +31,25 @@
|
|
|
31
31
|
* takes `process.argv` from its caller because only a program with a `main`
|
|
32
32
|
* has one. The environment is inherited whole, and every descriptor the
|
|
33
33
|
* supervisor holds is close-on-exec, so a worker starts with none of them
|
|
34
|
-
* (`docs/security/runtime.md`, RT-17, RT-19).
|
|
34
|
+
* but the channel ends below (`docs/security/runtime.md`, RT-17, RT-19).
|
|
35
|
+
*
|
|
36
|
+
* **Channels.** With `supervisorChannels` on, each worker is started with one
|
|
37
|
+
* end of a fresh `unixSocketPair` (`channelKind`: stream by default, since a
|
|
38
|
+
* seqpacket pair needs Linux and the supervisor panics on one elsewhere) and
|
|
39
|
+
* the supervisor keeps the other; `preforkSupervise` hands each readable end to
|
|
40
|
+
* a callback. With `serviceWorker` set, every other worker gets a pair to that
|
|
41
|
+
* worker. The ends go through `spawnWithFds`, from descriptor 3 in a fixed
|
|
42
|
+
* order: the supervisor channel, then a worker's service channel, or in the
|
|
43
|
+
* service worker its end to each other worker by index, skipping its own. Where
|
|
44
|
+
* they landed rides in `--nish-prefork-channels=` just before the worker flag,
|
|
45
|
+
* and `preforkSupervisorChannel`, `preforkServiceChannel` and
|
|
46
|
+
* `preforkServiceChannelOf` read it back. A pair cannot be handed to a worker
|
|
47
|
+
* already running, so service channels make the workers one group: when any of
|
|
48
|
+
* them exits, the supervisor sends the rest SIGTERM (SIGKILL after `graceMs`),
|
|
49
|
+
* and once none is left, starts them all again on fresh pairs, after the
|
|
50
|
+
* backoff of the one that exited first. No worker keeps an end whose peer is
|
|
51
|
+
* gone longer than that. With `restart` off nothing is signalled; a worker sees
|
|
52
|
+
* its peer's exit as a hang-up.
|
|
35
53
|
*
|
|
36
54
|
* **How many.** `NISH_WORKERS` when it is a positive whole number, else
|
|
37
55
|
* `PreforkConfig.workers` when that is positive, else
|
|
@@ -61,7 +79,16 @@
|
|
|
61
79
|
* Private helpers share the importing program's flat symbol namespace
|
|
62
80
|
* (`docs/wp26-stdlib.md` §3e), which is why each one carries the module's name.
|
|
63
81
|
*/
|
|
64
|
-
import {
|
|
82
|
+
import {
|
|
83
|
+
NET_UNIX_SEQPACKET,
|
|
84
|
+
NET_UNIX_STREAM,
|
|
85
|
+
netClose,
|
|
86
|
+
netRead,
|
|
87
|
+
pollAdd,
|
|
88
|
+
pollCreate,
|
|
89
|
+
pollWait,
|
|
90
|
+
unixSocketPair,
|
|
91
|
+
} from "nish:net"
|
|
65
92
|
import {
|
|
66
93
|
availableParallelism,
|
|
67
94
|
getenv,
|
|
@@ -71,11 +98,19 @@ import {
|
|
|
71
98
|
readSignal,
|
|
72
99
|
signalFd,
|
|
73
100
|
spawn,
|
|
101
|
+
spawnWithFds,
|
|
74
102
|
waitPid,
|
|
75
103
|
} from "nish:process"
|
|
76
104
|
|
|
77
105
|
/** The argument a worker is started with, its index after the `=`. */
|
|
78
106
|
export const PREFORK_WORKER_FLAG: string = "--nish-prefork-worker="
|
|
107
|
+
/**
|
|
108
|
+
* The argument just before the worker flag when the worker has channels: the
|
|
109
|
+
* descriptor of its supervisor channel, of its service channel, then in the
|
|
110
|
+
* service worker of its end to each worker by index, comma-separated, `-1`
|
|
111
|
+
* for none.
|
|
112
|
+
*/
|
|
113
|
+
export const PREFORK_CHANNELS_FLAG: string = "--nish-prefork-channels="
|
|
79
114
|
|
|
80
115
|
/** What `preforkWith` answers in the supervisor once its workers are gone. */
|
|
81
116
|
export const PREFORK_STOPPED: i32 = -1
|
|
@@ -109,6 +144,16 @@ export class PreforkConfig {
|
|
|
109
144
|
* and the supervisor returns once none is left.
|
|
110
145
|
*/
|
|
111
146
|
restart: boolean = true
|
|
147
|
+
/** Give each worker a socketpair to the supervisor (`preforkSupervise`). */
|
|
148
|
+
supervisorChannels: boolean = false
|
|
149
|
+
/** The worker every other worker gets a socketpair to; -1 for none. */
|
|
150
|
+
serviceWorker: i32 = -1
|
|
151
|
+
/**
|
|
152
|
+
* What every channel is: `NET_UNIX_STREAM` (1), or `NET_UNIX_SEQPACKET` (2),
|
|
153
|
+
* which keeps each write one message but only Linux has. Stream is the
|
|
154
|
+
* default because it works on Darwin too.
|
|
155
|
+
*/
|
|
156
|
+
channelKind: i32 = 1
|
|
112
157
|
}
|
|
113
158
|
|
|
114
159
|
/** True when `s` is one or more ASCII digits and nothing else. */
|
|
@@ -160,9 +205,18 @@ export const preforkIndex = (args: string[]): i32 => {
|
|
|
160
205
|
return preforkDigits(digits) ? parseInt(digits) : -1
|
|
161
206
|
}
|
|
162
207
|
|
|
163
|
-
/**
|
|
208
|
+
/**
|
|
209
|
+
* `args` (`process.argv`) without the worker flag and the channels flag before
|
|
210
|
+
* it, the same in every process.
|
|
211
|
+
*/
|
|
164
212
|
export const preforkArgs = (args: string[]): string[] => {
|
|
165
|
-
|
|
213
|
+
let keep: i32 = toI32(args.length)
|
|
214
|
+
if (preforkIndex(args) >= 0) {
|
|
215
|
+
keep = keep - 1
|
|
216
|
+
if (keep > 1 && args[keep - 1].startsWith(PREFORK_CHANNELS_FLAG)) {
|
|
217
|
+
keep = keep - 1
|
|
218
|
+
}
|
|
219
|
+
}
|
|
166
220
|
const out: string[] = []
|
|
167
221
|
for (let i: i32 = 0; i < toI32(args.length); i++) {
|
|
168
222
|
if (i < keep) {
|
|
@@ -172,6 +226,38 @@ export const preforkArgs = (args: string[]): string[] => {
|
|
|
172
226
|
return out
|
|
173
227
|
}
|
|
174
228
|
|
|
229
|
+
/** Field `at` of the channels flag in a worker's `args`, or -1. */
|
|
230
|
+
const preforkChannel = (args: string[], at: i32): i32 => {
|
|
231
|
+
if (preforkIndex(args) < 0 || args.length < 3) {
|
|
232
|
+
return -1
|
|
233
|
+
}
|
|
234
|
+
const flag: string = args[args.length - 2]
|
|
235
|
+
if (!flag.startsWith(PREFORK_CHANNELS_FLAG)) {
|
|
236
|
+
return -1
|
|
237
|
+
}
|
|
238
|
+
let rest: string = flag.substring(PREFORK_CHANNELS_FLAG.length)
|
|
239
|
+
for (let k: i32 = 0; k < at; k++) {
|
|
240
|
+
const comma: i32 = toI32(rest.indexOf(","))
|
|
241
|
+
if (comma < 0) {
|
|
242
|
+
return -1
|
|
243
|
+
}
|
|
244
|
+
rest = rest.substring(comma + 1)
|
|
245
|
+
}
|
|
246
|
+
const comma: i32 = toI32(rest.indexOf(","))
|
|
247
|
+
const field: string = comma < 0 ? rest : rest.substring(0, comma)
|
|
248
|
+
return preforkDigits(field) ? parseInt(field) : -1
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** In a worker, its end of its supervisor channel; -1 when it has none. */
|
|
252
|
+
export const preforkSupervisorChannel = (args: string[]): i32 => preforkChannel(args, 0)
|
|
253
|
+
|
|
254
|
+
/** In a worker, its end of its channel to the service worker; -1 in the service worker or when none. */
|
|
255
|
+
export const preforkServiceChannel = (args: string[]): i32 => preforkChannel(args, 1)
|
|
256
|
+
|
|
257
|
+
/** In the service worker, its end of the channel to worker `index`; -1 for itself or elsewhere. */
|
|
258
|
+
export const preforkServiceChannelOf = (args: string[], index: i32): i32 =>
|
|
259
|
+
index < 0 || index > 2147483645 ? -1 : preforkChannel(args, 2 + index)
|
|
260
|
+
|
|
175
261
|
/** Milliseconds on the monotonic clock. */
|
|
176
262
|
const preforkNowMs = (): i64 => monotonicNanos() / 1000000
|
|
177
263
|
|
|
@@ -189,6 +275,8 @@ class PreforkSlot {
|
|
|
189
275
|
delay: i32 = 0
|
|
190
276
|
/** The worker exited with `restart` off, and is never started again. */
|
|
191
277
|
gone: boolean = false
|
|
278
|
+
/** The supervisor's end of the worker's channel, -1 when it has none. */
|
|
279
|
+
channel: i32 = -1
|
|
192
280
|
}
|
|
193
281
|
|
|
194
282
|
/** The supervisor's state while it runs. */
|
|
@@ -197,31 +285,98 @@ class PreforkSupervisor {
|
|
|
197
285
|
program: string
|
|
198
286
|
args: string[]
|
|
199
287
|
slots: PreforkSlot[]
|
|
288
|
+
/** While the group starts: each worker's end to the service worker, -1 for none. */
|
|
289
|
+
toService: i32[]
|
|
290
|
+
/** While the group starts: the service worker's end to each worker, -1 for none. */
|
|
291
|
+
fromWorkers: i32[]
|
|
292
|
+
/** When a group still running gets SIGKILL. */
|
|
293
|
+
cascadeAt: i64 = 0
|
|
200
294
|
running: i32 = 0
|
|
295
|
+
/** The readiness loop each supervisor channel is added to. */
|
|
296
|
+
loop: i32
|
|
297
|
+
/** `config.serviceWorker`: with it at 0 or above, the workers are one group. */
|
|
298
|
+
service: i32
|
|
299
|
+
/** The group is being stopped, to be started again once none of it is left. */
|
|
300
|
+
cascading: boolean = false
|
|
201
301
|
|
|
202
|
-
constructor(config: PreforkConfig, args: string[], count: i32) {
|
|
302
|
+
constructor(config: PreforkConfig, args: string[], count: i32, loop: i32) {
|
|
203
303
|
this.config = config
|
|
304
|
+
this.loop = loop
|
|
305
|
+
this.service = config.serviceWorker
|
|
306
|
+
this.toService = []
|
|
307
|
+
this.fromWorkers = []
|
|
204
308
|
this.args = preforkArgs(args)
|
|
205
309
|
const self: string | null = platform === "linux" ? realpathSync("/proc/self/exe") : null
|
|
206
310
|
this.program = self !== null ? self : this.args[0]
|
|
207
311
|
this.slots = []
|
|
312
|
+
// No end yet; typed so an f64 build still pushes an i32.
|
|
313
|
+
const none: i32 = -1
|
|
208
314
|
for (let i: i32 = 0; i < count; i++) {
|
|
209
315
|
this.slots.push(new PreforkSlot())
|
|
316
|
+
this.toService.push(none)
|
|
317
|
+
this.fromWorkers.push(none)
|
|
210
318
|
}
|
|
211
319
|
}
|
|
212
320
|
|
|
213
|
-
/**
|
|
321
|
+
/**
|
|
322
|
+
* Start worker `index` with its channel ends, the service ones from
|
|
323
|
+
* `pairs`; false when a socketpair, the loop or the spawn refused it.
|
|
324
|
+
*/
|
|
214
325
|
start(index: i32): boolean {
|
|
326
|
+
const fds: i32[] = []
|
|
327
|
+
let own: i32 = -1
|
|
328
|
+
if (this.config.supervisorChannels) {
|
|
329
|
+
const pair: i32[] = [-1, -1]
|
|
330
|
+
if (unixSocketPair(this.config.channelKind, pair) !== 0) {
|
|
331
|
+
return false
|
|
332
|
+
}
|
|
333
|
+
// On the loop before the worker starts, so a refusal is a failed start.
|
|
334
|
+
if (pollAdd(this.loop, pair[0], 1, index + 1) < 0) {
|
|
335
|
+
netClose(pair[0])
|
|
336
|
+
netClose(pair[1])
|
|
337
|
+
return false
|
|
338
|
+
}
|
|
339
|
+
own = pair[0]
|
|
340
|
+
fds.push(pair[1])
|
|
341
|
+
}
|
|
342
|
+
// Entry i of `fds` is the child's descriptor 3 + i.
|
|
343
|
+
const fields: string[] = [own >= 0 ? "3" : "-1"]
|
|
344
|
+
if (this.service >= 0 && index !== this.service) {
|
|
345
|
+
fds.push(this.toService[index])
|
|
346
|
+
fields.push(`${2 + fds.length}`)
|
|
347
|
+
} else if (this.service >= 0) {
|
|
348
|
+
fields.push("-1")
|
|
349
|
+
for (let i: i32 = 0; i < toI32(this.slots.length); i++) {
|
|
350
|
+
if (i === index) {
|
|
351
|
+
fields.push("-1")
|
|
352
|
+
} else {
|
|
353
|
+
fds.push(this.fromWorkers[i])
|
|
354
|
+
fields.push(`${2 + fds.length}`)
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
215
358
|
const argv: string[] = [this.program]
|
|
216
359
|
for (let i: i32 = 1; i < toI32(this.args.length); i++) {
|
|
217
360
|
argv.push(this.args[i])
|
|
218
361
|
}
|
|
362
|
+
if (fds.length > 0) {
|
|
363
|
+
argv.push(`${PREFORK_CHANNELS_FLAG}${fields.join(",")}`)
|
|
364
|
+
}
|
|
219
365
|
argv.push(`${PREFORK_WORKER_FLAG}${index}`)
|
|
220
|
-
const pid: i32 = spawn(argv)
|
|
366
|
+
const pid: i32 = fds.length > 0 ? spawnWithFds(argv, fds) : spawn(argv)
|
|
367
|
+
if (own >= 0) {
|
|
368
|
+
netClose(fds[0])
|
|
369
|
+
}
|
|
221
370
|
if (pid <= 0) {
|
|
371
|
+
if (own >= 0) {
|
|
372
|
+
netClose(own)
|
|
373
|
+
}
|
|
222
374
|
return false
|
|
223
375
|
}
|
|
224
376
|
const slot: PreforkSlot = this.slots[index]
|
|
377
|
+
if (own >= 0) {
|
|
378
|
+
slot.channel = own
|
|
379
|
+
}
|
|
225
380
|
slot.pid = pid
|
|
226
381
|
slot.at = preforkNowMs()
|
|
227
382
|
this.running = this.running + 1
|
|
@@ -281,21 +436,132 @@ class PreforkSupervisor {
|
|
|
281
436
|
const slot: PreforkSlot = this.slots[index]
|
|
282
437
|
this.running = this.running - 1
|
|
283
438
|
slot.pid = 0
|
|
439
|
+
this.closeChannel(slot)
|
|
284
440
|
if (!this.config.restart) {
|
|
285
441
|
slot.gone = true
|
|
286
442
|
} else if (!stopping) {
|
|
287
|
-
|
|
443
|
+
const now: i64 = preforkNowMs()
|
|
444
|
+
if (this.cascading) {
|
|
445
|
+
// Stopped with its group, so it waits only on the group.
|
|
446
|
+
slot.at = now
|
|
447
|
+
} else {
|
|
448
|
+
this.backoff(slot, now, false)
|
|
449
|
+
if (this.service >= 0) {
|
|
450
|
+
this.cascade(now)
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
return true
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/** Close the supervisor's end of `slot`'s channel, which takes it off the loop. */
|
|
459
|
+
closeChannel(slot: PreforkSlot): void {
|
|
460
|
+
if (slot.channel >= 0) {
|
|
461
|
+
netClose(slot.channel)
|
|
462
|
+
slot.channel = -1
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/** Stop the group so it starts again whole: SIGTERM now, SIGKILL after `graceMs`. */
|
|
467
|
+
cascade(now: i64): void {
|
|
468
|
+
this.cascading = true
|
|
469
|
+
this.cascadeAt = now + toI64(this.config.graceMs)
|
|
470
|
+
this.signal(PREFORK_SIGTERM)
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/** SIGKILL a stopping group that outlived `graceMs`, again each `graceMs`. */
|
|
474
|
+
escalate(): void {
|
|
475
|
+
if (!this.cascading || this.running <= 0) {
|
|
476
|
+
return
|
|
477
|
+
}
|
|
478
|
+
const now: i64 = preforkNowMs()
|
|
479
|
+
if (now >= this.cascadeAt) {
|
|
480
|
+
this.cascadeAt = now + toI64(this.config.graceMs)
|
|
481
|
+
this.signal(PREFORK_SIGKILL)
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/** A pair from each worker to the service worker; false when one failed. */
|
|
486
|
+
pairs(): boolean {
|
|
487
|
+
for (let i: i32 = 0; i < toI32(this.slots.length); i++) {
|
|
488
|
+
if (i !== this.service) {
|
|
489
|
+
const pair: i32[] = [-1, -1]
|
|
490
|
+
if (unixSocketPair(this.config.channelKind, pair) !== 0) {
|
|
491
|
+
return false
|
|
492
|
+
}
|
|
493
|
+
this.toService[i] = pair[0]
|
|
494
|
+
this.fromWorkers[i] = pair[1]
|
|
288
495
|
}
|
|
289
496
|
}
|
|
290
497
|
return true
|
|
291
498
|
}
|
|
292
499
|
|
|
500
|
+
/** Close every end `pairs` made: the workers hold their copies now. */
|
|
501
|
+
closePairs(): void {
|
|
502
|
+
for (let i: i32 = 0; i < toI32(this.slots.length); i++) {
|
|
503
|
+
if (this.toService[i] >= 0) {
|
|
504
|
+
netClose(this.toService[i])
|
|
505
|
+
this.toService[i] = -1
|
|
506
|
+
}
|
|
507
|
+
if (this.fromWorkers[i] >= 0) {
|
|
508
|
+
netClose(this.fromWorkers[i])
|
|
509
|
+
this.fromWorkers[i] = -1
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* With a service worker: once no worker of the group runs and the latest
|
|
516
|
+
* slot's time has come, start them all on fresh pairs. A failure answers
|
|
517
|
+
* false when `first`; otherwise it backs off the slot that failed and stops
|
|
518
|
+
* the workers it started, to be tried again whole.
|
|
519
|
+
*/
|
|
520
|
+
restartGroup(first: boolean): boolean {
|
|
521
|
+
if (this.running > 0) {
|
|
522
|
+
return true
|
|
523
|
+
}
|
|
524
|
+
const now: i64 = preforkNowMs()
|
|
525
|
+
let due: i64 = 0
|
|
526
|
+
let waiting = false
|
|
527
|
+
for (let i: i32 = 0; i < toI32(this.slots.length); i++) {
|
|
528
|
+
if (!this.slots[i].gone) {
|
|
529
|
+
waiting = true
|
|
530
|
+
due = this.slots[i].at > due ? this.slots[i].at : due
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
if (!waiting || due > now) {
|
|
534
|
+
return true
|
|
535
|
+
}
|
|
536
|
+
this.cascading = false
|
|
537
|
+
let failed: i32 = this.pairs() ? -1 : this.service
|
|
538
|
+
for (let i: i32 = 0; i < toI32(this.slots.length) && failed < 0; i++) {
|
|
539
|
+
if (!this.start(i)) {
|
|
540
|
+
failed = i
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
this.closePairs()
|
|
544
|
+
if (failed < 0) {
|
|
545
|
+
return true
|
|
546
|
+
}
|
|
547
|
+
if (first) {
|
|
548
|
+
return false
|
|
549
|
+
}
|
|
550
|
+
console.error(`nish/net/prefork: cannot restart the workers at worker ${failed}`)
|
|
551
|
+
this.backoff(this.slots[failed], now, true)
|
|
552
|
+
this.cascade(now)
|
|
553
|
+
return true
|
|
554
|
+
}
|
|
555
|
+
|
|
293
556
|
/**
|
|
294
557
|
* Start every waiting worker whose time has come. When `first`, answer false
|
|
295
558
|
* at the first `spawn` that fails; otherwise a failed start waits on the
|
|
296
559
|
* same backoff as a worker that exited at once, and the answer is true.
|
|
297
560
|
*/
|
|
298
561
|
restart(first: boolean): boolean {
|
|
562
|
+
if (this.service >= 0) {
|
|
563
|
+
return this.restartGroup(first)
|
|
564
|
+
}
|
|
299
565
|
const now: i64 = preforkNowMs()
|
|
300
566
|
for (let i: i32 = 0; i < toI32(this.slots.length); i++) {
|
|
301
567
|
const slot: PreforkSlot = this.slots[i]
|
|
@@ -324,9 +590,13 @@ class PreforkSupervisor {
|
|
|
324
590
|
* worker is left. Two signals that arrive before the supervisor reads the
|
|
325
591
|
* first count as the second here too. Every reap goes through `reap`, so a
|
|
326
592
|
* child that is not a worker is skipped in both phases, and the SIGKILL
|
|
327
|
-
* phase ends only when the last worker is reaped or no child is left.
|
|
593
|
+
* phase ends only when the last worker is reaped or no child is left. The
|
|
594
|
+
* supervisor's channel ends are closed first, so only `signals` wakes it.
|
|
328
595
|
*/
|
|
329
596
|
stop(loop: i32, signals: i32, ready: i32[]): void {
|
|
597
|
+
for (let i: i32 = 0; i < toI32(this.slots.length); i++) {
|
|
598
|
+
this.closeChannel(this.slots[i])
|
|
599
|
+
}
|
|
330
600
|
this.signal(PREFORK_SIGTERM)
|
|
331
601
|
const deadline: i64 = preforkNowMs() + toI64(this.config.graceMs)
|
|
332
602
|
this.reap(true)
|
|
@@ -364,9 +634,27 @@ const preforkInvalid = (config: PreforkConfig): string => {
|
|
|
364
634
|
if (config.quickExitMs < 0 || config.graceMs < 0) {
|
|
365
635
|
return "quickExitMs or graceMs below 0"
|
|
366
636
|
}
|
|
637
|
+
if (config.channelKind !== NET_UNIX_STREAM && config.channelKind !== NET_UNIX_SEQPACKET) {
|
|
638
|
+
return "channelKind not NET_UNIX_STREAM or NET_UNIX_SEQPACKET"
|
|
639
|
+
}
|
|
640
|
+
const channels = config.supervisorChannels || config.serviceWorker >= 0
|
|
641
|
+
if (channels && config.channelKind === NET_UNIX_SEQPACKET && platform !== "linux") {
|
|
642
|
+
return "channelKind NET_UNIX_SEQPACKET: seqpacket channels need Linux"
|
|
643
|
+
}
|
|
644
|
+
if (config.serviceWorker < -1) {
|
|
645
|
+
return "serviceWorker below -1"
|
|
646
|
+
}
|
|
367
647
|
return ""
|
|
368
648
|
}
|
|
369
649
|
|
|
650
|
+
/** Read and drop what a channel holds: `preforkWith`'s callback. */
|
|
651
|
+
const preforkDiscard = (buf: u8[], _index: i32, fd: i32, _events: i32): void => {
|
|
652
|
+
const size: i32 = toI32(buf.length)
|
|
653
|
+
while (netRead(fd, buf, 0, size) > 0) {
|
|
654
|
+
// Dropped.
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
370
658
|
/**
|
|
371
659
|
* In a worker, its index. In the supervisor, run the workers until SIGTERM or
|
|
372
660
|
* SIGINT and answer `PREFORK_STOPPED`; with `restart` off, also until every
|
|
@@ -374,9 +662,32 @@ const preforkInvalid = (config: PreforkConfig): string => {
|
|
|
374
662
|
* `PREFORK_SPAWN_FAILED` when a worker's
|
|
375
663
|
* first start failed (those already started are stopped first) or no signal
|
|
376
664
|
* listener could be made (no worker is started). Panics on a config
|
|
377
|
-
* `preforkInvalid` names a field of.
|
|
665
|
+
* `preforkInvalid` names a field of. Supervisor channels are read and
|
|
666
|
+
* dropped; `preforkSupervise` hands them to the program.
|
|
378
667
|
*/
|
|
379
668
|
export const preforkWith = (args: string[], config: PreforkConfig): i32 => {
|
|
669
|
+
const buf: u8[] = new Array<u8>(512)
|
|
670
|
+
return preforkSupervise(args, config, buf, preforkDiscard)
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* `preforkWith`, calling `onChannel(state, index, fd, events)` in the
|
|
675
|
+
* supervisor whenever worker `index`'s supervisor channel `fd` is readable or
|
|
676
|
+
* hung up: `events` is `pollWait`'s, 1 readable and 4 added on a hang-up or
|
|
677
|
+
* error. The callback reads what is waiting, since an end left readable wakes
|
|
678
|
+
* the loop again at once, and may write back, but never closes `fd`: the
|
|
679
|
+
* supervisor closes it after a hang-up, when the worker is reaped and when it
|
|
680
|
+
* stops, and a restarted worker comes with a new one. Panics, besides, when
|
|
681
|
+
* `serviceWorker` is not below the worker count or the count is above 64 with
|
|
682
|
+
* a service worker, the most `spawnWithFds` hands one child; and, through
|
|
683
|
+
* `preforkInvalid`, on seqpacket channels off Linux.
|
|
684
|
+
*/
|
|
685
|
+
export const preforkSupervise = <S>(
|
|
686
|
+
args: string[],
|
|
687
|
+
config: PreforkConfig,
|
|
688
|
+
state: S,
|
|
689
|
+
onChannel: (state: S, index: i32, fd: i32, events: i32) => void
|
|
690
|
+
): i32 => {
|
|
380
691
|
const invalid: string = preforkInvalid(config)
|
|
381
692
|
if (invalid !== "") {
|
|
382
693
|
panic(`nish/net/prefork: ${invalid}`)
|
|
@@ -385,6 +696,10 @@ export const preforkWith = (args: string[], config: PreforkConfig): i32 => {
|
|
|
385
696
|
if (index >= 0) {
|
|
386
697
|
return index
|
|
387
698
|
}
|
|
699
|
+
const count: i32 = preforkWorkerCount(config.workers, getenv("NISH_WORKERS"))
|
|
700
|
+
if (config.serviceWorker >= count || (config.serviceWorker >= 0 && count > 64)) {
|
|
701
|
+
panic(`nish/net/prefork: serviceWorker not below ${count} workers, or above 64 workers`)
|
|
702
|
+
}
|
|
388
703
|
const signals: i32 = signalFd()
|
|
389
704
|
const loop: i32 = pollCreate()
|
|
390
705
|
if (signals < 0 || loop < 0 || pollAdd(loop, signals, 1, 0) < 0) {
|
|
@@ -396,12 +711,9 @@ export const preforkWith = (args: string[], config: PreforkConfig): i32 => {
|
|
|
396
711
|
}
|
|
397
712
|
return PREFORK_SPAWN_FAILED
|
|
398
713
|
}
|
|
399
|
-
const supervisor = new PreforkSupervisor(
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
preforkWorkerCount(config.workers, getenv("NISH_WORKERS"))
|
|
403
|
-
)
|
|
404
|
-
const ready: i32[] = new Array<i32>(2)
|
|
714
|
+
const supervisor = new PreforkSupervisor(config, args, count, loop)
|
|
715
|
+
// Token 0 is the signal pipe, token i + 1 worker i's channel.
|
|
716
|
+
const ready: i32[] = new Array<i32>(2 * count + 2)
|
|
405
717
|
let result: i32 = PREFORK_STOPPED
|
|
406
718
|
if (!supervisor.restart(true)) {
|
|
407
719
|
console.error(`nish/net/prefork: cannot start ${supervisor.program}`)
|
|
@@ -410,15 +722,27 @@ export const preforkWith = (args: string[], config: PreforkConfig): i32 => {
|
|
|
410
722
|
let running = true
|
|
411
723
|
while (running) {
|
|
412
724
|
const n: i32 = pollWait(loop, ready, config.tickMs)
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
725
|
+
for (let k: i32 = 0; k < n; k++) {
|
|
726
|
+
const token: i32 = ready[2 * k]
|
|
727
|
+
const events: i32 = ready[2 * k + 1]
|
|
728
|
+
if (token === 0) {
|
|
729
|
+
readSignal(signals)
|
|
730
|
+
running = false
|
|
731
|
+
} else if (supervisor.slots[token - 1].channel >= 0) {
|
|
732
|
+
const slot: PreforkSlot = supervisor.slots[token - 1]
|
|
733
|
+
onChannel(state, token - 1, slot.channel, events)
|
|
734
|
+
if ((events & 4) !== 0) {
|
|
735
|
+
supervisor.closeChannel(slot)
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
if (running) {
|
|
417
740
|
supervisor.reap(false)
|
|
418
741
|
if (!config.restart && supervisor.running === 0) {
|
|
419
742
|
result = PREFORK_WORKERS_EXITED
|
|
420
743
|
running = false
|
|
421
744
|
}
|
|
745
|
+
supervisor.escalate()
|
|
422
746
|
supervisor.restart(false)
|
|
423
747
|
}
|
|
424
748
|
}
|