chrome.alarms is "racing" to catch up if system time changed - google-chrome-extension

I'm using chrome.alarm to display countdown on extension icon, which updates every second'ish.
The problem I'm facing is if I change system's time forward (manually or if computer woke up from sleep), the counter starts racing, updating several times a second in attempt to catch up to new system time, or if I change time backwards, the timer stops.
How can I fix this so it would simply "jump" to current time?
Test extension:
manifest.json
{
"manifest_version": 3,
"name": "Test timer",
"author": "test",
"description": "Test chrome.alarm",
"version": "0.0.1",
"permissions":
[
"alarms"
],
"action": {},
"background": {
"service_worker": "service_worker.js"
}
}
service_worker.js
let i = 0,
start = new Date().getTime(),
pad = (n,s=2) => ("0"+n).slice(-s),
time = d => pad(d.getHours()) + ":" + pad(d.getMinutes()) + ":" + pad(d.getSeconds()) + "." + pad(d.getMilliseconds(),3);
chrome.alarms.onAlarm.addListener(loop);
console.log("started");
loop();
function loop()
{
const now = new Date().getTime(),
//make sure timer doesn't drift from starting point
next = now - ((now - start) % 1000);
//repeat after 1sec
chrome.alarms.create({ when: next + 1000 });
chrome.action.setBadgeText({text:"" + (i = ++i % 1000)});
console.log("Date:", time(new Date(now)), "alarm:", time(new Date(next)));
}

I've tested your code, and I've got some new discoveries. I've run into some Service Worker issues and I think it might have something to do with your "racing" alarm.
If I keep the service worker page open all the time, it runs smoothly and properly.
If I don't open the service worker, it will either "racing" or restart after a while, even if I don't change the system time or let my device fall asleep.
Since you're using Manifest V3, I have to tell you that Manifest V3 has some issues of Service Worker. It breaks sometimes. For more information, you can read this doc. You can refer to these workarounds for sure.

Patient: "doctor, I have a pain in my back".
Doctor: "and when does it hurt?"
Patient: "only when I breathe"
Doctor: "thus it's all resolved. Don't breathe anymore"
Fiddling with system time and chorme.alarms isn't very wise.
Normally you can move the clock forward (just for testing), but turning it back can create unusual issues.
(unless You refresh\reload the extension first).
Instead, we should investigate the behavior after waking up the PC.
Furthermore, these alarms have been designed for intervals of not less than one minute.
We agree that this limitation doesn't apply in development, but setting one-second alarms is a bit bold, isn't it?
So avoid breathing and the problem will resolve itself :-)
Joke apart, You could open a minimized tab and establish a connection between service worker and that tab making a continous ping-pong.
Till the service worker will be alive you''ll be able to dispay the countdown in extension icon using just setTimeour\ setInterval.

Here is my current solution by using combination of performance.now() and setTimeout:
let i = 0,
start = new Date().getTime(),
pad = (n,s=2) => ("0"+n).slice(-s),
time = d => pad(d.getHours()) + ":" + pad(d.getMinutes()) + ":" + pad(d.getSeconds()) + "." + pad(d.getMilliseconds(),3),
timer,
perfPrev = 0;
chrome.alarms.onAlarm.addListener(loop);
console.log("started");
loop();
function loop()
{
const now = new Date().getTime(),
perfNow = performance.now();
//make sure timer doesn't drift from starting point
const next = now - ((now - start) % 1000);
//repeat after 1sec
chrome.alarms.create("loop", { when: next + 1000 });
//detect "racing", when system time changed forward
if (perfNow - perfPrev < 800)
return;
perfPrev = perfNow;
clearTimeout(timer);
//backup plan for when system time changed backwards the alarm won't fire on time
timer = setTimeout(() =>
{
chrome.alarms.clear("loop");
loop();
}, 1000);
chrome.action.setBadgeText({text:"" + (i = ++i % 1000)});
console.log("Date:", time(new Date(now)), "alarm:", time(new Date(next)));
}

Related

A script that runs every second in a chrome extension with manifest v3

This is my first chrome extension using manifest v3, and I want to make a timer in it.
This is supposed to update every second, and not run on any specific tab nor the popup window.
I tried to do this in my service worker:
let counter = 0
setInterval(() => {
counter++
}, 1000)
But that didn't work well, because after around half a minute, the service worker would go "inactive", and thus stop this loop.
So I am just looking for a way to make a loop that executes some code every 1 second. This loop always has to be running. And I do not really have a way to "launch" say a function every second from another page. I can start it once, but because of the service worker that goes inactive after a while, then this script has to either just never die or relaunch itself every second.
Is this even possible?
Google recommends using their "Alarm API" instead of timers to schedule running code: https://developer.chrome.com/docs/extensions/mv3/migrating_to_service_workers/#alarms
So instead of running a "setTimeout" or "setInterval" in your background worker like this:
const TIMEOUT = 3 * 60 * 1000; // 3 minutes in milliseconds
setTimeout(() => {
chrome.action.setIcon({
path: getRandomIconPath(),
});
}, TIMEOUT);
You should instruct Chrome on when to run it instead:
chrome.alarms.create({ delayInMinutes: 3 });
chrome.alarms.onAlarm.addListener(() => {
chrome.action.setIcon({
path: getRandomIconPath(),
});
});

MacOS Catalina freezing+crashing after running Node.JS load test script

I wrote up a simple load testing script that runs N number of hits to and HTTP endpoint over M async parallel lanes. Each lane waits for the previous request to finish before starting a new request. The script, for my specific use-case, is randomly picking a numeric "width" parameter to add to the URL each time. The endpoint returns between 200k and 900k of image data on each request depending on the width parameter. But my script does not care about this data and simply relies on garbage collection to clean it up.
const fetch = require('node-fetch');
const MIN_WIDTH = 200;
const MAX_WIDTH = 1600;
const loadTestUrl = `
http://load-testing-server.com/endpoint?width={width}
`.trim();
async function fetchAll(url) {
const res = await fetch(url, {
method: 'GET'
});
if (!res.ok) {
throw new Error(res.statusText);
}
}
async function doSingleRun(runs, id) {
const runStart = Date.now();
console.log(`(id = ${id}) - Running ${runs} times...`);
for (let i = 0; i < runs; i++) {
const start = Date.now();
const width = Math.floor(Math.random() * (MAX_WIDTH - MIN_WIDTH)) + MIN_WIDTH;
try {
const result = await fetchAll(loadTestUrl.replace('{width}', `${width}`));
const duration = Date.now() - start;
console.log(`(id = ${id}) - Width ${width} Success. ${i+1}/${runs}. Duration: ${duration}`)
} catch (e) {
const duration = Date.now() - start;
console.log(`(id = ${id}) - Width ${width} Error fetching. ${i+1}/${runs}. Duration: ${duration}`, e)
}
}
console.log(`(id = ${id}) - Finished run. Duration: ` + (Date.now() - runStart));
}
(async function () {
const RUNS = 200;
const parallelRuns = 10;
const promises = [];
const parallelRunStart = Date.now();
console.log(`Running ${parallelRuns} parallel runs`)
for (let i = 0; i < parallelRuns; i++) {
promises.push(doSingleRun(RUNS, i))
}
await Promise.all(promises);
console.log(`Finished parallel runs. Duration ${Date.now() - parallelRunStart}`)
})();
When I run this in Node 14.17.3 on my MacBook Pro running MacOS 10.15.7 (Catalina) with even a modest parallel lane number of 3, after about 120 (x 3) hits of the endpoint the following happens in succession:
Console output ceases in the terminal for the script, indicating the script has halted
Other applications such as my browser are unable to make network connections.
Within 1 - 2 mins other applications on my machine begin to slow down and eventually freeze up.
My entire system crashes with a kernel panic and the machine reboots.
panic(cpu 2 caller 0xffffff7f91ba1ad5): userspace watchdog timeout: remoted connection watchdog expired, no updates from remoted monitoring thread in 60 seconds, 30 checkins from thread since monitoring enabled 640 seconds ago after loadservice: com.apple.logd, total successful checkins since load (642 seconds ago): 64, last successful checkin: 10 seconds ago
service: com.apple.WindowServer, total successful checkins since load (610 seconds ago): 60, last successful checkin: 10 seconds ago
I can very easily stop of the progression of these symptoms by doing a Ctrl+C in the terminal of my script and force quitting it. Everything quickly gets back to normal. And I can repeat the experiment multiple times before allowing it to crash my machine.
I've monitored Activity Monitor during the progression and there is very little (~1%) CPU usage, memory usage reaches up to maybe 60-70mb, though it is pretty evident that the Network activity is peaking during the script's run.
In my search for others with this problem there were only two Stack Overflow articles that came close:
node.js hangs other programs on my mac
Node script causes system freeze when uploading a lot of files
Anyone have any idea why this would happen? It seems very dangerous that a single app/script could so easily bring down a machine without being killed first by the OS.

How can I handle pm2 cron jobs that run longer than the cron interval?

I have a cron job running on pm2 that sends notifications on a 5 second interval. Although it should never happen, I'm concerned that the script will take longer than 5 seconds to run. Basically, if the previous run takes 6 seconds, I don't want to start the next run until the first one finishes. Is there a way to handle this solely in pm2? Everything I've found says to use shell scripting to handle it, but it's not nearly as easy to replicate and move to new servers when needed.
As of now, I have the cron job just running in a never ending while loop (unless there's an error) that waits up to 5 seconds at the end. If it errors, it exits and reports the error, then restarts because it's running via pm2. I'm not too excited about this implementation though. Are there other options?
edit for clarification of my current logic -
function runScript() {
while (!err) {
// do stuff
wait(5 seconds - however long 'do stuff' took) // if it took 1 second to 'do stuff', then it waits 4 seconds
}
}
runScript()
This feels like a hacky way to get around the cron limits of pm2. It's possible that I'm just being paranoid... I just wanna make sure I'm not using antipatterns.
What do you mean you have the cron job running in a while loop? PM2 is starting a node process which contains a never-ending while loop that waits 5 seconds? Your implementation of a cron seems off to me, maybe you could provide more details.
Instead of a cron, I would use something like setTimeout method. Run your script using PM2 and in the script is a method like such:
function sendMsg() {
// do the work
setTimeout(sendMsg, 5000); // call sendMsg after waiting 5 seconds
}
sendMsg();
By doing it this way, your sendMsg function can take all the time it needs to run, and the next call will start 5 seconds after that. PM2 will restart your application if it crashes.
If you're looking to do it at specific 5 second intervals, but only when the method is not running, simply add a tracking variable to the equation, something like:
let doingWork = false;
function sendMsg() {
if (!doingWork) {
doingWork = true;
// do the work
doingWork = false;
}
}
setInterval(sendMsg, 5000); // call sendMsg every 5 seconds
You could replace setInterval with PM2 cron call on the script, but the variable idea remains the same.
To have exactly 5000 ms between the end your actions:
var myAsyncLongAction = function(cb){
// your long action here
return cb();
};
var fn = function(){
setTimeout(function(){
// your long action here
myAsyncLongAction(function(){
console.log(new Date().getTime());
setImmediate(fn);
});
}, 5000)
};
fn();
To have exactly 5000 ms between the start of your actions :
var myAsyncLongAction = function(cb){
// your long action here
setTimeout(function(){
return cb();
}, 1000);
};
var fn = function(basedelay, delay){
if(delay === undefined)
delay = basedelay;
setTimeout(function(){
// your long action here
var start = new Date().getTime();
myAsyncLongAction(function(){
var end = new Date().getTime();
var gap = end - start;
console.log("Action took "+(gap)+" ms, send next action in : "+(basedelay - gap)+" ms");
setImmediate(fn, basedelay, (gap < basedelay ? 1 : basedelay - gap));
});
}, delay);
};
fn(5000);

Building a chat app: How to get time

I am building a chat app currently with PubNub. The problem now is from the app/frontend point of view, how should it get the time (server time). If every message is sent to the server, I could get the server time there. But with a 3rd party service like PubNub, how can I manage this? Since app sends messages to PubNub rather than my server. I dont want to rely on local time as users might have inaccurate clocks.
The simplest solution I thought of is: When app starts up, get server time. Record the difference between local time and server time (diff = Date.now() - serverTime). When sending messages, the time will be Date.now() - diff. Is this correct so far?
I guess this solution assumes 0 transmission (or latency) time? Is there a more correct or recommended way to implement this?
Your use case is probably the reason why pubnub.time() exists.
In fact, they even have a code example describing your drift calculation.
https://github.com/pubnub/javascript/blob/1fa0b48227625f92de9460338c222152c853abda/examples/time-drift-detla-detection/drift-delta-detection.html
// Drift Functions
function now(){ return+new Date }
function clock_drift(cb) {
clock_drift.start = now();
PUBNUB.time(function(timetoken){
var latency = (now() - clock_drift.start) / 2
, server_time = (timetoken / 10000) + latency
, local_time = now()
, drift = local_time - server_time;
cb(drift);
});
if (clock_drift.ival) return;
clock_drift.ival = setInterval( function(){clock_drift(cb)}, 1000 );
}
// This is how you use the code
// Periodically Get Latency in Miliseconds
clock_drift(function(latency){
var out = PUBNUB.$('latency');
out.innerHTML = "Clock Drift Delta: " + latency + "ms";
// Flash Update
PUBNUB.css( out, { background : latency > 2000 ? '#f32' : '#5b5' } );
setTimeout( function() {
PUBNUB.css( out, { background : '#444' } );
}, 300 );
});

nodejs - every minute, on the minute

How can I wait for a specific system time before firing ?
I want to fire an event when seconds = 0, i.e. every minute
while (1==1) {
var date = new Date();
var sec = date.getSeconds();
if(sec===0) {
Do Something()
}
}
You shouldn't do that, because with this while you will have a blocking operation. Also, there are better things to do in any JavaScript platform, like using setInterval/setTimeout functions.
The node docs for them are here.
A quick example of how to achieve what you want:
setInterval(function() {
var date = new Date();
if ( date.getSeconds() === 0 ) {
DoSomething();
}
}, 1000);
For a more fine grained control over scheduled processes in Node, maybe you should checkout node-cron.

Resources