From 306a95cd0425f6fd8ca15cde30720d9229edd6b0 Mon Sep 17 00:00:00 2001
From: z77ma <vlad.krb@gmail.com>
Date: Mon, 24 Jan 2022 18:27:30 +0100
Subject: [PATCH] Fix: The next event handler is not called if the previous
 event handler removes itself (#39716)

---
 .../clients/ts/signalr/src/HubConnection.ts   |  3 +-
 .../ts/signalr/tests/HubConnection.test.ts    | 46 +++++++++++++++++++
 2 files changed, 48 insertions(+), 1 deletion(-)

diff --git a/src/SignalR/clients/ts/signalr/src/HubConnection.ts b/src/SignalR/clients/ts/signalr/src/HubConnection.ts
index 3f1363374c7..aaaf27d8c0d 100644
--- a/src/SignalR/clients/ts/signalr/src/HubConnection.ts
+++ b/src/SignalR/clients/ts/signalr/src/HubConnection.ts
@@ -674,8 +674,9 @@ export class HubConnection {
     private _invokeClientMethod(invocationMessage: InvocationMessage) {
         const methods = this._methods[invocationMessage.target.toLowerCase()];
         if (methods) {
+            const methodsCopy = methods.slice();
             try {
-                methods.forEach((m) => m.apply(this, invocationMessage.arguments));
+                methodsCopy.forEach((m) => m.apply(this, invocationMessage.arguments));
             } catch (e) {
                 this._logger.log(LogLevel.Error, `A callback for the method ${invocationMessage.target.toLowerCase()} threw error '${e}'.`);
             }
diff --git a/src/SignalR/clients/ts/signalr/tests/HubConnection.test.ts b/src/SignalR/clients/ts/signalr/tests/HubConnection.test.ts
index 1e03dbb511a..cc0f2e074cf 100644
--- a/src/SignalR/clients/ts/signalr/tests/HubConnection.test.ts
+++ b/src/SignalR/clients/ts/signalr/tests/HubConnection.test.ts
@@ -918,6 +918,52 @@ describe("HubConnection", () => {
             });
         });
 
+        it("unsubscribing dynamically doesn't affect the current invocation loop", async () => {
+            await VerifyLogger.run(async (logger) => {
+                const eventToTrack = "eventName";
+
+                const connection = new TestConnection();
+                const hubConnection = createHubConnection(connection, logger);
+                try {
+                    await hubConnection.start();
+
+                    let numInvocations1 = 0;
+                    let numInvocations2 = 0;
+                    const callback1 = () => {
+                        hubConnection.off(eventToTrack, callback1);
+                        numInvocations1++;
+                    }
+                    const callback2 = () => numInvocations2++;
+
+                    hubConnection.on(eventToTrack, callback1);
+                    hubConnection.on(eventToTrack, callback2);
+
+                    connection.receive({
+                        arguments: [],
+                        nonblocking: true,
+                        target: eventToTrack,
+                        type: MessageType.Invocation,
+                    });
+
+                    expect(numInvocations1).toBe(1);
+                    expect(numInvocations2).toBe(1);
+
+                    connection.receive({
+                        arguments: [],
+                        nonblocking: true,
+                        target: eventToTrack,
+                        type: MessageType.Invocation,
+                    });
+
+                    expect(numInvocations1).toBe(1);
+                    expect(numInvocations2).toBe(2);
+                }
+                finally {
+                    await hubConnection.stop();
+                }
+            });
+        });
+
         it("unsubscribing from non-existing callbacks no-ops", async () => {
             await VerifyLogger.run(async (logger) => {
                 const connection = new TestConnection();
-- 
GitLab