Skip to content

Commit 21cdaff

Browse files
committed
Settle failed MySQL readiness probes
1 parent 1bb606a commit 21cdaff

2 files changed

Lines changed: 21 additions & 2 deletions

File tree

packages/cli/src/runtime-services.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,17 +197,29 @@ export async function waitForMysqlProtocol(host: string, port: number, timeoutMs
197197
function mysqlHandshake(host: string, port: number, signal?: AbortSignal): Promise<void> {
198198
return new Promise((resolve, reject) => {
199199
const socket = createConnection({ host, port })
200+
let settled = false
200201
const timer = setTimeout(() => socket.destroy(new Error("connection timeout")), 1_000)
201202
const abort = () => socket.destroy(new Error("aborted"))
203+
const fail = (error: Error) => {
204+
if (settled) return
205+
settled = true
206+
reject(error)
207+
}
202208
signal?.addEventListener("abort", abort, { once: true })
203-
socket.once("error", reject)
209+
socket.once("error", fail)
204210
socket.once("data", (chunk: Buffer) => {
211+
if (settled) return
212+
settled = true
205213
clearTimeout(timer)
206214
socket.destroy()
207215
if (chunk.length < 5 || chunk[4] !== 10) reject(new Error("invalid MySQL protocol handshake"))
208216
else resolve()
209217
})
210-
socket.once("close", () => { clearTimeout(timer); signal?.removeEventListener("abort", abort) })
218+
socket.once("close", () => {
219+
clearTimeout(timer)
220+
signal?.removeEventListener("abort", abort)
221+
fail(new Error("connection closed before MySQL protocol handshake"))
222+
})
211223
})
212224
}
213225

tests/runtime-services.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ await waitForMysqlProtocol("127.0.0.1", address.port, 250)
2222
await new Promise<void>((resolve) => server.close(() => resolve()))
2323
await assert.rejects(waitForMysqlProtocol("127.0.0.1", address.port, 25), /readiness timed out/)
2424

25+
const closingServer = createServer((socket) => socket.end())
26+
await new Promise<void>((resolve) => closingServer.listen(0, "127.0.0.1", resolve))
27+
const closingAddress = closingServer.address()
28+
assert.ok(closingAddress && typeof closingAddress !== "string")
29+
await assert.rejects(waitForMysqlProtocol("127.0.0.1", closingAddress.port, 25), /readiness timed out/, "a pre-handshake close remains retryable instead of leaving an unsettled promise")
30+
await new Promise<void>((resolve) => closingServer.close(() => resolve()))
31+
2532
const calls: Array<{ args: string[]; env?: NodeJS.ProcessEnv; signal?: AbortSignal }> = []
2633
const dependencies: RuntimeServiceDependencies = {
2734
randomBytes: (size) => Buffer.alloc(size, 7),

0 commit comments

Comments
 (0)