socketduct 0.1.15 → 0.1.16
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 @@
|
|
|
1
|
+
Fixed a crash in `DeferredVirtualSocket` where a non-function callback value (e.g. `null`) passed to `write()` or `end()` threw `selectedCallback is not a function` instead of failing gracefully.
|
package/package.json
CHANGED
|
@@ -259,7 +259,7 @@ export class DeferredVirtualSocket extends VirtualSocket {
|
|
|
259
259
|
*/
|
|
260
260
|
write(chunk, encoding, callback) {
|
|
261
261
|
const selectedEncoding = typeof encoding === "string" ? encoding : undefined
|
|
262
|
-
const selectedCallback = typeof encoding === "function" ? encoding : callback
|
|
262
|
+
const selectedCallback = typeof encoding === "function" ? encoding : typeof callback === "function" ? callback : undefined
|
|
263
263
|
if (typeof chunk !== "string" && !ArrayBuffer.isView(chunk)) {
|
|
264
264
|
return selectedEncoding === undefined
|
|
265
265
|
? super.write(chunk, selectedCallback)
|
|
@@ -302,7 +302,7 @@ export class DeferredVirtualSocket extends VirtualSocket {
|
|
|
302
302
|
*/
|
|
303
303
|
end(chunk, encoding, callback) {
|
|
304
304
|
if (typeof chunk === "function") return super.end(chunk)
|
|
305
|
-
const selectedCallback = typeof encoding === "function" ? encoding : callback
|
|
305
|
+
const selectedCallback = typeof encoding === "function" ? encoding : typeof callback === "function" ? callback : undefined
|
|
306
306
|
if (chunk !== undefined) {
|
|
307
307
|
if (typeof encoding === "string") this.write(chunk, encoding)
|
|
308
308
|
else this.write(chunk)
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/* eslint-disable jsdoc/escape-inline-tags, jsdoc/require-param-type, jsdoc/require-returns */
|
|
2
|
+
import assert from "node:assert/strict"
|
|
3
|
+
import test from "node:test"
|
|
4
|
+
|
|
5
|
+
import {DeferredVirtualSocket} from "../../../src/client/deferred-virtual-socket.js"
|
|
6
|
+
import {VirtualSocket} from "../../../src/client/virtual-socket.js"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {{reserveMemoryError?: Error}} [options] - Optional rejection to inject into the byte budget.
|
|
10
|
+
* @returns {{reservedBytes: () => number, socket: import('../../../src/client/deferred-virtual-socket.js').DeferredVirtualSocket}} Deferred socket fixture with reserved-byte accounting.
|
|
11
|
+
*/
|
|
12
|
+
function deferredSocket(options = {}) {
|
|
13
|
+
let reservedBytes = 0
|
|
14
|
+
const socket = new DeferredVirtualSocket({host: "127.0.0.1", port: 4793}, {
|
|
15
|
+
budget: {
|
|
16
|
+
reserveMemory(bytes) {
|
|
17
|
+
if (options.reserveMemoryError) throw options.reserveMemoryError
|
|
18
|
+
reservedBytes += bytes
|
|
19
|
+
},
|
|
20
|
+
releaseMemory(bytes) {
|
|
21
|
+
reservedBytes -= bytes
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
onCancel: () => {},
|
|
25
|
+
ref: () => {},
|
|
26
|
+
unref: () => {}
|
|
27
|
+
})
|
|
28
|
+
return {reservedBytes: () => reservedBytes, socket}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Fake member connection that settles write and final callbacks on the next macrotask. */
|
|
32
|
+
function memberConnection() {
|
|
33
|
+
/** @type {Buffer[]} */
|
|
34
|
+
const writes = []
|
|
35
|
+
let ended = 0
|
|
36
|
+
return {
|
|
37
|
+
connection: /** @type {import('../../../src/client/virtual-socket.js').VirtualSocketOwner} */ (/** @type {unknown} */ ({
|
|
38
|
+
/**
|
|
39
|
+
* @param {number} _id @param {Buffer} data @param {(error?: Error | null) => void} callback
|
|
40
|
+
* @param data
|
|
41
|
+
* @param callback
|
|
42
|
+
*/
|
|
43
|
+
_writeStream: (_id, data, callback) => {
|
|
44
|
+
writes.push(data)
|
|
45
|
+
setImmediate(callback)
|
|
46
|
+
},
|
|
47
|
+
/**
|
|
48
|
+
* @param {number} _id @param {(error?: Error | null) => void} callback
|
|
49
|
+
* @param callback
|
|
50
|
+
*/
|
|
51
|
+
_endStream: (_id, callback) => {
|
|
52
|
+
ended += 1
|
|
53
|
+
callback()
|
|
54
|
+
},
|
|
55
|
+
_destroyStream: () => {},
|
|
56
|
+
_resumeReadable: () => {},
|
|
57
|
+
physicalAddress: () => ({address: "127.0.0.1", port: 43210}),
|
|
58
|
+
ref: () => {},
|
|
59
|
+
unref: () => {}
|
|
60
|
+
})),
|
|
61
|
+
ended: () => ended,
|
|
62
|
+
writes
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
test("DeferredVirtualSocket write ignores non-function callback values in both parameter slots", async () => {
|
|
67
|
+
const first = deferredSocket()
|
|
68
|
+
// @ts-expect-error Deliberate non-function callback value; the socket must ignore it.
|
|
69
|
+
assert.equal(first.socket.write("hello", null), true)
|
|
70
|
+
const closed = new Promise((resolve) => first.socket.once("close", resolve))
|
|
71
|
+
first.socket.destroy()
|
|
72
|
+
await closed
|
|
73
|
+
assert.equal(first.socket.destroyed, true)
|
|
74
|
+
|
|
75
|
+
const second = deferredSocket()
|
|
76
|
+
// @ts-expect-error Deliberate non-function callback value; the socket must ignore it.
|
|
77
|
+
assert.equal(second.socket.write("hello", "utf8", null), true)
|
|
78
|
+
const closedAgain = new Promise((resolve) => second.socket.once("close", resolve))
|
|
79
|
+
assert.doesNotThrow(() => second.socket.destroy())
|
|
80
|
+
await closedAgain
|
|
81
|
+
assert.equal(second.socket.destroyed, true)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
test("DeferredVirtualSocket write budget failure destroys the socket without crashing on non-function callback values", async () => {
|
|
85
|
+
const failure = new Error("budget exhausted")
|
|
86
|
+
const {socket} = deferredSocket({reserveMemoryError: failure})
|
|
87
|
+
const error = new Promise((resolve) => socket.once("error", resolve))
|
|
88
|
+
// @ts-expect-error Deliberate non-function callback value; the socket must ignore it.
|
|
89
|
+
assert.equal(socket.write("hello", "utf8", null), false)
|
|
90
|
+
assert.equal(await error, failure)
|
|
91
|
+
assert.equal(socket.destroyed, true)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
test("DeferredVirtualSocket write budget failure invokes the completion callback with the failure", async () => {
|
|
95
|
+
const failure = new Error("budget exhausted")
|
|
96
|
+
const {reservedBytes, socket} = deferredSocket({reserveMemoryError: failure})
|
|
97
|
+
/** @type {Error | null | undefined} */
|
|
98
|
+
let callbackError
|
|
99
|
+
const error = new Promise((resolve) => socket.once("error", resolve))
|
|
100
|
+
assert.equal(socket.write("hello", "utf8", (received) => {
|
|
101
|
+
callbackError = received
|
|
102
|
+
}), false)
|
|
103
|
+
await error
|
|
104
|
+
assert.equal(callbackError, failure)
|
|
105
|
+
assert.equal(reservedBytes(), 0)
|
|
106
|
+
assert.equal(socket.destroyed, true)
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
test("DeferredVirtualSocket write completion after member assignment ignores non-function callback values", async () => {
|
|
110
|
+
const {socket} = deferredSocket()
|
|
111
|
+
const member = memberConnection()
|
|
112
|
+
socket.assign(new VirtualSocket(member.connection, 1, {host: "127.0.0.1", port: 4793}), "gateway-1")
|
|
113
|
+
// @ts-expect-error Deliberate non-function callback value; the socket must ignore it.
|
|
114
|
+
assert.equal(socket.write("hello", "utf8", null), true)
|
|
115
|
+
await new Promise((resolve) => setImmediate(resolve))
|
|
116
|
+
const written = member.writes[0]
|
|
117
|
+
assert(written !== undefined)
|
|
118
|
+
assert.equal(written.toString(), "hello")
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
test("DeferredVirtualSocket end ignores non-function callback values in both parameter slots", async () => {
|
|
122
|
+
const first = deferredSocket()
|
|
123
|
+
assert.doesNotThrow(() => {
|
|
124
|
+
// @ts-expect-error Deliberate non-function callback value; the socket must ignore it.
|
|
125
|
+
first.socket.end("hello", null)
|
|
126
|
+
})
|
|
127
|
+
assert.equal(first.socket.writableEnded, true)
|
|
128
|
+
const closed = new Promise((resolve) => first.socket.once("close", resolve))
|
|
129
|
+
first.socket.destroy()
|
|
130
|
+
await closed
|
|
131
|
+
assert.equal(first.socket.destroyed, true)
|
|
132
|
+
|
|
133
|
+
const second = deferredSocket()
|
|
134
|
+
assert.doesNotThrow(() => {
|
|
135
|
+
// @ts-expect-error Deliberate non-function callback value; the socket must ignore it.
|
|
136
|
+
second.socket.end("hello", "utf8", null)
|
|
137
|
+
})
|
|
138
|
+
assert.equal(second.socket.writableEnded, true)
|
|
139
|
+
const closedAgain = new Promise((resolve) => second.socket.once("close", resolve))
|
|
140
|
+
second.socket.destroy()
|
|
141
|
+
await closedAgain
|
|
142
|
+
assert.equal(second.socket.destroyed, true)
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
test("DeferredVirtualSocket end does not forward non-function callback values to the stream", async () => {
|
|
146
|
+
const {socket} = deferredSocket()
|
|
147
|
+
assert.doesNotThrow(() => {
|
|
148
|
+
// @ts-expect-error Deliberate non-function callback value; the socket must ignore it.
|
|
149
|
+
socket.end("hello", "utf8", 42)
|
|
150
|
+
})
|
|
151
|
+
assert.equal(socket.writableEnded, true)
|
|
152
|
+
const closed = new Promise((resolve) => socket.once("close", resolve))
|
|
153
|
+
socket.destroy()
|
|
154
|
+
await closed
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
test("DeferredVirtualSocket end completion after member assignment invokes the callback on finish", async () => {
|
|
158
|
+
const {socket} = deferredSocket()
|
|
159
|
+
const member = memberConnection()
|
|
160
|
+
socket.assign(new VirtualSocket(member.connection, 1, {host: "127.0.0.1", port: 4793}), "gateway-1")
|
|
161
|
+
let finishedCallback = false
|
|
162
|
+
const finished = new Promise((resolve) => socket.once("finish", resolve))
|
|
163
|
+
socket.end("hello", "utf8", () => {
|
|
164
|
+
finishedCallback = true
|
|
165
|
+
})
|
|
166
|
+
await finished
|
|
167
|
+
assert.equal(finishedCallback, true)
|
|
168
|
+
assert.equal(socket.writableFinished, true)
|
|
169
|
+
assert.equal(member.ended(), 1)
|
|
170
|
+
})
|
|
@@ -41,7 +41,9 @@ test("TensorBuzz exclusively owns verification and container build validation",
|
|
|
41
41
|
assert.match(tensorbuzz, /privileged: true/)
|
|
42
42
|
assert.match(tensorbuzz, /docker build --tag socketduct-ci \./)
|
|
43
43
|
assert.doesNotMatch(tensorbuzz, /docker (?:login|push)/)
|
|
44
|
-
|
|
44
|
+
// release-patch refuses to publish a package marked "private": true, so the
|
|
45
|
+
// published UNLICENSED npm release requires the flag to be absent or false.
|
|
46
|
+
assert.notEqual(packageMetadata.private, true)
|
|
45
47
|
assert.equal(packageMetadata.license, "UNLICENSED")
|
|
46
48
|
})
|
|
47
49
|
|