@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
|
@@ -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
|
+
}
|