@volter/tabnode 0.4.1 → 0.5.1
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/dist/__sw__.js +259 -5
- package/dist/assets/{runtime-worker-LWfW6VWE.js → runtime-worker-DBZ2flHK.js} +4000 -2944
- package/dist/fetch-transport.d.ts +20 -0
- package/dist/guest-timers.d.ts +15 -0
- package/dist/http-client-transport.d.ts +19 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.mjs +5196 -3062
- package/dist/native-stream-binding.d.ts +57 -0
- package/dist/native-stream-owner.d.ts +121 -0
- package/dist/node-lib/binding/duplex_stream.d.ts +26 -0
- package/dist/node-lib/binding/fds.d.ts +8 -0
- package/dist/node-lib/binding/fs.d.ts +311 -78
- package/dist/node-lib/binding/misc.d.ts +1 -0
- package/dist/node-lib/binding/pipe_wrap.d.ts +4 -4
- package/dist/node-lib/binding/process_wrap.d.ts +12 -1
- package/dist/node-lib/binding/stream_wrap.d.ts +27 -18
- package/dist/node-lib/binding/tcp_wrap.d.ts +0 -1
- package/dist/node-lib/binding/tty_wrap.d.ts +43 -2
- package/dist/node-lib/binding/util.d.ts +83 -0
- package/dist/node-lib/binding/uv.d.ts +4 -0
- package/dist/node-lib/fs-module.d.ts +1 -0
- package/dist/node-lib/internals/buffer-and-streams.d.ts +30 -17
- package/dist/node-lib/internals/index.d.ts +1 -1
- package/dist/node-lib/load.d.ts +17 -3
- package/dist/node-lib/module-hooks.d.ts +1 -0
- package/dist/node-lib/public-modules.d.ts +1 -1
- package/dist/node-lib/timers.d.ts +38 -0
- package/dist/node-process-host.d.ts +41 -0
- package/dist/process-registry.d.ts +39 -0
- package/dist/process-tokens.d.ts +11 -0
- package/dist/promise-ownership.d.ts +3 -0
- package/dist/runtime.d.ts +10 -9
- package/dist/shims/child_process.d.ts +2 -0
- package/dist/shims/crypto.d.ts +660 -119
- package/dist/shims/dns.d.ts +76 -2
- package/dist/shims/process.d.ts +1 -0
- package/dist/shims/string_decoder.d.ts +19 -3
- package/dist/shims/tls.d.ts +853 -31
- package/dist/source-edits.d.ts +7 -0
- package/package.json +1 -1
- package/src/code-transforms.ts +2 -3
- package/src/fetch-transport.ts +98 -0
- package/src/guest-timers.ts +89 -0
- package/src/http-client-transport.ts +169 -0
- package/src/index.ts +18 -0
- package/src/native-stream-binding.ts +393 -0
- package/src/native-stream-owner.ts +317 -0
- package/src/node-lib/binding/duplex_stream.ts +95 -0
- package/src/node-lib/binding/fds.ts +18 -1
- package/src/node-lib/binding/fs.ts +45 -17
- package/src/node-lib/binding/misc.ts +2 -0
- package/src/node-lib/binding/pipe_wrap.ts +35 -12
- package/src/node-lib/binding/process_wrap.ts +50 -9
- package/src/node-lib/binding/stream_wrap.ts +196 -72
- package/src/node-lib/binding/tcp_wrap.ts +43 -8
- package/src/node-lib/binding/tty_wrap.ts +135 -8
- package/src/node-lib/binding/util.ts +24 -15
- package/src/node-lib/binding/uv.ts +4 -0
- package/src/node-lib/fs-module.ts +2 -18
- package/src/node-lib/internal/navigator.js +149 -0
- package/src/node-lib/internals/buffer-and-streams.ts +50 -11
- package/src/node-lib/internals/events-util.ts +5 -0
- package/src/node-lib/internals/index.ts +12 -2
- package/src/node-lib/load.ts +170 -62
- package/src/node-lib/module-hooks.ts +5 -2
- package/src/node-lib/public-modules.ts +12 -48
- package/src/node-lib/sources.ts +4 -0
- package/src/node-lib/timers.ts +93 -0
- package/src/node-process-host.ts +124 -0
- package/src/process-registry.ts +144 -0
- package/src/process-tokens.ts +55 -6
- package/src/promise-ownership.ts +28 -0
- package/src/runtime.ts +90 -214
- package/src/shims/child_process.ts +310 -80
- package/src/shims/crypto.ts +115 -100
- package/src/shims/dns.ts +17 -1
- package/src/shims/process.ts +33 -11
- package/src/shims/string_decoder.ts +10 -6
- package/src/shims/tls.ts +49 -19
- package/src/source-edits.ts +20 -0
|
@@ -13,7 +13,10 @@
|
|
|
13
13
|
* `net.js` is vendored.
|
|
14
14
|
*/
|
|
15
15
|
import { bytesOfString } from './buffer';
|
|
16
|
-
import { UV_EOF, UV_EBADF, UV_ECONNRESET } from './uv';
|
|
16
|
+
import { UV_EOF, UV_EBADF, UV_ECONNRESET, UV_EPIPE } from './uv';
|
|
17
|
+
import { attachNativeStream, nativeStreamFor } from '../../native-stream-binding';
|
|
18
|
+
import type { TCP } from './tcp_wrap';
|
|
19
|
+
import type { Pipe } from './pipe_wrap';
|
|
17
20
|
|
|
18
21
|
/**
|
|
19
22
|
* The bytes this view names. Node's `Buffer.from` of a short string is a
|
|
@@ -70,15 +73,33 @@ interface Inbound {
|
|
|
70
73
|
handle?: unknown;
|
|
71
74
|
}
|
|
72
75
|
|
|
76
|
+
/** The connection end shared by duplicated native descriptors, not a JS socket. */
|
|
77
|
+
class StreamEndpoint {
|
|
78
|
+
readonly handles = new Set<LibuvStreamWrap>();
|
|
79
|
+
peer: StreamEndpoint | null = null;
|
|
80
|
+
inbound: Inbound[] = [];
|
|
81
|
+
eof = false;
|
|
82
|
+
error: number | null = null;
|
|
83
|
+
writeEnded = false;
|
|
84
|
+
flushScheduled = false;
|
|
85
|
+
readonly finalizers: Array<() => void> = [];
|
|
86
|
+
readonly capacityListeners = new Set<() => void>();
|
|
87
|
+
|
|
88
|
+
capacityChanged(): void {
|
|
89
|
+
for (const listener of [...this.capacityListeners]) listener();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
constructor(handle: LibuvStreamWrap) { this.handles.add(handle); }
|
|
93
|
+
}
|
|
94
|
+
|
|
73
95
|
/**
|
|
74
96
|
* The base every `TCP` and `Pipe` extends, as `LibuvStreamWrap` is the base of
|
|
75
97
|
* libuv's `uv_stream_t` wrappers.
|
|
76
98
|
*
|
|
77
|
-
* A pairing
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
* `
|
|
81
|
-
* `readStart` and `readStop` mean.
|
|
99
|
+
* A pairing joins two connection endpoints. Duplicated wrappers reference the
|
|
100
|
+
* same endpoint and consume its bytes once. A write queues a copy at the peer;
|
|
101
|
+
* reads are delivered on its own turn while a wrapper is reading, which is
|
|
102
|
+
* what `readStart` and `readStop` mean.
|
|
82
103
|
*/
|
|
83
104
|
export class LibuvStreamWrap implements OwnedHandle {
|
|
84
105
|
/** Set by `net.js` to `onStreamRead`; the only way bytes reach a stream. */
|
|
@@ -92,8 +113,13 @@ export class LibuvStreamWrap implements OwnedHandle {
|
|
|
92
113
|
/** The engine has no descriptors for a paired handle, as libuv reports -1 for one it has no fd for. */
|
|
93
114
|
fd = -1;
|
|
94
115
|
|
|
95
|
-
|
|
96
|
-
|
|
116
|
+
private streamEndpoint = new StreamEndpoint(this);
|
|
117
|
+
/** A live wrapper on the other end, including after the original was closed. */
|
|
118
|
+
get peer(): LibuvStreamWrap | null {
|
|
119
|
+
const native = nativeStreamFor(this);
|
|
120
|
+
if (native) return native.peer;
|
|
121
|
+
return this.streamEndpoint.peer?.handles.values().next().value ?? null;
|
|
122
|
+
}
|
|
97
123
|
/** True once `close()` has been called; a closed handle carries nothing. */
|
|
98
124
|
closed = false;
|
|
99
125
|
/**
|
|
@@ -103,9 +129,9 @@ export class LibuvStreamWrap implements OwnedHandle {
|
|
|
103
129
|
*/
|
|
104
130
|
pendingHandle: unknown = null;
|
|
105
131
|
|
|
106
|
-
protected inbound: Inbound[]
|
|
107
|
-
protected inboundEof
|
|
108
|
-
protected inboundError: number | null
|
|
132
|
+
protected get inbound(): Inbound[] { return this.streamEndpoint.inbound; }
|
|
133
|
+
protected get inboundEof(): boolean { return this.streamEndpoint.eof; }
|
|
134
|
+
protected get inboundError(): number | null { return this.streamEndpoint.error; }
|
|
109
135
|
protected eofDelivered = false;
|
|
110
136
|
protected userBuffer: Uint8Array | null = null;
|
|
111
137
|
protected readonly asyncId = nextAsyncId++;
|
|
@@ -114,15 +140,22 @@ export class LibuvStreamWrap implements OwnedHandle {
|
|
|
114
140
|
registerHandle(this);
|
|
115
141
|
}
|
|
116
142
|
|
|
143
|
+
protected attachNative(kind: 'tcp' | 'pipe', type: number): void {
|
|
144
|
+
attachNativeStream(this as unknown as TCP | Pipe, kind, type,
|
|
145
|
+
callback => this.closeLocal(callback), () => this.flushInbound());
|
|
146
|
+
}
|
|
147
|
+
|
|
117
148
|
getAsyncId(): number {
|
|
118
149
|
return this.asyncId;
|
|
119
150
|
}
|
|
120
151
|
|
|
121
152
|
ref(): void {
|
|
153
|
+
nativeStreamFor(this)?.operation('ref');
|
|
122
154
|
refHandle(this);
|
|
123
155
|
}
|
|
124
156
|
|
|
125
157
|
unref(): void {
|
|
158
|
+
nativeStreamFor(this)?.operation('unref');
|
|
126
159
|
unrefHandle(this);
|
|
127
160
|
}
|
|
128
161
|
|
|
@@ -132,12 +165,16 @@ export class LibuvStreamWrap implements OwnedHandle {
|
|
|
132
165
|
|
|
133
166
|
readStart(): number {
|
|
134
167
|
if (this.closed) return UV_EBADF;
|
|
168
|
+
const status = nativeStreamFor(this)?.beginRead() ?? 0;
|
|
169
|
+
if (status !== 0) return status;
|
|
135
170
|
this.reading = true;
|
|
136
171
|
this.flushInbound();
|
|
137
172
|
return 0;
|
|
138
173
|
}
|
|
139
174
|
|
|
140
175
|
readStop(): number {
|
|
176
|
+
// The remote owner may already have posted its one permitted read. Keep
|
|
177
|
+
// that credit until arrival; pause guest delivery without granting another.
|
|
141
178
|
this.reading = false;
|
|
142
179
|
return 0;
|
|
143
180
|
}
|
|
@@ -152,13 +189,16 @@ export class LibuvStreamWrap implements OwnedHandle {
|
|
|
152
189
|
* reads EOF. The request completes on its own turn, as libuv's does.
|
|
153
190
|
*/
|
|
154
191
|
shutdown(req: ShutdownWrap): number {
|
|
192
|
+
if (this.closed) return UV_EBADF;
|
|
193
|
+
const native = nativeStreamFor(this);
|
|
194
|
+
if (native) return native.shutdown(status => req.oncomplete?.(status));
|
|
155
195
|
this.sendEof();
|
|
156
196
|
queueMicrotask(() => { req.oncomplete?.(0); });
|
|
157
197
|
return 0;
|
|
158
198
|
}
|
|
159
199
|
|
|
160
|
-
writeBuffer(req: WriteWrap, buffer: Uint8Array): number {
|
|
161
|
-
return this.dispatchWrite(req, buffer);
|
|
200
|
+
writeBuffer(req: WriteWrap, buffer: Uint8Array, handle?: unknown): number {
|
|
201
|
+
return this.dispatchWrite(req, buffer, handle);
|
|
162
202
|
}
|
|
163
203
|
|
|
164
204
|
writev(req: WriteWrap, chunks: unknown[], allBuffers: boolean): number {
|
|
@@ -172,6 +212,8 @@ export class LibuvStreamWrap implements OwnedHandle {
|
|
|
172
212
|
parts.push(typeof chunk === 'string' ? bytesOfString(chunk, encoding) : (chunk as Uint8Array));
|
|
173
213
|
}
|
|
174
214
|
}
|
|
215
|
+
const native = nativeStreamFor(this);
|
|
216
|
+
if (native) return native.write(req, parts);
|
|
175
217
|
let total = 0;
|
|
176
218
|
for (const part of parts) total += part.byteLength;
|
|
177
219
|
const joined = new Uint8Array(total);
|
|
@@ -200,25 +242,58 @@ export class LibuvStreamWrap implements OwnedHandle {
|
|
|
200
242
|
}
|
|
201
243
|
|
|
202
244
|
/**
|
|
203
|
-
* libuv's `uv_close`. The peer reads EOF
|
|
204
|
-
*
|
|
245
|
+
* libuv's `uv_close`. The peer reads EOF when the last descriptor closes;
|
|
246
|
+
* the callback runs on its own turn, because `net.js` emits
|
|
205
247
|
* `close` from it and Node emits that after the turn that destroyed.
|
|
206
248
|
*/
|
|
207
249
|
close(callback?: () => void): void {
|
|
250
|
+
const native = nativeStreamFor(this);
|
|
251
|
+
if (native) { native.close(callback); return; }
|
|
252
|
+
this.closeLocal(callback);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private closeLocal(callback?: () => void): void {
|
|
256
|
+
const failures: unknown[] = [];
|
|
257
|
+
const release = (action: () => void): void => { try { action(); } catch (cause) { failures.push(cause); } };
|
|
208
258
|
if (!this.closed) {
|
|
209
259
|
this.closed = true;
|
|
210
260
|
releaseHandle(this);
|
|
211
|
-
const
|
|
212
|
-
this
|
|
261
|
+
const endpoint = this.streamEndpoint;
|
|
262
|
+
endpoint.handles.delete(this);
|
|
213
263
|
// Writes land in the peer's inbound queue at once; flushing here
|
|
214
264
|
// delivers those bytes and then EOF, in that order, on this turn. A
|
|
215
265
|
// child's setupChannel then disconnects (`connected` false) before the
|
|
216
266
|
// next `process.send`, so that send is ERR_IPC_CHANNEL_CLOSED rather
|
|
217
267
|
// than a write on a handle a later release would close (`write EBADF`).
|
|
218
|
-
|
|
219
|
-
|
|
268
|
+
// Closing one descriptor must not end a connection another process
|
|
269
|
+
// still holds (`send(socket, { keepOpen: true })` is one such case).
|
|
270
|
+
if (endpoint.handles.size === 0) {
|
|
271
|
+
const peer = endpoint.peer;
|
|
272
|
+
endpoint.peer = null;
|
|
273
|
+
if (peer) {
|
|
274
|
+
peer.peer = null;
|
|
275
|
+
peer.eof = true;
|
|
276
|
+
for (const handle of peer.handles) release(() => handle.flushInbound());
|
|
277
|
+
}
|
|
278
|
+
for (const finalize of endpoint.finalizers.splice(0)) release(finalize);
|
|
279
|
+
// SCM_RIGHTS copies queued on an unread IPC channel have no receiving
|
|
280
|
+
// process yet. Closing that channel must release them too.
|
|
281
|
+
for (const pending of endpoint.inbound) {
|
|
282
|
+
if (pending.handle instanceof LibuvStreamWrap) release(() => (pending.handle as LibuvStreamWrap).close());
|
|
283
|
+
}
|
|
284
|
+
endpoint.inbound.length = 0;
|
|
285
|
+
release(() => endpoint.capacityChanged());
|
|
286
|
+
endpoint.capacityListeners.clear();
|
|
287
|
+
}
|
|
288
|
+
release(() => this.onCloseHandle());
|
|
220
289
|
}
|
|
221
|
-
|
|
290
|
+
// Node schedules a destroy(error)'s error on nextTick after close() returns.
|
|
291
|
+
// The native close completion comes afterward. One microtask here ran the
|
|
292
|
+
// close listener first, so ClientRequest replaced the actual policy denial
|
|
293
|
+
// with ECONNRESET ("socket hang up"). Leave that tick ahead of completion.
|
|
294
|
+
if (callback) queueMicrotask(() => queueMicrotask(callback));
|
|
295
|
+
if (failures.length === 1) throw failures[0];
|
|
296
|
+
if (failures.length > 1) throw new AggregateError(failures, 'Native stream cleanup failed.');
|
|
222
297
|
}
|
|
223
298
|
|
|
224
299
|
/** What a `TCP` or a `Pipe` gives up beyond the stream: a port, a path. */
|
|
@@ -234,21 +309,61 @@ export class LibuvStreamWrap implements OwnedHandle {
|
|
|
234
309
|
* for both, that close ended the connection the child had just been given
|
|
235
310
|
* and its first write was `EBADF`.
|
|
236
311
|
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
* and silences its reads before the write).
|
|
312
|
+
* Both descriptors share the connection and its unread bytes. Node may
|
|
313
|
+
* close the sender on acknowledgement or keep it open; the binding must
|
|
314
|
+
* support either, without knowing the child_process option that chose it.
|
|
241
315
|
*/
|
|
242
316
|
duplicate(): LibuvStreamWrap {
|
|
243
317
|
const copy = new LibuvStreamWrap();
|
|
244
|
-
copy.
|
|
318
|
+
copy.shareConnectionFrom(this);
|
|
245
319
|
return copy;
|
|
246
320
|
}
|
|
247
321
|
|
|
248
322
|
/** Two handles become each other's peer; what one writes the other reads. */
|
|
249
323
|
static pair(a: LibuvStreamWrap, b: LibuvStreamWrap): void {
|
|
250
|
-
a
|
|
251
|
-
|
|
324
|
+
const left = nativeStreamFor(a), right = nativeStreamFor(b);
|
|
325
|
+
if (left || right) {
|
|
326
|
+
if (!left || !right) throw new Error('Cannot pair native and realm-local stream handles.');
|
|
327
|
+
left.pair(right);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
a.streamEndpoint.peer = b.streamEndpoint;
|
|
331
|
+
b.streamEndpoint.peer = a.streamEndpoint;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/** A fresh wrapper acquires a descriptor reference on this connection. */
|
|
335
|
+
protected shareConnectionFrom(other: LibuvStreamWrap): void {
|
|
336
|
+
this.streamEndpoint.handles.delete(this);
|
|
337
|
+
this.streamEndpoint = other.streamEndpoint;
|
|
338
|
+
this.streamEndpoint.handles.add(this);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/** Native connection resources outlive individual duplicated wrappers. */
|
|
342
|
+
protected onLastReferenceClose(callback: () => void): void {
|
|
343
|
+
this.streamEndpoint.finalizers.push(callback);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** Host transports bound copies waiting in the native receiving queue. */
|
|
347
|
+
get peerQueuedBytes(): number {
|
|
348
|
+
let bytes = 0;
|
|
349
|
+
for (const chunk of this.streamEndpoint.peer?.inbound ?? []) bytes += chunk.bytes.byteLength;
|
|
350
|
+
return bytes;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
get peerQueuedHandles(): number {
|
|
354
|
+
let count = 0;
|
|
355
|
+
for (const chunk of this.streamEndpoint.peer?.inbound ?? []) {
|
|
356
|
+
if (chunk.handle !== undefined && chunk.handle !== null) count++;
|
|
357
|
+
}
|
|
358
|
+
return count;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/** Changes to the same connection end, even after its wrapper is replaced. */
|
|
362
|
+
onPeerReadCapacity(listener: () => void): () => void {
|
|
363
|
+
const endpoint = this.streamEndpoint.peer;
|
|
364
|
+
if (!endpoint) return () => {};
|
|
365
|
+
endpoint.capacityListeners.add(listener);
|
|
366
|
+
return () => { endpoint.capacityListeners.delete(listener); };
|
|
252
367
|
}
|
|
253
368
|
|
|
254
369
|
/**
|
|
@@ -261,15 +376,11 @@ export class LibuvStreamWrap implements OwnedHandle {
|
|
|
261
376
|
* waiting for a message its parent had already sent.
|
|
262
377
|
*/
|
|
263
378
|
takeOverFrom(other: LibuvStreamWrap): void {
|
|
264
|
-
|
|
265
|
-
other
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
other.inbound = [];
|
|
270
|
-
}
|
|
271
|
-
if (other.inboundEof) this.inboundEof = true;
|
|
272
|
-
if (other.inboundError !== null && this.inboundError === null) this.inboundError = other.inboundError;
|
|
379
|
+
if (other === this) return;
|
|
380
|
+
if (nativeStreamFor(this) || nativeStreamFor(other)) throw new Error('Remote descriptors must be inherited and opened through their owner.');
|
|
381
|
+
this.shareConnectionFrom(other);
|
|
382
|
+
this.streamEndpoint.handles.delete(other);
|
|
383
|
+
other.streamEndpoint = new StreamEndpoint(other);
|
|
273
384
|
this.flushInbound();
|
|
274
385
|
}
|
|
275
386
|
|
|
@@ -289,11 +400,14 @@ export class LibuvStreamWrap implements OwnedHandle {
|
|
|
289
400
|
* produced nothing.
|
|
290
401
|
*/
|
|
291
402
|
protected dispatchWrite(req: WriteWrap, buffer: Uint8Array, handle?: unknown): number {
|
|
403
|
+
const native = nativeStreamFor(this);
|
|
404
|
+
if (native) return native.write(req, [buffer], handle);
|
|
292
405
|
void req;
|
|
293
406
|
const length = buffer.byteLength;
|
|
294
407
|
streamBaseState[kBytesWritten] = length;
|
|
295
408
|
streamBaseState[kLastWriteWasAsync] = 0;
|
|
296
409
|
if (this.closed) return UV_EBADF;
|
|
410
|
+
if (this.streamEndpoint.writeEnded) return UV_EPIPE;
|
|
297
411
|
this.bytesWritten += length;
|
|
298
412
|
const peer = this.peer;
|
|
299
413
|
if (!peer || peer.closed) return 0;
|
|
@@ -310,6 +424,7 @@ export class LibuvStreamWrap implements OwnedHandle {
|
|
|
310
424
|
* reader first and the bytes never reached it at all.
|
|
311
425
|
*/
|
|
312
426
|
protected sendEof(): void {
|
|
427
|
+
this.streamEndpoint.writeEnded = true;
|
|
313
428
|
const peer = this.peer;
|
|
314
429
|
if (peer) peer.receiveEof();
|
|
315
430
|
}
|
|
@@ -319,6 +434,7 @@ export class LibuvStreamWrap implements OwnedHandle {
|
|
|
319
434
|
* which is how `socket.resetAndDestroy()` differs from `socket.destroy()`.
|
|
320
435
|
*/
|
|
321
436
|
protected sendReset(): void {
|
|
437
|
+
this.streamEndpoint.writeEnded = true;
|
|
322
438
|
const peer = this.peer;
|
|
323
439
|
if (peer) peer.receiveError(UV_ECONNRESET);
|
|
324
440
|
}
|
|
@@ -326,7 +442,7 @@ export class LibuvStreamWrap implements OwnedHandle {
|
|
|
326
442
|
/** An error arriving on the read side, delivered as libuv delivers one. */
|
|
327
443
|
receiveError(code: number): void {
|
|
328
444
|
if (this.closed) return;
|
|
329
|
-
this.
|
|
445
|
+
this.streamEndpoint.error = code;
|
|
330
446
|
this.scheduleFlush();
|
|
331
447
|
}
|
|
332
448
|
|
|
@@ -340,7 +456,7 @@ export class LibuvStreamWrap implements OwnedHandle {
|
|
|
340
456
|
/** The peer is done writing. */
|
|
341
457
|
receiveEof(): void {
|
|
342
458
|
if (this.closed) return;
|
|
343
|
-
this.
|
|
459
|
+
this.streamEndpoint.eof = true;
|
|
344
460
|
this.scheduleFlush();
|
|
345
461
|
}
|
|
346
462
|
|
|
@@ -350,11 +466,14 @@ export class LibuvStreamWrap implements OwnedHandle {
|
|
|
350
466
|
* libuv's loop reads in: everything a writer did in one turn is read in the
|
|
351
467
|
* order it did it, and never from inside the call that did it.
|
|
352
468
|
*/
|
|
353
|
-
private flushScheduled = false;
|
|
354
469
|
protected scheduleFlush(): void {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
470
|
+
const endpoint = this.streamEndpoint;
|
|
471
|
+
if (endpoint.flushScheduled) return;
|
|
472
|
+
endpoint.flushScheduled = true;
|
|
473
|
+
queueMicrotask(() => {
|
|
474
|
+
endpoint.flushScheduled = false;
|
|
475
|
+
for (const handle of endpoint.handles) handle.flushInbound();
|
|
476
|
+
});
|
|
358
477
|
}
|
|
359
478
|
|
|
360
479
|
/**
|
|
@@ -364,39 +483,44 @@ export class LibuvStreamWrap implements OwnedHandle {
|
|
|
364
483
|
* from inside `onread`, so the loop re-reads `reading` every time.
|
|
365
484
|
*/
|
|
366
485
|
protected flushInbound(): void {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
486
|
+
try {
|
|
487
|
+
while (this.reading && !this.closed && this.inbound.length > 0) {
|
|
488
|
+
const next = this.inbound.shift() as Inbound;
|
|
489
|
+
const bytes = next.bytes;
|
|
490
|
+
// The handle is on the channel before the bytes it belongs to reach the
|
|
491
|
+
// reader, which is where `setupChannel` takes it off.
|
|
492
|
+
if (next.handle !== undefined) this.pendingHandle = next.handle;
|
|
493
|
+
const target = this.userBuffer;
|
|
494
|
+
if (target) {
|
|
495
|
+
// `net.Socket({ onread })`: the bytes land in the program's buffer and
|
|
496
|
+
// `onStreamRead` hands the count to its callback.
|
|
497
|
+
const taken = Math.min(target.byteLength, bytes.byteLength);
|
|
498
|
+
this.bytesRead += taken;
|
|
499
|
+
target.set(bytes.subarray(0, taken));
|
|
500
|
+
if (taken < bytes.byteLength) this.inbound.unshift({ bytes: bytes.subarray(taken) });
|
|
501
|
+
streamBaseState[kReadBytesOrError] = taken;
|
|
502
|
+
streamBaseState[kArrayBufferOffset] = 0;
|
|
503
|
+
try { this.onread?.(null); } finally { this.streamEndpoint.capacityChanged(); }
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
streamBaseState[kReadBytesOrError] = bytes.byteLength;
|
|
507
|
+
this.bytesRead += bytes.byteLength;
|
|
508
|
+
streamBaseState[kArrayBufferOffset] = bytes.byteOffset;
|
|
509
|
+
try { this.onread?.(bytes.buffer as ArrayBuffer); } finally { this.streamEndpoint.capacityChanged(); }
|
|
510
|
+
}
|
|
511
|
+
if (this.reading && !this.closed && this.inbound.length === 0 && !this.eofDelivered &&
|
|
512
|
+
(this.inboundError !== null || this.inboundEof)) {
|
|
513
|
+
this.eofDelivered = true;
|
|
514
|
+
// libuv stops a stream when it reads EOF, and a stopped handle does not
|
|
515
|
+
// hold the loop: a socket whose peer has gone is not work, however much
|
|
516
|
+
// unread data is still in its buffer.
|
|
517
|
+
stopHandle(this);
|
|
518
|
+
streamBaseState[kReadBytesOrError] = this.inboundError ?? UV_EOF;
|
|
382
519
|
streamBaseState[kArrayBufferOffset] = 0;
|
|
383
520
|
this.onread?.(null);
|
|
384
|
-
continue;
|
|
385
521
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
this.onread?.(bytes.buffer as ArrayBuffer);
|
|
389
|
-
}
|
|
390
|
-
if (this.reading && !this.closed && this.inbound.length === 0 && !this.eofDelivered &&
|
|
391
|
-
(this.inboundError !== null || this.inboundEof)) {
|
|
392
|
-
this.eofDelivered = true;
|
|
393
|
-
// libuv stops a stream when it reads EOF, and a stopped handle does not
|
|
394
|
-
// hold the loop: a socket whose peer has gone is not work, however much
|
|
395
|
-
// unread data is still in its buffer.
|
|
396
|
-
stopHandle(this);
|
|
397
|
-
streamBaseState[kReadBytesOrError] = this.inboundError ?? UV_EOF;
|
|
398
|
-
streamBaseState[kArrayBufferOffset] = 0;
|
|
399
|
-
this.onread?.(null);
|
|
522
|
+
} finally {
|
|
523
|
+
nativeStreamFor(this)?.readFlushed(this.inbound.length === 0, this.eofDelivered);
|
|
400
524
|
}
|
|
401
525
|
}
|
|
402
526
|
}
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { LibuvStreamWrap } from './stream_wrap';
|
|
16
16
|
import { __adoptHandle, ownerOf } from './handles';
|
|
17
|
-
import {
|
|
17
|
+
import { nativeStreamFor, registerNativeStreamConstructor } from '../../native-stream-binding';
|
|
18
|
+
import { UV_EADDRINUSE, UV_ECONNREFUSED, UV_EINVAL, UV_ENOTSUP, UV_EBADF } from './uv';
|
|
18
19
|
|
|
19
20
|
/** libuv's `uv_tcp_t` flavours, and the two flags `getFlags` builds. */
|
|
20
21
|
export const constants = {
|
|
@@ -100,18 +101,22 @@ export class TCP extends LibuvStreamWrap {
|
|
|
100
101
|
|
|
101
102
|
private local: SockName | null = null;
|
|
102
103
|
private remote: SockName | null = null;
|
|
103
|
-
private ephemeral: number | null = null;
|
|
104
104
|
|
|
105
105
|
constructor(type: number = constants.SOCKET) {
|
|
106
106
|
super();
|
|
107
107
|
this.type = type;
|
|
108
|
+
this.attachNative('tcp', type);
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
bind(address: string, port: number, _flags?: number): number {
|
|
112
|
+
const native = nativeStreamFor(this);
|
|
113
|
+
if (native) return native.call({ operation: 'bind', id: native.descriptor.id, address, port, flags: _flags }).status;
|
|
111
114
|
return this.bindTo(address || '0.0.0.0', port, 'IPv4');
|
|
112
115
|
}
|
|
113
116
|
|
|
114
117
|
bind6(address: string, port: number, _flags?: number): number {
|
|
118
|
+
const native = nativeStreamFor(this);
|
|
119
|
+
if (native) return native.call({ operation: 'bind', id: native.descriptor.id, address, port, flags: _flags, ipv6: true }).status;
|
|
115
120
|
return this.bindTo(address || '::', port, 'IPv6');
|
|
116
121
|
}
|
|
117
122
|
|
|
@@ -124,10 +129,17 @@ export class TCP extends LibuvStreamWrap {
|
|
|
124
129
|
if (this.local && boundPorts.get(this.local.port) === this) boundPorts.delete(this.local.port);
|
|
125
130
|
this.local = { address, family, port: bound };
|
|
126
131
|
boundPorts.set(bound, this);
|
|
132
|
+
this.onLastReferenceClose(() => { if (boundPorts.get(bound) === this) boundPorts.delete(bound); });
|
|
127
133
|
return 0;
|
|
128
134
|
}
|
|
129
135
|
|
|
130
136
|
listen(_backlog: number): number {
|
|
137
|
+
const native = nativeStreamFor(this);
|
|
138
|
+
if (native) {
|
|
139
|
+
const status = native.call({ operation: 'listen', id: native.descriptor.id, backlog: _backlog }).status;
|
|
140
|
+
if (status === 0) this.listening = true;
|
|
141
|
+
return status;
|
|
142
|
+
}
|
|
131
143
|
if (!this.local) {
|
|
132
144
|
const err = this.bindTo('0.0.0.0', 0, 'IPv4');
|
|
133
145
|
if (err) return err;
|
|
@@ -146,12 +158,14 @@ export class TCP extends LibuvStreamWrap {
|
|
|
146
158
|
}
|
|
147
159
|
|
|
148
160
|
private connectTo(req: TCPConnectWrap, address: string, port: number, family: string): number {
|
|
161
|
+
const native = nativeStreamFor(this);
|
|
162
|
+
if (native) return native.connect(address, port, family === 'IPv6', status => req.oncomplete?.(status, this, req, true, true));
|
|
149
163
|
const target = Number(port);
|
|
150
164
|
const reachable = isThisHost(address);
|
|
151
165
|
if (!this.local) {
|
|
152
166
|
const mine = allocatePort();
|
|
153
167
|
ephemeralPorts.add(mine);
|
|
154
|
-
this.
|
|
168
|
+
this.onLastReferenceClose(() => { ephemeralPorts.delete(mine); });
|
|
155
169
|
this.local = { address: family === 'IPv6' ? '::1' : '127.0.0.1', family, port: mine };
|
|
156
170
|
}
|
|
157
171
|
this.remote = { address, family, port: target };
|
|
@@ -165,7 +179,7 @@ export class TCP extends LibuvStreamWrap {
|
|
|
165
179
|
req.oncomplete?.(UV_ECONNREFUSED, this, req, true, true);
|
|
166
180
|
return;
|
|
167
181
|
}
|
|
168
|
-
const accepted = new TCP(constants.SOCKET);
|
|
182
|
+
const accepted = new (server.constructor as typeof TCP)(constants.SOCKET);
|
|
169
183
|
accepted.local = { ...(server.local as SockName), port: target };
|
|
170
184
|
accepted.remote = { ...(this.local as SockName) };
|
|
171
185
|
// An accepted connection is a handle of the loop that is serving, not of
|
|
@@ -193,13 +207,20 @@ export class TCP extends LibuvStreamWrap {
|
|
|
193
207
|
|
|
194
208
|
/** libuv's `dup()`: the same connection, under both its names. */
|
|
195
209
|
override duplicate(): TCP {
|
|
196
|
-
const
|
|
210
|
+
const native = nativeStreamFor(this);
|
|
211
|
+
const copy = native ? native.duplicate() as TCP : new (this.constructor as typeof TCP)(constants.SOCKET);
|
|
197
212
|
if (this.local && this.remote) copy.adoptNames(this.local, this.remote);
|
|
198
|
-
copy.
|
|
213
|
+
if (!native) copy.shareConnectionFrom(this);
|
|
199
214
|
return copy;
|
|
200
215
|
}
|
|
201
216
|
|
|
202
217
|
getsockname(out: Partial<SockName>): number {
|
|
218
|
+
const native = nativeStreamFor(this);
|
|
219
|
+
if (native) {
|
|
220
|
+
const result = native.call({ operation: 'getsockname', id: native.descriptor.id });
|
|
221
|
+
if (result.address) Object.assign(out, result.address);
|
|
222
|
+
return result.status;
|
|
223
|
+
}
|
|
203
224
|
if (!this.local) return UV_EINVAL;
|
|
204
225
|
out.address = this.local.address;
|
|
205
226
|
out.family = this.local.family;
|
|
@@ -208,6 +229,12 @@ export class TCP extends LibuvStreamWrap {
|
|
|
208
229
|
}
|
|
209
230
|
|
|
210
231
|
getpeername(out: Partial<SockName>): number {
|
|
232
|
+
const native = nativeStreamFor(this);
|
|
233
|
+
if (native) {
|
|
234
|
+
const result = native.call({ operation: 'getpeername', id: native.descriptor.id });
|
|
235
|
+
if (result.address) Object.assign(out, result.address);
|
|
236
|
+
return result.status;
|
|
237
|
+
}
|
|
211
238
|
if (!this.remote) return UV_EINVAL;
|
|
212
239
|
out.address = this.remote.address;
|
|
213
240
|
out.family = this.remote.family;
|
|
@@ -231,11 +258,20 @@ export class TCP extends LibuvStreamWrap {
|
|
|
231
258
|
|
|
232
259
|
/** An engine handle carries no descriptor a program can hand it. */
|
|
233
260
|
open(_fd: number): number {
|
|
261
|
+
const native = nativeStreamFor(this);
|
|
262
|
+
if (native) {
|
|
263
|
+
const status = native.call({ operation: 'open', id: native.descriptor.id, fd: _fd }).status;
|
|
264
|
+
if (status === 0) this.fd = _fd;
|
|
265
|
+
return status;
|
|
266
|
+
}
|
|
234
267
|
return UV_ENOTSUP;
|
|
235
268
|
}
|
|
236
269
|
|
|
237
270
|
/** libuv's `uv_tcp_close_reset`: the peer reads ECONNRESET rather than EOF. */
|
|
238
271
|
reset(callback?: () => void): number {
|
|
272
|
+
if (this.closed) return UV_EBADF;
|
|
273
|
+
const native = nativeStreamFor(this);
|
|
274
|
+
if (native) return native.close(callback, true);
|
|
239
275
|
this.sendReset();
|
|
240
276
|
this.close(callback);
|
|
241
277
|
return 0;
|
|
@@ -246,9 +282,8 @@ export class TCP extends LibuvStreamWrap {
|
|
|
246
282
|
this.listening = false;
|
|
247
283
|
this.onconnection = null;
|
|
248
284
|
if (wasListening && this.local) { try { onCloseWatcher?.(this.local.port); } catch { /* as above */ } }
|
|
249
|
-
if (this.local && boundPorts.get(this.local.port) === this) boundPorts.delete(this.local.port);
|
|
250
|
-
if (this.ephemeral !== null) { ephemeralPorts.delete(this.ephemeral); this.ephemeral = null; }
|
|
251
285
|
}
|
|
252
286
|
}
|
|
253
287
|
|
|
288
|
+
registerNativeStreamConstructor('tcp', TCP);
|
|
254
289
|
export default { TCP, TCPConnectWrap, constants };
|