Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SignalR connection gets terminated after 5 minutes in Chrome #31079

Closed
mgazell opened this issue Mar 19, 2021 · 76 comments
Closed

SignalR connection gets terminated after 5 minutes in Chrome #31079

mgazell opened this issue Mar 19, 2021 · 76 comments
Assignees
Labels
area-signalr Includes: SignalR clients and servers
Milestone

Comments

@mgazell
Copy link

mgazell commented Mar 19, 2021

I'm facing an issue maintaining a signalR connection using the @microsoft/signalr client package. After establishing a connection and 5 minutes of tab inactivity (user active in another tab or window), the connection gets closed. This seems to be related to recent policy changes in Chrome (JavaScript throttling) forcing WebSocket connection termination in 5 minutes of tab inactivity.

You can reproduce this issue using this serverless signalR sample at docs.microsoft.com

The only change that I did on the sample was to change the reference from @aspnet/signalr to @microsoft-signalR in the index.html file

<script src="https://cdn.jsdelivr.net/npm/@aspnet/[email protected]/dist/browser/signalr.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/5.0.3/signalr.min.js"></script>

Please see snip from my console below.

image

@javiercn javiercn added the area-signalr Includes: SignalR clients and servers label Mar 19, 2021
@BrennanConroy
Copy link
Member

I can't repro this issue, what OS are you on? What version is the OS? Did you make any more modifications to the app?

@BrennanConroy BrennanConroy added the Needs: Author Feedback The author of this issue needs to respond in order for us to continue investigating this issue. label Mar 22, 2021
@mgazell
Copy link
Author

mgazell commented Mar 22, 2021

I'm on Windows 10 (version 1709) and using Chrome (Version 89.0.4389.90 (Official Build) (64-bit))

No, I didn't make any other change other than the script reference change that I highlighted.

@ghost ghost added Needs: Attention 👋 This issue needs the attention of a contributor, typically because the OP has provided an update. and removed Needs: Author Feedback The author of this issue needs to respond in order for us to continue investigating this issue. labels Mar 22, 2021
@Davilink
Copy link

Davilink commented Mar 22, 2021

I can't repro this issue, what OS are you on? What version is the OS? Did you make any more modifications to the app?

I was able to reproduce this issue with Chrome on Windows like every single time:

  1. Open a tab using signalr with websocket
  2. Wait for the connection to be established
  3. Switch a tab
  4. Minimize Chrome
  5. Wait 5 minutes (10 minutes to be sure)
  6. Go back to the original tab
  7. Websocket is disconnected

I try to reproduce it on Chrome Linux, but i was under the impression that wasn't reproducible under Linux, but i did...only 1 time, and i cannot reproduce it anymore... (really strange), but on Chrome Windows it's every time.

Other reproduce step:
SignalR/SignalR#4536 (comment)

@Davilink
Copy link

Davilink commented Mar 22, 2021

Workaround using the « Page Visibility Api » (https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API)

const connection: HubConnection = new HubConnectionBuilder()
  .withUrl('/hub/MyHub')
  .build();

document.AddEventListener('visibilitychange', () => {
  if (document.visibilityState === 'visible' && connection.state !== HubConnectionState.Connected) {
    connection.start();
  }
}

Note: This workaround will not work for all use case because you lose data when the connection is drop. Other thing to take into consideration is the fact that it's a new connection, so if you are using groups, you will need to resubscribe to the group

@IanYates
Copy link

Workaround using the « Page Visibility Api » (https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API)

const connection: HubConnection = new HubConnectionBuilder()
  .withUrl('/hub/MyHub')
  .build();

document.AddEventListener('visibilitychange', () => {
  if (document.visibilityState === 'visible' && connection.state !== HubConnectionState.Connected) {
    connection.start();
  }
}

Note: This workaround will not work for all use case because you lose data when the connection is drop.

Good point about the data loss, but it's at least helpful to have the notification of the issue so we might be able to tell the user to expect issues. Thanks!

@mgazell
Copy link
Author

mgazell commented Mar 25, 2021

Hi @BrennanConroy ,

Are you still not able to repro the issue? Please let me know if you need more inputs.

@Vikki123
Copy link

Vikki123 commented Mar 25, 2021

We are also facing the same issue. We are also seeing the same behavior once the tab is minimized for 5 mins. We do not support re-connect option as its not feasible for the use case. Any workaround to keep the ping alive ?.

We are using @microsoft/signalr": "3.1.2

@prasadmamidi7
Copy link

I am also facing same problem

Use case

  • Create asp.net core singlar core application as backend app
  • Create angular app as frontend app
  • Go with default values for ClientTimeoutInterval (30sec) and KeepAliveInterval (15sec)
  • Launch angular app on chrome browser , miminize the browser for 7 mins
  • Signalr Hub will get OnDisconnectedAsync on backend app and websocket connection is closed.

Application version

Chrome Browser: Version 89.0.4389.90 (Official Build) (64-bit)
Asp.net core : 3.1.2
Angular -signalr core :3.1.2

Note: this behaviour is not observed on Microsoft Edge and Firefox .

Any workaround much appreciated.

@BrennanConroy
Copy link
Member

Are you still not able to repro the issue?

Still can't repro, We have a rough idea of where there could be an issue with the library with the new Chrome "feature", so we'll try to figure something out there. And probably rely on the community to test it out :)

Any workaround much appreciated.

A workaround would probably be to change the server-side and client-side timeout values to be over 1 minute.

@Vikki123
Copy link

@BrennanConroy I quite dint understand how increasing the timeout to 1min would solve this issue. Isnt it going to fail anyways after one min as the connection is disconnected already due to inactivity.?

1 similar comment
@Vikki123

This comment has been minimized.

@Davilink
Copy link

Davilink commented Mar 25, 2021

Other workaround: using the onclose event

let isCloseRequested = false;

const connection: HubConnection = new HubConnectionBuilder()
  .withUrl('/hub/MyHub')
  .build();

connection.onclose(error => {
  if (!isCloseRequested) {
    setTimeout(() => connection.start(), 0);
  }

  if (error) {
    console.error(error);
  }
});

export function start(): Promise<void> {
  isCloseRequested = false;
  return connection.start();
}

export function stop(): Promise<void> {
  isCloseRequested = true;
  return connection.stop();
}

Note: This workaround will not work for all use case because you lose data when the connection is drop. Other thing to take into consideration is the fact that it's a new connection, so if you are using groups, you will need to resubscribe to the group.

UPDATE: attempt to fix the « resubscribe to the group »

let isCloseRequested = false;
let reconnectFn: (() => Promise<void>) | null = null;

const connection: HubConnection = new HubConnectionBuilder()
  .withUrl('/hub/MyHub')
  .build();

connection.onclose(error => {
  if (!isCloseRequested) {
    setTimeout(() => connection.start().then(() => reconnectFn?.()), 0);
  }

  if (error) {
    console.error(error);
  }
});

export function start(): Promise<void> {
  isCloseRequested = false;
  return connection.start();
}

export function stop(): Promise<void> {
  isCloseRequested = true;
  return connection.stop();
}

export function join(roomId: string): Promise<void> {
  reconnectFn = () => connection.invoke('JoinRoom', roomId);
  return reconnectFn();
}

export function leave(roomId: string): Promise<void> {
  reconnectFn = null;
  return connection.invoke('LeaveRoom', roomId);
}

@andrew-tevent
Copy link

I hope I'm not hijacking this, but I am seeing something similar in my Blazor WebAssembly app - in that the SignalR Core Client stops responding to KeepAlives from the server. The server, not seeing a response within the KeepAlive Timeout closes the connection - the client immediately reconnects (I have auto reconnect on)

image

This appears to be, like described, when the user isn't necessarily active in the app. It is quite easily reproducible (but not every time for some reason)

@BrennanConroy BrennanConroy added this to the Next sprint planning milestone Mar 26, 2021
@ghost
Copy link

ghost commented Mar 26, 2021

Thanks for contacting us.
We're moving this issue to the Next sprint planning milestone for future evaluation / consideration. We will evaluate the request when we are planning the work for the next milestone. To learn more about what to expect next and how this issue will be handled you can read more about our triage process here.

@BrennanConroy BrennanConroy removed the Needs: Attention 👋 This issue needs the attention of a contributor, typically because the OP has provided an update. label Mar 26, 2021
@sonnywood
Copy link

I was working on this issue last week. We have a telephony application using Signalr to push messages to the browser. I am tracking every connect, reconnect, and disconnect from the server. I was lucky enough to be working on a laptop with low memory so this issue was more pronounced in my situation. I have Windows 10 with 4GB and 85% used. It appears that browser updates to Chrome, Edge, Firefox etc. have come with a new feature to suspend activity on inactive tabs when memory is low.

In my testing:

Firefox

  • Unload on low memory is turned off by default.
  • Did not suspend activity on my inactive tab but did cause a reconnect after approximately 5 hours. The reconnect used the same connection id so it didn't cause any issue with our tracking of the user's connection id.

Firefox_SleepingTabs_Settings
Firefox_Logs

Edge

  • Edge has settings to allow ignoring specific sites, setting specific times or turning it off.
  • Sleeping tabs is turned on and set to 2 hours by default.
  • Didn't cause a disconnect but the tab was still suspended. It was not possible to send a signalr message to the browser.
  • When tab became active the Signalr caused a connect event but passed the same connection id.

Edge_SleepingTabs_Settings
Edge_Logs

Chrome

  • Had a setting in the flags to turn it on/off in older versions but it is now removed.
  • Tab freezing is turned on by default.
  • After 5 minutes the tab became frozen.
  • Signalr started disconnecting ever 3 minutes and reconnecting after 30 seconds.
  • A new connection id was created.
  • Activity was suspended on the tab.
  • When tab became active the behaviror was inconsistent. Sometimes I saw old messages. Sometimes the connection was still not working.

Chrome_SleepingTabs_Settings
Chrome_Logs

@IanYates
Copy link

IanYates commented Apr 6, 2021

@sonnywood - that's a good summary. Thanks!

@jahmai-ca
Copy link

Thanks for the great info @sonnywood! I am seeing something very similar, in that Chrome lists our signalR websocket being reconnected periodically (between every 1 and 5 minutes).

@WayneHiller
Copy link

This is also happening in the "Microsoft.AspNetCore.SignalR.Client" Version="5.0.4" in a Blazor Wasm app.

@BrennanConroy
Copy link
Member

Oh right, thanks for mentioning that. I thought about it last night but then forgot today :)

@WayneHiller
Copy link

For reference I was able to manually fix the issue in a JS client (jquery.signalR-2.4.0.js) using this pull request.

SignalR/SignalR#4544

@prasadmamidi7
Copy link

Thank you for the support. Problem is not observed after migrating to singlar Core javascript library 3.1.15. Can you please confirm whether this Client library version 3.1.15 is comptiable to Server library version 3.1.1 (i.e. asp.net core side)

@mgazell mgazell closed this as completed May 27, 2021
@BrennanConroy
Copy link
Member

Can you please confirm whether this Client library version 3.1.15 is comptiable to Server library version 3.1.1

Yes

@aosyatnik
Copy link

The disconnect can only happen within 5mins? Cause we are using 5.0.6 version and experiencing disconnect because of missing ping response from a client (Connection aborted. Reason: Connection ping timeout.), but it happens not regularly(we are running the app during the night and sometimes it gets disconnected and can not reconnect anymore).

@ajbeaven
Copy link

I'm also still experiencing this in 5.0.7 on Microsoft Edge. Appears to timeout when pings stop being sent to the server after 5 minutes of inactivity.

image

I needed to capture this using Fiddler as having devtools opened in the background appears to make the tab stay active. I've saved the request in fiddler if you want to grab a copy.

@BrennanConroy
Copy link
Member

From a quick look at Edge's heuristics for sleeping tabs it doesn't look like SignalR can do anything there.

https://techcommunity.microsoft.com/t5/articles/sleeping-tabs-faq/m-p/1705434

What activities prevent a site from going to sleep?
To help you stay on task and limit potential compatibility issues, the following activities prevent sites from going to sleep:
* The page is currently visible (active tab)
* The page is currently holding a Web Lock or an IndexedDB transaction
* The page is sharing its BrowsingInstance with another page
* The page is your company’s internal (intranet) site
* The page is currently being inspected by DevTools
* The page is currently playing audio
* The page is currently capturing a window or screen
* The page is currently capturing user media (webcam, microphone, etc)
* The page is currently being mirrored (casting, etc)
* The page is on the user’s block list in Settings
* The page is currently using WebUSB
* We will continue to evolve this list as needed.

So short of the SignalR library spinning up more expensive work like capturing the screen (which would require the user to allow the website to do that), we can't do anything.

You can add websites to be excluded if you know they need it.

@ajbeaven
Copy link

Would regularly opening then closing a web lock work for this purpose or does that fall under the expensive work category?

@WayneHiller
Copy link

@@ajbeaven Are you talking about the js client? I thought I read the Edge had a 2 hour limit before putting a page to sleep. All the issues boil down to chromium throttling settimouts that have nested calls more than 5 deep after a page is sleeping for 5 mins

@IanYates
Copy link

@ajbeaven & @WayneHiller - when I saw Edge implemented its sleeping tabs, I saw it said that it wouldn't affect pages that had active web locks.
So, being slightly abusive, I added a small bit of JS code to our pages to take a web lock with a random name on page load. I'm not sure if that is having any positive effect, and certainly it was Chrome rather than Edge that was causing us massive issues in ASP.NET SignalR (not Core - but affected in the same way), but it hasn't hurt us either.
Ours is a line of business application where the user leaving it on there screen for an extended period of time is to be expected, and they will want the updates to continue to flow.

@ajbeaven
Copy link

ajbeaven commented Jun 29, 2021

@WayneHiller Yep, the JS client. Ahh, based on the blog post that introduces this feature (linked above), it looks like you are right about the default being a 2 hour timeout. I must have reduced it to 5 minutes in the settings to try the feature out. Whether it's 5 minutes or 2 hours though, it's still a problem.

@IanYates see note about the 2 hour default above, which is possibly why it wasn't as problematic for you in Edge. Good to know that web lock hack didn't cause any problems for you - I might do something similar if this can't be integrated in to the JS client.

@WayneHiller
Copy link

@ajbeaven As far as I understand the issue was fixed in the latest js client. I copied the code changes over to my signalr js file and it fixed the chrome issue. Not sure about edge tho. I am using the .net client in a Blazor webassembly app and it is not fixed there yet so might need to try the lock trick

@aosyatnik
Copy link

@ajbeaven @WayneHiller sorry, I didn't get it, did you find the solution for the disconnect issue? We have almost the same scenario, that user wants to have a running signalr connection in inactive tab for many hours perfectly without any disconnects.

@WayneHiller
Copy link

@aosyatnik As far as I know the Chrome Throttling issue on sleeping pages was fixed in the newer versions of the JS client. I have not checked in Edge lately tho, will do that today.

@aosyatnik
Copy link

@aosyatnik As far as I know the Chrome Throttling issue on sleeping pages was fixed in the newer versions of the JS client. I have not checked in Edge lately tho, will do that today.

@WayneHiller @microsoft/signalr 5.0.7, right?

@WayneHiller
Copy link

@aosyatnik Yes. I actually took the changes and put them into my jquery-signalR-2.4.0 file.
#31079 (comment) Note that this was only to fix a specific issue with a change Chrome made to throttle SetTimeout's that where chained more than 5 levels deep when a page went to sleep (5 mins in Chrome).

@WayneHiller
Copy link

WayneHiller commented Jun 29, 2021

@IanYates The web lock looks like it could fix the same issue that is happening in the .Net Client in a Blazor Webassembly app. Creating the lock is pretty easy but the trick is how to keep the lock active for the duration of the session without incurring overhead.

Oh I found this...

// Capture promise control functions:
let resolve, reject;
const p = new Promise((res, rej) => { resolve = res; reject = rej; });

// Request the lock:
navigator.locks.request('my_resource', lock => {
  // Lock is acquired.

  return p;
  // Now lock will be held until either resolve() or reject() is called.
});

@IanYates
Copy link

@WayneHiller , yeah I just acquire the lock with a random name shortly after page load and leave it alone, probably stored in some global but otherwise not touched again.
It doesn't cost anything to maintain, and since it has a random name it won't clash

@WayneHiller
Copy link

@IanYates From what I read the lock will be release as soon as the method returns so need a way to keep it locked, like with the promise above:

navigator.locks.request('my_resource', async lock => {
  // The lock has been acquired.
  await do_something();
  await do_something_else();
  // Now the lock will be released.
});

@IanYates
Copy link

@WayneHiller - that's the common example. But if you don't use async/await in the callback, and instead fall back to promises, you can work it a bit differently...

See https://developer.mozilla.org/en-US/docs/Web/API/Web_Locks_API#advanced_use

// Capture promise control functions:
let resolve, reject;
const p = new Promise((res, rej) => { resolve = res; reject = rej; });

// Request the lock:
navigator.locks.request('my_resource', lock => {
  // Lock is acquired.

  return p;
  // Now lock will be held until either resolve() or reject() is called.
});

You've got p. Just don't resolve() it

(I had to go find the code I'd written and then double-check my sanity as I suddenly doubted my understanding, until I found that advanced section again and went "aha")

@WayneHiller
Copy link

Ya seems like that will work fine for Chrome/Edge. The lock name is unique by domain so if you don't use locks for anything else no worries about the name. maybe something like "signalr-fix". I will try it in my Blazor Wasm app

@BrennanConroy
Copy link
Member

FYI, using #33970 to track tab freezing. WebLocks seem interesting.

@tofron
Copy link

tofron commented Jul 16, 2021

i'm so sorry. why this thread closed? i tried with blazor webassembly the signalR still disconnecting after 5 minutes. I already update my signalR with version 3.1.17, but it still disconnected after 5 minutes. is there any workaround that didn't posted here?

@BrennanConroy
Copy link
Member

Tracked by dotnet/runtime#51041

@WayneHiller
Copy link

@tofron It does not seem to be fixed yet. I tried the Web Lock tricks above but does not seem to be helping in Edge.

@nmylle
Copy link

nmylle commented Jul 20, 2021

Can someone tell me how to fix this problem?

@IanYates
Copy link

Can someone tell me how to fix this problem?

Which specific problem do you have? More to the point, are you using SignalR from asp.net, SignalR from asp.net core, server side blazor, or client side blazor wasm?

For the first three, the solution now is to just upgrade to the latest release.
For the blazor wasm I don't believe there's an elegant fix yet as it involves quite a bit more plumbing code.

@nmylle
Copy link

nmylle commented Jul 20, 2021

Can someone tell me how to fix this problem?

Which specific problem do you have? More to the point, are you using SignalR from asp.net, SignalR from asp.net core, server side blazor, or client side blazor wasm?

For the first three, the solution now is to just upgrade to the latest release.
For the blazor wasm I don't believe there's an elegant fix yet as it involves quite a bit more plumbing code.

Upgrading did fix the problem

@ghost ghost locked as resolved and limited conversation to collaborators Aug 19, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-signalr Includes: SignalR clients and servers
Projects
None yet
Development

No branches or pull requests