Skip to content

MQTT keepalive less than 60s and chrome 88 intensive timer throttling #1257

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

Closed
dirkkessler opened this issue Mar 8, 2021 · 35 comments · Fixed by #1636 or #1753
Closed

MQTT keepalive less than 60s and chrome 88 intensive timer throttling #1257

dirkkessler opened this issue Mar 8, 2021 · 35 comments · Fixed by #1636 or #1753

Comments

@dirkkessler
Copy link

dirkkessler commented Mar 8, 2021

  • it would be great to have some documentation regarding best practices in regards to keepalive and chrome intensive timer throttling
  • it is not uncommon for our application tab to be put in the background by our users since they can also spend a fair amount of time in other web applications
  • our keepalive is less than 60s since we need to know fairly quickly when the connection with the broker has been severed since we require timely message delivery
  • chome 88 only executes timers every minute during intensive timer throttling which means our keepalive does not happen within the configured time frame which in turn causes disconnects
  • please advise, thanks

https://docs.google.com/document/d/105Hlbcb1mTV06bT9aDpwBotY13eZiKV5Hl4PW5LlyeE/edit
https://developer.chrome.com/blog/timer-throttling-in-chrome-88/

AB#9481421

@Eric24
Copy link

Eric24 commented Apr 14, 2021

Just to add a bit of detail to this, it is absolutely an issue. It only happens during long periods of idle time (i.e. no message traffic), where the only traffic is pingreq/pingresp. The default keepalive setting of 60 seconds causes the pingresp to be "missed", so the connection is closed (perhaps this could be fixed internally to mqtt.js, to look for the pingresp message before closing the connection, which I'm assuming is driven by some kind of timer event).

But the quick fix is to extend the keepalive timer; I used 120 seconds (but it's possible that 90 seconds or something else between 60 and 120 seconds would also work).

Once Chrome starts its "intensive timer throttling", is a noticeable delay develops between pingreq and pingresp (which is usually sub-second with our broker), but with the longer keepalive interval, the pingresp message is "seen" before the connection is closed.

@kroutal
Copy link

kroutal commented Nov 24, 2021

Hi,
Paho-mqtt js is not impacted by this issue. I noticed that paho-mqtt js library reschedules the ping on pingresp .
paho-mqtt.js
It seems that reshecheduling the pings on "packetreceive" solves this issue

client.on("packetreceive", function() {
    client._shiftPingInterval();
});

On another thing would be to solve the reconnection which will not occur on reconnectperiod because of the throttling.

One thing that could be done would be to shedule the reconnection on the close event

client.on("close", function() {
    //schedule client._reconnect();
});

@carloscolombo
Copy link

carloscolombo commented Dec 6, 2021

In my case, as a work around, I offloaded the keepalive timer logic to a Web Worker. Web Workers are not throttled by the Chromium's Intensive Timer Throttling policy. I did the following:

Examples are in TS

  1. Set the keepalive timer to 0 to opt out of the library's internal timer that is being throttled.
this.client = mqtt.connect(myUrl, {
    [...],
    keepalive: 0,
    [...]
})
  1. Timer logic that will be used by Web Worker
/**
 * This Web Worker logic will notify our application to ping our MQTT connection to keep it alive.
 *
 */
function keepAlive() {
    const ctx: Worker = self as any;
    setInterval(() => {
        ctx.postMessage('keep-alive'); // will post to our application, doesn't need to be named 'keep-alive' can be anything
    }, 15000); // every 15 seconds
}
  1. Create Web Worker to use the timer logic above. In my case I turned the function above as a Blob. You don't have to, you can use the standard URL way of creating a Web Worker.
/**
 * Creates a web worker via blob (instead of file)
 *
 */
function createWebWorker(func: Function) {
    return new Worker(URL.createObjectURL(new Blob(["(" + func.toString() + ")()"], { type: 'text/javascript' })));
}
  1. Now in the connect event listener, create the Web Worker and add the listener to pingreq our connection.
this.client.on('connect', () => {
        // create web worker using our "keepAlive()" function
        this.keepAliveWorker = createWebWorker(keepAlive);

        // event listener for our Web Worker
        this.keepAliveWorker.addEventListener('message', (event) => {
            // ping our mqtt connection to keep it alive
            (this.client as any)._sendPacket({ cmd: 'pingreq' });
        })
});
  1. Clean up Web Worker on disconnects
this.client.on('close', () => {
        // clean up keep alive web worker scheduler
        this.keepAliveWorker.terminate();
});

Hopefully this can help someone else as it did for me. Please point out any flaws in my logic to help myself and others that my run into this issue.

@pimenas
Copy link

pimenas commented Dec 13, 2021

@carloscolombo I see the following flaws in your solution

  • By setting keepalive to 0, on the mqtt connect options, you also disable the server side checking. According to spec

If the Keep Alive value is non-zero and the Server does not receive an MQTT Control Packet from the Client within one and a half times the Keep Alive time period, it MUST close the Network Connection to the Client as if the network had failed

  • I don't see any checking that the ping response was received. Maybe instead of _sendPacket you could call _checkPing (and also set pingResp to true initially).

@sunmd
Copy link

sunmd commented Feb 25, 2022

I also encountered this problem, thanks for the reminder. I seem that setting the pingResp on "packetreceive" solves this issue.

 this.client.on("packetreceive", () => {
     this.client.pingResp = true;
 });

@erichaus
Copy link
Contributor

erichaus commented May 8, 2022

@kroutal could you explain what that's actually doing?

    client._shiftPingInterval();

@kroutal
Copy link

kroutal commented Jun 14, 2022

@hoIIer

This is the current workaround I'm using:

....
this.mqttClient.on('packetsend', this.onPacketSend());
...
private onPacketReceived(mqttClient: MqttClient): OnPacketCallback {
  return (packet: Packet) => {
    //Used to fix chrome issue 
    if (packet.cmd == 'pingresp') {
      (mqttClient as any)._shiftPingInterval();
    }
  };
}

Each time a ping message is sent by the client. The server will send back a ping response and trigger the 'packetsend' callback. Since this is done through a websocket this will not be impacted by the throtling. Within the callback the call to _shiftPingInterval() will restart the ping timer. The throtling will only start after the second tick of the timer.

@github-actions
Copy link

This is an automated message to let you know that this issue has
gone 365 days without any activity. In order to ensure that we work
on issues that still matter, this issue will be closed in 14 days.

If this issue is still important, you can simply comment with a
"bump" to keep it open.

Thank you for your contribution.

@github-actions github-actions bot added the stale label Jun 15, 2023
@github-actions
Copy link

This issue was automatically closed due to inactivity.

@robertsLando
Copy link
Member

Hi everyone. I started helping maintaining this library. It would be super helpful if someone of you could write a solution to this on docs and/or even better try an implementation of one of the proposed solutions. We already have a var named IS_BROWSER that we use to detect browser env, that in combination to a specific connection option could be used to add the fix needed on browser

@swapnonil-b
Copy link

@robertsLando Perhaps we could use worker-timers https://www.npmjs.com/package/worker-timers when IS_BROWSER is true. Timers from this package are meant to be run in webworkers and thus be immune to the throttling from browsers.

@swapnonil-b
Copy link

swapnonil-b commented Aug 9, 2023

Also in terms of @carloscolombo 's solution, we can also use the worker-timers setInterval to handle things. Keeping most of the steps the same, but instead of spawning the web worker ourselves, we can just do this on the connect callback.

import { setInterval, clearInterval } from 'worker-timers';

...

this.client.on('connect', () => {
      // do your other stuff

     this.timer  = setInterval(() => {
          (this.client as any)._sendPacket({ cmd: 'pingreq' });
        }, 15 * 1000);
});

you would need to handle the clearInterval on the disconnect.

@robertsLando
Copy link
Member

Problem is we are actually using reInterval package to handle ping scheduling. We should drop retimer or think about adding workers support on that module when running on browser. Would you mind to submit a PR?

@robertsLando
Copy link
Member

robertsLando commented Aug 10, 2023

I have created an issue there: mcollina/retimer#10. I thinnk if we add the support there then we can import the setInterval and setTimeout functions directly from that package instead of adding worker-timers dep also on mqttjs

@robertsLando
Copy link
Member

Ok retimer@4 has been released with that change. Let me do a bump+release here 🚀

@RainfoxAri
Copy link

@robertsLando Any update? I didn't see it in the commit history and also not in the release notes, that's why i'm asking. :)

@robertsLando
Copy link
Member

I tried to use retimer latest but many tests fail then and I cannot find out why...

@oliverunger
Copy link

Can you give an update. When will this problem be fixed?

@robertsLando
Copy link
Member

I don't have an ETA for this, would you like to submit a PR?

@jellybins
Copy link

who can tell me this issue has been fixed in which release version?

@robertsLando
Copy link
Member

@jellybins I submitted a possible fix 4 months ago in version 5.0.0. I also tried to use the retimer solution without success

@oliverunger
Copy link

@jellybins I switched to eclipse paho javascript and that works great.

@robertsLando
Copy link
Member

Yeah that's cool last commit was 4 years ago on paho mqtt: https://github.com/eclipse/paho.mqtt.javascript

@oliverunger
Copy link

For me:
works but old > doesn't work but fancy

@robertsLando
Copy link
Member

@oliverunger I'm still waiting for someone to give me feedbacks about this, It could be it is working now...

@jellybins
Copy link

@oliverunger I'm still waiting for someone to give me feedbacks about this, It could be it is working now...

#1361 in this issue ,some one tested the version 5.0.3 still have same problem

@robertsLando
Copy link
Member

latest is 5.3.1, also mine was just a supposition that error was related to this, I need someone that finds a reliable way to reprouce this issue so I can fix it

@jellybins
Copy link

latest is 5.3.1, also mine was just a supposition that error was related to this, I need someone that finds a reliable way to reprouce this issue so I can fix it

version 4.x (such as the last 4.3.7) can reliable reproduce this issue ;and i could not upgrade to version 5.x because using vue2

@jellybins
Copy link

jellybins commented Dec 1, 2023

just use this, use the newest google chrome browser and just connect, no need to subscribe , when connected open the preview console and change the browser tab to background, then about every 5 minutes you'll get a disconnect console msg; Ps: i have tried to change the mqtt version to the newest ,but it went wrong;

@robertsLando
Copy link
Member

and i could not upgrade to version 5.x because using vue2

What? All my projects are using Vue 2 and I use mqttjs without any problems...

Ps: i have tried to change the mqtt version to the newest ,but it went wrong;

So at the end you have been able to try latest version and it still fails?

@jellybins
Copy link

and i could not upgrade to version 5.x because using vue2

What? All my projects are using Vue 2 and I use mqttjs without any problems...

Ps: i have tried to change the mqtt version to the newest ,but it went wrong;

So at the end you have been able to try latest version and it still fails?

you can try to change the mqtt version to newest in the last link i mentioned,then it compiled error

@robertsLando
Copy link
Member

robertsLando commented Dec 1, 2023

I've successfully reproduced this with vite example, it closes after 60s after being put on background. I will try to see what I can do

@robertsLando
Copy link
Member

Fix available starting from version 5.3.2

@jellybins
Copy link

jellybins commented Dec 5, 2023

Fix available starting from version 5.3.2

i tested that,it works normal now , in chinese “稳的一批”

@robertsLando
Copy link
Member

Thanks for your feedback @jellybins

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet