@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.
@@ -0,0 +1,985 @@
1
+ /**
2
+ * nish/net/postgres-carrier: `PgCarrier`, a `PgPool`'s queries on an
3
+ * `HttpHandler`'s loop, so a route handler queries PostgreSQL without
4
+ * blocking its worker.
5
+ *
6
+ * `nish/net/postgres` reads no socket, clock or entropy source; this module is
7
+ * the one that does, and it is kept apart so that one stays sans-IO. The
8
+ * carrier's sockets sit in an epoll set the handler watches (`HttpHandler.watch`), a
9
+ * route handler hands a request to `run` (or to `runInt` and `runText` for an
10
+ * attached `PgBatcher`), and `next()` answers `PG_CARRIER_BUILD` to bind a
11
+ * plain query on `connection()`, its rows, and one final event —
12
+ * `PG_CARRIER_DONE`, `PG_CARRIER_MISSING` or `PG_CARRIER_FAILED` — with
13
+ * `request()` the `HttpRequest` to answer. It connects its pool's slots
14
+ * itself, gives up on one that has not answered in `connectTimeoutMs`, and
15
+ * sizes every table from `PgCarrierLimits` at construction.
16
+ *
17
+ * `tests/link/std_postgres_carrier` runs one against a loopback backend;
18
+ * `tests/postgres/carrier.ts` against a live server. Caps and findings:
19
+ * `docs/security/postgres.md`.
20
+ *
21
+ * Written for this module; not ported from another implementation.
22
+ */
23
+ import { HANDLER_MAX_WATCHED, HttpHandler, HttpRequest } from "nish/net/handler"
24
+ import { H1_WANT_READ, H1_WANT_WRITE } from "nish/net/http1-server"
25
+ import {
26
+ PG_BATCH_DONE,
27
+ PG_BATCH_FAILED,
28
+ PG_BATCH_NONE,
29
+ PG_BATCH_ROW,
30
+ PG_COMPLETE,
31
+ PG_ERROR,
32
+ PG_ERR_NONE,
33
+ PG_FAILED,
34
+ PG_GROUP_DONE,
35
+ PG_LOST,
36
+ PG_NEED_MORE,
37
+ PG_READY,
38
+ PG_ROW,
39
+ PG_TARGET_OK,
40
+ PgBatcher,
41
+ PgConnection,
42
+ PgPool,
43
+ PgTarget,
44
+ } from "nish/net/postgres"
45
+ import {
46
+ connectResult,
47
+ netAddress,
48
+ netClose,
49
+ netRead,
50
+ netWrite,
51
+ pollAdd,
52
+ pollCreate,
53
+ pollModify,
54
+ pollRemove,
55
+ pollWait,
56
+ tcpConnect,
57
+ unixConnect,
58
+ } from "nish:net"
59
+ import { monotonicNanos } from "nish:process"
60
+
61
+ /** The client nonce a SCRAM exchange starts with, as `PgPool.connect` takes it. */
62
+ const PG_CARRIER_NONCE_BYTES: i32 = 18
63
+
64
+ /** `PgCarrier.next()`: nothing more this turn; wait on the handler again. */
65
+ export const PG_CARRIER_NONE: i32 = 0
66
+ /** A plain query has its connection: bind it on `connection()`, then call `send()`. */
67
+ export const PG_CARRIER_BUILD: i32 = 1
68
+ /** A row for `query`, to read on `connection()` before the next call. */
69
+ export const PG_CARRIER_ROW: i32 = 2
70
+ /** A plain query's statement finished: `connection().tagRows()` reads its tag. */
71
+ export const PG_CARRIER_COMPLETE: i32 = 3
72
+ /** The server refused a plain query's statement: `connection().lastError` says why, and `PG_CARRIER_FAILED` follows. */
73
+ export const PG_CARRIER_ERROR: i32 = 4
74
+ /** `query` is over and it worked: answer its request. A final event. */
75
+ export const PG_CARRIER_DONE: i32 = 5
76
+ /** A batched query's key has no row. A final event. */
77
+ export const PG_CARRIER_MISSING: i32 = 6
78
+ /** `query` is over and it failed (`failure` and `socketError` say how). A final event. */
79
+ export const PG_CARRIER_FAILED: i32 = 7
80
+ /** `run`, `runInt` or `runText`: no room for another query now. */
81
+ export const PG_CARRIER_FULL: i32 = -2
82
+
83
+ /** `netRead` and `netWrite`: nothing to do now. */
84
+ const PG_CARRIER_AGAIN: i32 = -11
85
+ /** A ready socket with input to read: readable, or hung up or in error. */
86
+ const PG_CARRIER_READ_BITS: i32 = 5
87
+ /** The socket error a connection past its connect timeout reports (ETIMEDOUT). */
88
+ const PG_CARRIER_TIMED_OUT: i32 = -110
89
+ /** The most batchers one carrier routes. */
90
+ const PG_CARRIER_MAX_BATCHERS: i32 = 8
91
+ /** The most queries or executions one carrier holds. */
92
+ const PG_CARRIER_MAX_QUERIES: i32 = 65536
93
+ /** Where `next()` is in its pass, so that a call resumes where the last stopped. */
94
+ const PG_CARRIER_POLL: i32 = 0
95
+ const PG_CARRIER_SLOTS: i32 = 1
96
+ const PG_CARRIER_DISPATCH: i32 = 2
97
+ const PG_CARRIER_FLUSH: i32 = 3
98
+ /** `pgCarrierDispatch`: look again, something is owed. */
99
+ const PG_CARRIER_AGAIN_PASS: i32 = -1
100
+
101
+ /**
102
+ * The caps a `PgCarrier` is built with, all set before it starts: nothing
103
+ * grows after.
104
+ */
105
+ export class PgCarrierLimits {
106
+ /** The most queries waiting at once, plain and batched (1 to 65536). */
107
+ queries: i32 = 256
108
+ /** The most batched executions waiting or in flight at once (1 to 65536). */
109
+ executions: i32 = 64
110
+ /** How long a connection has to connect and authenticate before it fails, in milliseconds (1 to 3600000). */
111
+ connectTimeoutMs: i32 = 5000
112
+ /** The id the carrier's descriptor is watched under on the handler (`HttpHandler.watch`), 0 to `HANDLER_MAX_WATCHED - 1`. */
113
+ watch: i32 = 0
114
+ }
115
+
116
+ // Fills free stack `a` highest first, so that the lowest is handed out first.
117
+ const fillFree = (a: i32[]): void => {
118
+ const n: i32 = toI32(a.length)
119
+ for (let k: i32 = 0; k < n; k++) {
120
+ a[k] = n - 1 - k
121
+ }
122
+ }
123
+
124
+ /**
125
+ * A `PgPool` on an `HttpHandler`'s loop: the pool's sockets sit in one
126
+ * epoll set of the carrier's own, which the handler watches beside its
127
+ * connections, so that a route handler queries PostgreSQL without blocking
128
+ * its worker. Each turn the program waits on the handler, hands the requests
129
+ * it takes to `run` (or `runInt` and `runText`, for an attached `PgBatcher`),
130
+ * and reads `next()` until `PG_CARRIER_NONE`: every query gets its rows and
131
+ * then exactly one final event (`PG_CARRIER_DONE`, `PG_CARRIER_MISSING` or
132
+ * `PG_CARRIER_FAILED`), with `request()` the `HttpRequest` to answer and
133
+ * `tag` the program's own number for what was asked.
134
+ *
135
+ * The carrier connects the slots the pool wants to `target`, which must be a
136
+ * Unix socket or a numeric address (it resolves no names), reads and writes
137
+ * their bytes, fails a connection that has not authenticated within
138
+ * `connectTimeoutMs`, and closes a failed one's socket. A connection that
139
+ * fails takes the queries on it with it, each `PG_CARRIER_FAILED`. Every
140
+ * table is sized at construction from `PgCarrierLimits`: `run` answers
141
+ * `PG_CARRIER_FULL` rather than grow one, and once its connections are up
142
+ * nothing is allocated per query. A final event's query is freed on the
143
+ * following `next()` call, so `query`, `tag` and `request()` hold until then.
144
+ */
145
+ export class PgCarrier {
146
+ private readonly pool: PgPool
147
+ private readonly handler: HttpHandler
148
+ private readonly path: string
149
+ private readonly address: u8[]
150
+ private readonly nonce: u8[]
151
+ private readonly ready: i32[]
152
+ private readonly timeoutNs: i64
153
+ // Each pool slot's socket (-1 for none), whether its connect finished,
154
+ // whether a connection was ever made in it, when its connect times out
155
+ // (0 for no deadline), and the socket error that failed it.
156
+ private readonly fds: i32[]
157
+ private readonly connected: boolean[]
158
+ // The events each slot's socket is watched for, so that one unchanged
159
+ // is not asked for again.
160
+ private readonly interest: i32[]
161
+ private readonly made: boolean[]
162
+ private readonly deadlines: i64[]
163
+ private readonly errors: i32[]
164
+ // Each query's tag, request, pool ticket (-1 for a batched one), whether
165
+ // the server failed it, and the stack of free ones.
166
+ private readonly qTags: i32[]
167
+ private readonly qRequests: (HttpRequest | null)[]
168
+ private readonly qTickets: i32[]
169
+ private readonly qFailed: boolean[]
170
+ private readonly qFree: i32[]
171
+ // Each batched execution's batcher, run (-1 before `take`), whether its run
172
+ // was ended, and the stack of free ones.
173
+ private readonly eBatchers: i32[]
174
+ private readonly eRuns: i32[]
175
+ private readonly eEnded: boolean[]
176
+ private readonly eFree: i32[]
177
+ // Pool tickets to what they carry: a query, or `queries` plus an
178
+ // execution. Open addressing, -1 for an empty slot.
179
+ private readonly mapKeys: i32[]
180
+ private readonly mapValues: i32[]
181
+ private readonly batchers: PgBatcher[]
182
+ private readonly batchQueued: boolean[]
183
+ private readonly batchStalled: boolean[]
184
+ private conn: PgConnection | null = null
185
+ /** The query the last event is about, as `run` answered it; -1 for none. */
186
+ query: i32 = -1
187
+ /** The tag `query` was run with. */
188
+ tag: i32 = -1
189
+ /** Behind a `PG_CARRIER_FAILED`: the connection's `PG_ERR_*` failure when it was lost, else `PG_ERR_NONE` (the server's error, or a bind with no room). */
190
+ failure: i32 = 0
191
+ /** Behind a `PG_CARRIER_FAILED` for a lost connection: the socket error that ended it (`-110` for a connect timeout), or 0. */
192
+ socketError: i32 = 0
193
+ private readonly watchId: i32
194
+ private readonly loop: i32
195
+ private qFreeCount: i32 = 0
196
+ private eFreeCount: i32 = 0
197
+ private readonly mapMask: i32
198
+ // What a call owes: the query being built, one dropped unsent, the query
199
+ // whose final event was answered last, a run's events, and requests shed
200
+ // from a batcher whose execution was lost.
201
+ private building: i32 = -1
202
+ private dropped: i32 = -1
203
+ private finished: i32 = -1
204
+ private drainBatcher: i32 = -1
205
+ private drainRun: i32 = -1
206
+ private drainFailure: i32 = 0
207
+ private drainError: i32 = 0
208
+ private shedBatcher: i32 = -1
209
+ private shedLeft: i32 = 0
210
+ private phase: i32 = 0
211
+ private at: i32 = 0
212
+ private readonly unix: boolean
213
+ private connecting: boolean = true
214
+ private closed: boolean = false
215
+
216
+ /**
217
+ * A carrier for `pool`'s connections to `target`, its descriptor watched on
218
+ * `handler` as `limits.watch`. Panics on a cap or watch id out of range,
219
+ * or a target that failed or is not a numeric address.
220
+ */
221
+ constructor(limits: PgCarrierLimits, pool: PgPool, target: PgTarget, handler: HttpHandler) {
222
+ const queries: i32 = limits.queries
223
+ const executions: i32 = limits.executions
224
+ if (
225
+ queries < 1 ||
226
+ queries > PG_CARRIER_MAX_QUERIES ||
227
+ executions < 1 ||
228
+ executions > PG_CARRIER_MAX_QUERIES ||
229
+ limits.connectTimeoutMs < 1 ||
230
+ limits.connectTimeoutMs > 3600000 ||
231
+ limits.watch < 0 ||
232
+ limits.watch >= HANDLER_MAX_WATCHED
233
+ ) {
234
+ panic("nish/net/postgres-carrier: a PgCarrierLimits cap out of range")
235
+ }
236
+ if (target.failure !== PG_TARGET_OK) {
237
+ panic("nish/net/postgres-carrier: a PgCarrier for a target that failed")
238
+ }
239
+ this.address = new Array<u8>(18)
240
+ if (!target.unix && netAddress(this.address, target.host, target.port) < 0) {
241
+ panic("nish/net/postgres-carrier: a PgCarrier's TCP host must be a numeric address")
242
+ }
243
+ this.pool = pool
244
+ this.handler = handler
245
+ this.watchId = limits.watch
246
+ this.unix = target.unix
247
+ this.path = target.path
248
+ this.nonce = new Array<u8>(PG_CARRIER_NONCE_BYTES)
249
+ this.timeoutNs = toI64(limits.connectTimeoutMs) * toI64(1000000)
250
+ const size: i32 = pool.size
251
+ this.ready = new Array<i32>(2 * size)
252
+ this.fds = new Array<i32>(size)
253
+ this.connected = new Array<boolean>(size)
254
+ this.interest = new Array<i32>(size)
255
+ this.made = new Array<boolean>(size)
256
+ this.deadlines = new Array<i64>(size)
257
+ this.errors = new Array<i32>(size)
258
+ for (let s: i32 = 0; s < size && s < toI32(this.fds.length); s++) {
259
+ this.fds[s] = -1
260
+ }
261
+ this.qTags = new Array<i32>(queries)
262
+ this.qRequests = new Array<HttpRequest | null>(queries)
263
+ this.qTickets = new Array<i32>(queries)
264
+ this.qFailed = new Array<boolean>(queries)
265
+ this.qFree = new Array<i32>(queries)
266
+ fillFree(this.qFree)
267
+ this.qFreeCount = queries
268
+ this.eBatchers = new Array<i32>(executions)
269
+ this.eRuns = new Array<i32>(executions)
270
+ this.eEnded = new Array<boolean>(executions)
271
+ this.eFree = new Array<i32>(executions)
272
+ fillFree(this.eFree)
273
+ this.eFreeCount = executions
274
+ // At least twice the tickets ever held, a power of two for the mask.
275
+ let mapSize: i32 = 2
276
+ while (mapSize / 2 < queries + executions) {
277
+ mapSize *= 2
278
+ }
279
+ this.mapKeys = new Array<i32>(mapSize)
280
+ this.mapValues = new Array<i32>(mapSize)
281
+ for (let i: i32 = 0; i < mapSize && i < toI32(this.mapKeys.length); i++) {
282
+ this.mapKeys[i] = -1
283
+ }
284
+ this.mapMask = mapSize - 1
285
+ this.batchers = []
286
+ this.batchQueued = new Array<boolean>(PG_CARRIER_MAX_BATCHERS)
287
+ this.batchStalled = new Array<boolean>(PG_CARRIER_MAX_BATCHERS)
288
+ this.loop = pollCreate()
289
+ if (this.loop < 0) {
290
+ panic("nish/net/postgres-carrier: PgCarrier could not create its poll set")
291
+ }
292
+ handler.watch(this.loop, limits.watch)
293
+ }
294
+
295
+ /**
296
+ * Registers `batcher`'s statement on the pool and answers its id for
297
+ * `runInt` and `runText`. Panics past 8 batchers, and wherever
298
+ * `PgPool.register` refuses: once the pool has connected, on a NUL in the
299
+ * batcher's name or SQL, or past 256 statements.
300
+ */
301
+ attach(batcher: PgBatcher): i32 {
302
+ const id: i32 = toI32(this.batchers.length)
303
+ const types: i32[] = []
304
+ if (id >= PG_CARRIER_MAX_BATCHERS || !this.pool.register(batcher.name, batcher.sql, types)) {
305
+ panic(
306
+ "nish/net/postgres-carrier: PgCarrier.attach past 8 batchers, after the pool connected, on a NUL in the name or SQL, or past 256 statements"
307
+ )
308
+ }
309
+ this.batchers.push(batcher)
310
+ return id
311
+ }
312
+
313
+ /**
314
+ * Queues a plain query for `req` (null for one of the program's own) and
315
+ * answers its number; `PG_CARRIER_BUILD` comes for it once a connection
316
+ * has room. `PG_CARRIER_FULL` when `queries` are waiting, the pool's
317
+ * queue is full or the carrier is closed. Returns at once.
318
+ */
319
+ run(req: HttpRequest | null, tag: i32): i32 {
320
+ const q: i32 = this.take(req, tag)
321
+ if (q < 0) {
322
+ return PG_CARRIER_FULL
323
+ }
324
+ const ticket: i32 = this.pool.request()
325
+ if (ticket < 0) {
326
+ this.free(q)
327
+ return PG_CARRIER_FULL
328
+ }
329
+ this.qTickets[q] = ticket
330
+ this.mapPut(ticket, q)
331
+ return q
332
+ }
333
+
334
+ /** Queues `req`'s int8 `key` on batcher `batcher`, as `run` does a plain query; `PG_CARRIER_FULL` also when the batcher's queue is. */
335
+ runInt(req: HttpRequest | null, batcher: i32, key: i64, tag: i32): i32 {
336
+ const b: PgBatcher = this.batcher(batcher)
337
+ const q: i32 = this.take(req, tag)
338
+ if (q < 0) {
339
+ return PG_CARRIER_FULL
340
+ }
341
+ if (!b.addInt(key, q)) {
342
+ this.free(q)
343
+ return PG_CARRIER_FULL
344
+ }
345
+ return q
346
+ }
347
+
348
+ /** Queues `req`'s text key `bytes[start, start + length)` on batcher `batcher`, as `runInt` does; also `PG_CARRIER_FULL` when `PgBatcher.addText` refuses the key. */
349
+ runText(req: HttpRequest | null, batcher: i32, bytes: u8[], start: i32, length: i32, tag: i32): i32 {
350
+ const b: PgBatcher = this.batcher(batcher)
351
+ const q: i32 = this.take(req, tag)
352
+ if (q < 0) {
353
+ return PG_CARRIER_FULL
354
+ }
355
+ if (!b.addText(bytes, start, length, q)) {
356
+ this.free(q)
357
+ return PG_CARRIER_FULL
358
+ }
359
+ return q
360
+ }
361
+
362
+ /** The request `query` was run for. Panics when it was run with none, or no event named a query. */
363
+ request(): HttpRequest {
364
+ const q: i32 = this.query
365
+ const req: HttpRequest | null = q >= 0 && q < toI32(this.qRequests.length) ? this.qRequests[q] : null
366
+ if (req === null) {
367
+ panic("nish/net/postgres-carrier: PgCarrier.request with no request for the query")
368
+ }
369
+ return req
370
+ }
371
+
372
+ /** The connection the last `PG_CARRIER_BUILD` or row is on. Panics before any. */
373
+ connection(): PgConnection {
374
+ const conn: PgConnection | null = this.conn
375
+ if (conn === null) {
376
+ panic("nish/net/postgres-carrier: PgCarrier.connection before any query had one")
377
+ }
378
+ return conn
379
+ }
380
+
381
+ /**
382
+ * Ends the query `PG_CARRIER_BUILD` handed out, as `PgPool.send` does,
383
+ * and answers whether it went. A bind left open is dropped, and the query
384
+ * fails; one with nothing written fails at once, on the next `next()`.
385
+ * Panics when no build is open.
386
+ */
387
+ send(): boolean {
388
+ const q: i32 = this.building
389
+ if (q < 0 || q >= toI32(this.qTickets.length)) {
390
+ panic("nish/net/postgres-carrier: PgCarrier.send with no PG_CARRIER_BUILD open")
391
+ }
392
+ this.building = -1
393
+ if (this.pool.send()) {
394
+ return true
395
+ }
396
+ const conn: PgConnection = this.connection()
397
+ if (conn.bindCancel()) {
398
+ // The group may hold the connection's prepares, which still go; the query does not.
399
+ this.qFailed[q] = true
400
+ if (this.pool.send()) {
401
+ return true
402
+ }
403
+ }
404
+ // Nothing was written: a live connection dropped the query, and a failed
405
+ // one reports it lost with the rest.
406
+ if (conn.failure === PG_ERR_NONE) {
407
+ this.mapRemove(this.qTickets[q])
408
+ this.dropped = q
409
+ }
410
+ return false
411
+ }
412
+
413
+ /**
414
+ * The next event, resuming where the last call stopped: the events the
415
+ * pool's connections read, then the queries handed to connections
416
+ * (`PG_CARRIER_BUILD`), then `PG_CARRIER_NONE` once their bytes are
417
+ * written and nothing more can happen until the handler's next turn. The
418
+ * connect timeout is checked here, so the program's `wait` timeout bounds
419
+ * how late it fires. Panics while a `PG_CARRIER_BUILD` is unsent.
420
+ * `PG_CARRIER_NONE` once the carrier is closed, which connects nothing.
421
+ */
422
+ next(): i32 {
423
+ if (this.closed) {
424
+ return PG_CARRIER_NONE
425
+ }
426
+ if (this.building >= 0) {
427
+ panic("nish/net/postgres-carrier: PgCarrier.next before send() ended the PG_CARRIER_BUILD")
428
+ }
429
+ if (this.finished >= 0) {
430
+ this.free(this.finished)
431
+ this.finished = -1
432
+ }
433
+ while (true) {
434
+ if (this.dropped >= 0) {
435
+ const q: i32 = this.dropped
436
+ this.dropped = -1
437
+ this.failure = PG_ERR_NONE
438
+ this.socketError = 0
439
+ return this.final(q, PG_CARRIER_FAILED)
440
+ }
441
+ if (this.drainRun >= 0) {
442
+ const event: i32 = this.drain()
443
+ if (event !== PG_CARRIER_NONE) {
444
+ return event
445
+ }
446
+ }
447
+ if (this.shedLeft > 0) {
448
+ this.shedLeft -= 1
449
+ const q: i32 = this.batcher(this.shedBatcher).shed()
450
+ if (q >= 0) {
451
+ return this.final(q, PG_CARRIER_FAILED)
452
+ }
453
+ this.shedLeft = 0
454
+ }
455
+ if (this.phase === PG_CARRIER_POLL) {
456
+ this.poll()
457
+ this.phase = PG_CARRIER_SLOTS
458
+ this.at = 0
459
+ }
460
+ if (this.phase === PG_CARRIER_SLOTS) {
461
+ const event: i32 = this.slots()
462
+ if (event === PG_CARRIER_AGAIN_PASS) {
463
+ continue
464
+ }
465
+ if (event !== PG_CARRIER_NONE) {
466
+ return event
467
+ }
468
+ this.phase = PG_CARRIER_DISPATCH
469
+ }
470
+ if (this.phase === PG_CARRIER_DISPATCH) {
471
+ const event: i32 = this.dispatch()
472
+ if (event === PG_CARRIER_AGAIN_PASS) {
473
+ continue
474
+ }
475
+ if (event !== PG_CARRIER_NONE) {
476
+ return event
477
+ }
478
+ this.phase = PG_CARRIER_FLUSH
479
+ }
480
+ // Connects run once a pass, so that a server refusing at once cannot
481
+ // spin the loop; a socket that failed has its events read before the
482
+ // turn ends.
483
+ const failed: boolean = this.connectAndFlush(this.connecting)
484
+ this.connecting = false
485
+ if (failed) {
486
+ this.phase = PG_CARRIER_SLOTS
487
+ this.at = 0
488
+ continue
489
+ }
490
+ this.phase = PG_CARRIER_POLL
491
+ this.connecting = true
492
+ return PG_CARRIER_NONE
493
+ }
494
+ }
495
+
496
+ /**
497
+ * Tells every connection the session is over, writes what it can of that,
498
+ * and closes the sockets and the carrier's poll set, which closing takes
499
+ * off the handler's watch. Queries still in flight get no further event:
500
+ * their requests are the program's to answer. After it, `run`, `runInt`
501
+ * and `runText` answer `PG_CARRIER_FULL` and `next()` `PG_CARRIER_NONE`;
502
+ * a second `close()` does nothing. The pool is the program's to `dispose()`.
503
+ */
504
+ close(): void {
505
+ if (this.closed) {
506
+ return
507
+ }
508
+ this.closed = true
509
+ for (let s: i32 = 0; s < toI32(this.fds.length); s++) {
510
+ const fd: i32 = this.fds[s]
511
+ if (fd >= 0) {
512
+ const conn: PgConnection = this.pool.connection(s)
513
+ conn.terminate()
514
+ this.flush(conn, fd)
515
+ this.dropSocket(s, fd)
516
+ }
517
+ }
518
+ netClose(this.loop)
519
+ }
520
+
521
+ // Reads what the sockets the poll set reports have, finishes their
522
+ // connects, and fails the connections past their connect timeout.
523
+ private poll(): void {
524
+ if (this.handler.fired(this.watchId)) {
525
+ const n: i32 = pollWait(this.loop, this.ready, toI32(0))
526
+ for (let k: i32 = 0; k < n && 2 * k + 1 < toI32(this.ready.length); k++) {
527
+ const s: i32 = this.ready[2 * k]
528
+ const bits: i32 = this.ready[2 * k + 1]
529
+ if (s >= 0 && s < toI32(this.fds.length) && this.fds[s] >= 0) {
530
+ this.service(s, bits)
531
+ }
532
+ }
533
+ }
534
+ let now: i64 = 0
535
+ for (let s: i32 = 0; s < toI32(this.deadlines.length); s++) {
536
+ const deadline: i64 = this.deadlines[s]
537
+ if (deadline > 0) {
538
+ if (now === 0) {
539
+ now = monotonicNanos()
540
+ }
541
+ // A connection the server already failed keeps that failure.
542
+ if (now >= deadline && this.pool.connection(s).failure === PG_ERR_NONE) {
543
+ this.fail(s, PG_CARRIER_TIMED_OUT)
544
+ }
545
+ }
546
+ }
547
+ for (let b: i32 = 0; b < toI32(this.batchStalled.length); b++) {
548
+ this.batchStalled[b] = false
549
+ }
550
+ }
551
+
552
+ // One ready socket: a connect finished, or input to read.
553
+ private service(s: i32, bits: i32): void {
554
+ const conn: PgConnection = this.pool.connection(s)
555
+ if (conn.failure !== PG_ERR_NONE) {
556
+ return
557
+ }
558
+ const fd: i32 = this.fds[s]
559
+ if (!this.connected[s]) {
560
+ const result: i32 = connectResult(fd)
561
+ if (result !== 0) {
562
+ this.fail(s, result)
563
+ return
564
+ }
565
+ this.connected[s] = true
566
+ }
567
+ const room: i32 = conn.inputRoom()
568
+ if ((bits & PG_CARRIER_READ_BITS) !== 0 && room > 0) {
569
+ const got: i32 = netRead(fd, conn.inputBuffer(), conn.inputEnd(), room)
570
+ if (got > 0) {
571
+ conn.received(got)
572
+ } else if (got !== PG_CARRIER_AGAIN) {
573
+ this.fail(s, got)
574
+ }
575
+ }
576
+ }
577
+
578
+ // Ends slot `s`'s connection for `error`: its events then say so.
579
+ private fail(s: i32, error: i32): void {
580
+ this.pool.connection(s).closed()
581
+ this.errors[s] = error
582
+ this.deadlines[s] = 0
583
+ }
584
+
585
+ // The events of every slot's connection, resuming at `at`.
586
+ private slots(): i32 {
587
+ while (this.at < toI32(this.made.length)) {
588
+ const s: i32 = this.at
589
+ if (!this.made[s]) {
590
+ this.at += 1
591
+ continue
592
+ }
593
+ const event: i32 = this.pool.next(s)
594
+ if (event === PG_NEED_MORE) {
595
+ this.at += 1
596
+ continue
597
+ }
598
+ const routed: i32 = this.route(s, event)
599
+ if (routed !== PG_CARRIER_NONE) {
600
+ return routed
601
+ }
602
+ }
603
+ return PG_CARRIER_NONE
604
+ }
605
+
606
+ // One pool event from slot `s`, as the carrier's own; `PG_CARRIER_NONE`
607
+ // for one it keeps, `PG_CARRIER_AGAIN_PASS` when a run's events are owed.
608
+ private route(s: i32, event: i32): i32 {
609
+ const conn: PgConnection = this.pool.connection(s)
610
+ if (event === PG_READY) {
611
+ this.deadlines[s] = 0
612
+ return PG_CARRIER_NONE
613
+ }
614
+ if (event === PG_FAILED) {
615
+ const fd: i32 = this.fds[s]
616
+ if (fd >= 0) {
617
+ this.dropSocket(s, fd)
618
+ }
619
+ this.deadlines[s] = 0
620
+ return PG_CARRIER_NONE
621
+ }
622
+ const ticket: i32 = this.pool.ticket
623
+ const v: i32 = ticket < 0 ? -1 : this.mapGet(ticket)
624
+ if (v < 0) {
625
+ // A notice, or the event of a query no longer held.
626
+ return PG_CARRIER_NONE
627
+ }
628
+ this.conn = conn
629
+ const queries: i32 = toI32(this.qTags.length)
630
+ if (v < queries) {
631
+ return this.routePlain(s, v, ticket, event, conn)
632
+ }
633
+ return this.routeBatch(s, v - queries, ticket, event, conn)
634
+ }
635
+
636
+ private routePlain(s: i32, q: i32, ticket: i32, event: i32, conn: PgConnection): i32 {
637
+ if (event === PG_ROW || event === PG_COMPLETE) {
638
+ this.announce(q)
639
+ return event === PG_ROW ? PG_CARRIER_ROW : PG_CARRIER_COMPLETE
640
+ }
641
+ if (event === PG_ERROR) {
642
+ this.qFailed[q] = true
643
+ this.announce(q)
644
+ return PG_CARRIER_ERROR
645
+ }
646
+ if (event === PG_GROUP_DONE) {
647
+ this.mapRemove(ticket)
648
+ const failed: boolean = this.qFailed[q] || conn.groupFailed
649
+ this.failure = PG_ERR_NONE
650
+ this.socketError = 0
651
+ return this.final(q, failed ? PG_CARRIER_FAILED : PG_CARRIER_DONE)
652
+ }
653
+ if (event === PG_LOST) {
654
+ this.mapRemove(ticket)
655
+ this.failure = conn.failure
656
+ this.socketError = this.errors[s]
657
+ return this.final(q, PG_CARRIER_FAILED)
658
+ }
659
+ return PG_CARRIER_NONE
660
+ }
661
+
662
+ private routeBatch(s: i32, e: i32, ticket: i32, event: i32, conn: PgConnection): i32 {
663
+ const id: i32 = this.eBatchers[e]
664
+ const b: PgBatcher = this.batcher(id)
665
+ const r: i32 = this.eRuns[e]
666
+ if (event === PG_LOST && r < 0) {
667
+ // Lost before a connection took it: its requests would wait on a
668
+ // server that cannot be reached, so one execution's worth fail now.
669
+ this.mapRemove(ticket)
670
+ this.freeExecution(e)
671
+ this.batchQueued[id] = false
672
+ this.shedBatcher = id
673
+ this.shedLeft = b.maxRequests
674
+ this.failure = conn.failure
675
+ this.socketError = this.errors[s]
676
+ return PG_CARRIER_AGAIN_PASS
677
+ }
678
+ if (r < 0) {
679
+ return PG_CARRIER_NONE
680
+ }
681
+ if (event === PG_ROW) {
682
+ if (b.row(r, conn)) {
683
+ this.startDrain(id, r, PG_ERR_NONE, toI32(0))
684
+ return PG_CARRIER_AGAIN_PASS
685
+ }
686
+ return PG_CARRIER_NONE
687
+ }
688
+ if (event === PG_COMPLETE) {
689
+ b.end(r)
690
+ this.eEnded[e] = true
691
+ this.startDrain(id, r, PG_ERR_NONE, toI32(0))
692
+ return PG_CARRIER_AGAIN_PASS
693
+ }
694
+ if (event === PG_ERROR || event === PG_GROUP_DONE || event === PG_LOST) {
695
+ const lost: boolean = event === PG_LOST
696
+ if (event !== PG_ERROR) {
697
+ this.mapRemove(ticket)
698
+ this.freeExecution(e)
699
+ }
700
+ if (!this.eEnded[e]) {
701
+ b.fail(r)
702
+ this.eEnded[e] = true
703
+ this.startDrain(id, r, lost ? conn.failure : PG_ERR_NONE, lost ? this.errors[s] : toI32(0))
704
+ return PG_CARRIER_AGAIN_PASS
705
+ }
706
+ }
707
+ return PG_CARRIER_NONE
708
+ }
709
+
710
+ private startDrain(id: i32, r: i32, failure: i32, error: i32): void {
711
+ this.drainBatcher = id
712
+ this.drainRun = r
713
+ this.drainFailure = failure
714
+ this.drainError = error
715
+ }
716
+
717
+ // The next of the events a run owes, as the carrier's own.
718
+ private drain(): i32 {
719
+ const b: PgBatcher = this.batcher(this.drainBatcher)
720
+ const event: i32 = b.next(this.drainRun)
721
+ const q: i32 = b.tag
722
+ if (event === PG_BATCH_NONE || q < 0 || q >= toI32(this.qTags.length)) {
723
+ this.drainRun = -1
724
+ return PG_CARRIER_NONE
725
+ }
726
+ if (event === PG_BATCH_ROW) {
727
+ this.announce(q)
728
+ return PG_CARRIER_ROW
729
+ }
730
+ if (event === PG_BATCH_FAILED) {
731
+ this.failure = this.drainFailure
732
+ this.socketError = this.drainError
733
+ return this.final(q, PG_CARRIER_FAILED)
734
+ }
735
+ this.failure = PG_ERR_NONE
736
+ this.socketError = 0
737
+ return this.final(q, event === PG_BATCH_DONE ? PG_CARRIER_DONE : PG_CARRIER_MISSING)
738
+ }
739
+
740
+ // Queues an execution for each batcher with requests waiting, then hands
741
+ // waiting tickets to connections: a plain query's is the program's to
742
+ // build, an execution's is bound here.
743
+ private dispatch(): i32 {
744
+ for (let id: i32 = 0; id < toI32(this.batchers.length) && id < PG_CARRIER_MAX_BATCHERS; id++) {
745
+ if (
746
+ this.batchQueued[id] ||
747
+ this.batchStalled[id] ||
748
+ this.batchers[id].waiting() === 0 ||
749
+ this.eFreeCount === 0
750
+ ) {
751
+ continue
752
+ }
753
+ const ticket: i32 = this.pool.request()
754
+ if (ticket < 0) {
755
+ break
756
+ }
757
+ this.eFreeCount -= 1
758
+ const e: i32 = this.eFree[this.eFreeCount]
759
+ this.eBatchers[e] = id
760
+ this.eRuns[e] = -1
761
+ this.eEnded[e] = false
762
+ this.mapPut(ticket, toI32(this.qTags.length) + e)
763
+ this.batchQueued[id] = true
764
+ }
765
+ const slot: i32 = this.pool.dispatch()
766
+ if (slot < 0) {
767
+ return PG_CARRIER_NONE
768
+ }
769
+ const conn: PgConnection = this.pool.connection(slot)
770
+ const ticket: i32 = this.pool.ticket
771
+ const v: i32 = this.mapGet(ticket)
772
+ const queries: i32 = toI32(this.qTags.length)
773
+ if (v >= 0 && v < queries) {
774
+ this.conn = conn
775
+ this.building = v
776
+ this.announce(v)
777
+ return PG_CARRIER_BUILD
778
+ }
779
+ if (v < 0) {
780
+ this.pool.send()
781
+ return PG_CARRIER_AGAIN_PASS
782
+ }
783
+ const e: i32 = v - queries
784
+ const id: i32 = this.eBatchers[e]
785
+ const b: PgBatcher = this.batcher(id)
786
+ this.batchQueued[id] = false
787
+ const r: i32 = b.take(conn)
788
+ if (r < 0) {
789
+ // Every run is in flight, or the connection had no room for the bind:
790
+ // the requests stay queued for the next turn.
791
+ this.batchStalled[id] = true
792
+ this.pool.send()
793
+ if (conn.failure === PG_ERR_NONE) {
794
+ this.mapRemove(ticket)
795
+ this.freeExecution(e)
796
+ }
797
+ return PG_CARRIER_AGAIN_PASS
798
+ }
799
+ this.eRuns[e] = r
800
+ if (!this.pool.send() && conn.failure === PG_ERR_NONE) {
801
+ this.mapRemove(ticket)
802
+ this.freeExecution(e)
803
+ b.fail(r)
804
+ this.startDrain(id, r, PG_ERR_NONE, toI32(0))
805
+ }
806
+ return PG_CARRIER_AGAIN_PASS
807
+ }
808
+
809
+ // Connects the slots the pool wants (when `connect`), then writes what
810
+ // each connection queued and asks for writability while some stays.
811
+ // True when a socket failed and its events are owed.
812
+ private connectAndFlush(connect: boolean): boolean {
813
+ let failed: boolean = false
814
+ if (connect) {
815
+ let slot: i32 = this.pool.wanted()
816
+ const deadline: i64 = slot >= 0 ? monotonicNanos() + this.timeoutNs : toI64(0)
817
+ while (slot >= 0 && slot < toI32(this.fds.length)) {
818
+ crypto.getRandomValues(this.nonce)
819
+ this.pool.connect(slot, this.nonce)
820
+ this.made[slot] = true
821
+ this.connected[slot] = false
822
+ this.interest[slot] = H1_WANT_WRITE
823
+ this.errors[slot] = 0
824
+ this.deadlines[slot] = deadline
825
+ const fd: i32 = this.unix ? unixConnect(this.path) : tcpConnect(this.address)
826
+ this.fds[slot] = fd
827
+ if (fd < 0) {
828
+ this.fail(slot, fd)
829
+ failed = true
830
+ } else if (pollAdd(this.loop, fd, H1_WANT_WRITE, slot) < 0) {
831
+ this.fail(slot, toI32(-1))
832
+ failed = true
833
+ }
834
+ slot = this.pool.wanted()
835
+ }
836
+ }
837
+ for (let s: i32 = 0; s < toI32(this.fds.length); s++) {
838
+ const fd: i32 = this.fds[s]
839
+ if (fd < 0 || !this.connected[s]) {
840
+ continue
841
+ }
842
+ const conn: PgConnection = this.pool.connection(s)
843
+ if (conn.failure !== PG_ERR_NONE) {
844
+ continue
845
+ }
846
+ const written: i32 = this.flush(conn, fd)
847
+ if (written < 0) {
848
+ this.fail(s, written)
849
+ failed = true
850
+ continue
851
+ }
852
+ const wants: i32 = conn.outputEnd() > conn.outputStart() ? H1_WANT_READ | H1_WANT_WRITE : H1_WANT_READ
853
+ if (wants !== this.interest[s]) {
854
+ pollModify(this.loop, fd, wants, s)
855
+ this.interest[s] = wants
856
+ }
857
+ }
858
+ return failed
859
+ }
860
+
861
+ // Writes what `conn` queued until the socket takes no more; the socket
862
+ // error when it fails, else 0.
863
+ private flush(conn: PgConnection, fd: i32): i32 {
864
+ while (conn.outputEnd() > conn.outputStart()) {
865
+ const n: i32 = netWrite(
866
+ fd,
867
+ conn.outputBuffer(),
868
+ conn.outputStart(),
869
+ conn.outputEnd() - conn.outputStart()
870
+ )
871
+ if (n === PG_CARRIER_AGAIN) {
872
+ return 0
873
+ }
874
+ if (n <= 0) {
875
+ return n < 0 ? n : -1
876
+ }
877
+ conn.sent(n)
878
+ }
879
+ return 0
880
+ }
881
+
882
+ // Names query `q` as the one the event is about.
883
+ private announce(q: i32): void {
884
+ this.query = q
885
+ this.tag = this.qTags[q]
886
+ }
887
+
888
+ // Takes slot `s`'s socket `fd` off the poll set and closes it.
889
+ private dropSocket(s: i32, fd: i32): void {
890
+ pollRemove(this.loop, fd)
891
+ netClose(fd)
892
+ this.fds[s] = -1
893
+ }
894
+
895
+ // Answers query `q`'s final `event`; the query is freed on the next call.
896
+ private final(q: i32, event: i32): i32 {
897
+ this.announce(q)
898
+ this.finished = q
899
+ return event
900
+ }
901
+
902
+ private batcher(id: i32): PgBatcher {
903
+ if (id < 0 || id >= toI32(this.batchers.length)) {
904
+ panic("nish/net/postgres-carrier: PgCarrier batcher id out of range")
905
+ }
906
+ return this.batchers[id]
907
+ }
908
+
909
+ // A free query for `req` and `tag`, or -1 when none is free or the carrier is closed.
910
+ private take(req: HttpRequest | null, tag: i32): i32 {
911
+ if (this.closed || this.qFreeCount === 0) {
912
+ return -1
913
+ }
914
+ this.qFreeCount -= 1
915
+ const q: i32 = this.qFree[this.qFreeCount]
916
+ this.qTags[q] = tag
917
+ this.qRequests[q] = req
918
+ this.qTickets[q] = -1
919
+ this.qFailed[q] = false
920
+ return q
921
+ }
922
+
923
+ private free(q: i32): void {
924
+ this.qRequests[q] = null
925
+ this.qFree[this.qFreeCount] = q
926
+ this.qFreeCount += 1
927
+ }
928
+
929
+ private freeExecution(e: i32): void {
930
+ this.eFree[this.eFreeCount] = e
931
+ this.eFreeCount += 1
932
+ }
933
+
934
+ private mapPut(key: i32, value: i32): void {
935
+ const mask: i32 = this.mapMask
936
+ let i: i32 = key & mask
937
+ while (this.mapKeys[i] !== -1) {
938
+ i = (i + 1) & mask
939
+ }
940
+ this.mapKeys[i] = key
941
+ this.mapValues[i] = value
942
+ }
943
+
944
+ private mapGet(key: i32): i32 {
945
+ const mask: i32 = this.mapMask
946
+ let i: i32 = key & mask
947
+ while (this.mapKeys[i] !== -1) {
948
+ if (this.mapKeys[i] === key) {
949
+ return this.mapValues[i]
950
+ }
951
+ i = (i + 1) & mask
952
+ }
953
+ return -1
954
+ }
955
+
956
+ // Removes `key`, shifting back the entries after it that would no longer
957
+ // be found past the hole, so that no tombstone is ever left.
958
+ private mapRemove(key: i32): void {
959
+ const mask: i32 = this.mapMask
960
+ let i: i32 = key & mask
961
+ while (this.mapKeys[i] !== key) {
962
+ if (this.mapKeys[i] === -1) {
963
+ return
964
+ }
965
+ i = (i + 1) & mask
966
+ }
967
+ let j: i32 = i
968
+ while (true) {
969
+ j = (j + 1) & mask
970
+ const k: i32 = this.mapKeys[j]
971
+ if (k === -1) {
972
+ break
973
+ }
974
+ // The entry at `j` stays when its home is cyclically within (i, j].
975
+ const home: i32 = k & mask
976
+ const stays: boolean = i <= j ? home > i && home <= j : home > i || home <= j
977
+ if (!stays) {
978
+ this.mapKeys[i] = k
979
+ this.mapValues[i] = this.mapValues[j]
980
+ i = j
981
+ }
982
+ }
983
+ this.mapKeys[i] = -1
984
+ }
985
+ }