Skip to content

Commit 12adf59

Browse files
[release/8.0] Handle rejected promises inside incoming Invocation messages (#55230)
* Handle rejected promises inside incoming Invocation messages Incoming messages of Invocation type result in executing a clientMethod promise, but promise rejections are unhandled. This can result in a unhandled rejected promise, potentially resulting in Node.js process crashing. Note, this was previously detected via eslint and suppressed in code. To avoid this, catch promise rejections and log the failure. One scenario this case can happen is, if during the clientMethod call, the server connection is disconnected, the invokation will still attempt to send a completion message (through _sendWithProtocol). This will then result in a promise rejection, such as the following: ``` Cannot send data if the connection is not in the 'Connected' State. at HttpConnection.send (node_modules\@microsoft\signalr\dist\cjs\HttpConnection.js:95:35) at HubConnection._sendMessage (node_modules\@microsoft\signalr\dist\cjs\HubConnection.js:266:32) at HubConnection._sendWithProtocol (node_modules\@microsoft\signalr\dist\cjs\HubConnection.js:273:21) at HubConnection._invokeClientMethod (node_modules\@microsoft\signalr\dist\cjs\HubConnection.js:577:24) at processTicksAndRejections (node:internal/process/task_queues:95:5) ``` * Add test of callback sending response with a rejected promise Adds a test covering when callback invoked when server invokes a method on the client and then handles rejected promise on send When the unhandled promise fix is not applied to HubConnection, the following error fails the HubConnection.test.ts test suite: Send error 831 | connection.send = () => { 832 | promiseRejected = true; > 833 | return Promise.reject(new Error("Send error")); | ^ 834 | } 835 | p.resolve(); 836 | }); at TestConnection.connection.send (signalr/tests/HubConnection.test.ts:833:51) at HubConnection.send [as _sendMessage] (signalr/src/HubConnection.ts:422:32) at HubConnection._sendMessage [as _sendWithProtocol] (signalr/src/HubConnection.ts:433:25) at HubConnection._sendWithProtocol [as _invokeClientMethod] (signalr/src/HubConnection.ts:808:24) --------- Co-authored-by: Danny Amirault <[email protected]>
1 parent c21f27d commit 12adf59

File tree

2 files changed

+36
-2
lines changed

2 files changed

+36
-2
lines changed

src/SignalR/clients/ts/signalr/src/HubConnection.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -614,8 +614,10 @@ export class HubConnection {
614614

615615
switch (message.type) {
616616
case MessageType.Invocation:
617-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
618-
this._invokeClientMethod(message);
617+
this._invokeClientMethod(message)
618+
.catch((e) => {
619+
this._logger.log(LogLevel.Error, `Invoke client method threw error: ${getErrorString(e)}`)
620+
});
619621
break;
620622
case MessageType.StreamItem:
621623
case MessageType.Completion: {

src/SignalR/clients/ts/signalr/tests/HubConnection.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -818,6 +818,38 @@ describe("HubConnection", () => {
818818
});
819819
});
820820

821+
it("callback invoked when server invokes a method on the client and then handles rejected promise on send", async () => {
822+
await VerifyLogger.run(async (logger) => {
823+
const connection = new TestConnection();
824+
const hubConnection = createHubConnection(connection, logger);
825+
let promiseRejected = false;
826+
try {
827+
await hubConnection.start();
828+
const p = new PromiseSource<void>();
829+
hubConnection.on("message", async () => {
830+
// Force sending of response to error
831+
connection.send = () => {
832+
promiseRejected = true;
833+
return Promise.reject(new Error("Send error"));
834+
}
835+
p.resolve();
836+
});
837+
connection.receive({
838+
arguments: ["test"],
839+
nonblocking: true,
840+
target: "message",
841+
invocationId: "0",
842+
type: MessageType.Invocation,
843+
});
844+
845+
await p;
846+
expect(promiseRejected).toBe(true);
847+
} finally {
848+
await hubConnection.stop();
849+
}
850+
}, new RegExp("Invoke client method threw error: Error: Send error"));
851+
});
852+
821853
it("stop on handshake error", async () => {
822854
await VerifyLogger.run(async (logger) => {
823855
const connection = new TestConnection(false);

0 commit comments

Comments
 (0)