Skip to content

Commit 4326290

Browse files
authored
Fix node.js process termination due to delayed websocket error callback (#3065)
* fix unhandled error and add test case * reduce test time * update HISTORY * assert for trace message
1 parent 5c816e4 commit 4326290

File tree

3 files changed

+61
-1
lines changed

3 files changed

+61
-1
lines changed

packages/xrpl/HISTORY.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xr
44

55
## Unreleased
66

7+
### Fixed
8+
* Fixes node.js process termination when websocket send method errors after connection is closed.
9+
710
## 4.4.0 (2025-07-29)
811

912
### Added

packages/xrpl/src/client/connection.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,18 @@ export class Connection extends EventEmitter {
301301
>(request, timeout ?? this.config.timeout)
302302
this.trace('send', message)
303303
websocketSendAsync(this.ws, message).catch((error) => {
304-
this.requestManager.reject(id, error)
304+
try {
305+
this.requestManager.reject(id, error)
306+
} catch (err) {
307+
if (err instanceof XrplError) {
308+
this.trace(
309+
'send',
310+
`send errored after connection was closed: ${err.toString()}`,
311+
)
312+
} else {
313+
this.trace('send', String(err))
314+
}
315+
}
305316
})
306317

307318
return responsePromise

packages/xrpl/test/connection.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -979,4 +979,50 @@ describe('Connection', function () {
979979
},
980980
TIMEOUT,
981981
)
982+
983+
it(
984+
'Delayed websocket error callback on send',
985+
async () => {
986+
const traceMessages: string[] = []
987+
// @ts-expect-error -- Testing private member
988+
clientContext.client.connection.trace = (
989+
id: string,
990+
message: string,
991+
): void => {
992+
traceMessages.push(`${id}: ${message}`)
993+
}
994+
995+
// @ts-expect-error -- Testing private member
996+
clientContext.client.connection.ws.send = function (
997+
_ignore,
998+
sendCallback,
999+
): void {
1000+
// server_info request will timeout in 0.5s, but we send an error after 1s
1001+
setTimeout(() => {
1002+
sendCallback({ message: 'some error' })
1003+
}, 1000)
1004+
}
1005+
1006+
await clientContext.client.connection
1007+
.request({ command: 'server_info' }, 500)
1008+
.then(() => {
1009+
assert.fail('Should throw TimeoutError')
1010+
})
1011+
.catch((error) => {
1012+
assert(error instanceof TimeoutError)
1013+
assert.include(error.message, 'Timeout for request')
1014+
})
1015+
1016+
// wait to ensure that XrplError is not thrown after test is done
1017+
await new Promise((resolve) => {
1018+
setTimeout(resolve, 2000)
1019+
})
1020+
1021+
assert.includeMembers(traceMessages, [
1022+
'send: send errored after connection was closed: [XrplError(No existing promise with id 1, ' +
1023+
'{"type":"reject","error":{"name":"DisconnectedError","data":{"message":"some error"}}})]',
1024+
])
1025+
},
1026+
TIMEOUT,
1027+
)
9821028
})

0 commit comments

Comments
 (0)