refactor: Extract watchdog module.

We now have 100% code coverage on this somewhat
fiddly code.

We also break activity's dependency on server_events.
This commit is contained in:
Steve Howell
2021-03-22 12:59:50 +00:00
committed by Tim Abbott
parent d644e42dc1
commit dbf19fe8d7
6 changed files with 106 additions and 26 deletions

View File

@@ -86,8 +86,8 @@ reload itself:
garbage-collected by the server, meaning the browser can no longer
get real-time updates altogether. In this case, the browser
auto-reloads immediately in order to reconnect. We have coded an
unsuspend trigger (based on some clever time logic) that ensures we
check immediately when a client unsuspends; grep for `unsuspend` to
unsuspend callback (based on some clever time logic) that ensures we
check immediately when a client unsuspends; grep for `watchdog` to
see the code.
* If a new version of the server has been deployed, we want to reload
the browser so that it will start running the latest code. However,

View File

@@ -51,12 +51,12 @@ mock_esm("../../static/js/resize", {
mock_esm("../../static/js/scroll_util", {
scroll_element_into_container: () => {},
});
mock_esm("../../static/js/server_events", {
check_for_unsuspend() {},
});
mock_esm("../../static/js/stream_popover", {
show_streamlist_sidebar() {},
});
mock_esm("../../static/js/watchdog", {
check_for_unsuspend() {},
});
set_global("document", _document);
const huddle_data = zrequire("huddle_data");

View File

@@ -0,0 +1,63 @@
"use strict";
const {strict: assert} = require("assert");
const MockDate = require("mockdate");
const {set_global, zrequire} = require("../zjsunit/namespace");
const {run_test} = require("../zjsunit/test");
let time = 0;
let checker;
MockDate.set(time);
function advance_secs(secs) {
time += secs * 1000;
MockDate.set(time);
}
set_global("setInterval", (f, interval) => {
checker = f;
assert.equal(interval, 5000);
});
const watchdog = zrequire("watchdog");
run_test("basics", () => {
// Test without callbacks first.
checker();
advance_secs(5);
checker();
let num_times_called_back = 0;
function callback() {
num_times_called_back += 1;
}
watchdog.on_unsuspend(callback);
// Simulate healthy operation.
advance_secs(5);
checker();
assert.equal(num_times_called_back, 0);
// Simulate machine going to sleep.
advance_secs(21);
checker();
assert.equal(num_times_called_back, 1);
// Simulate healthy operations resume, and
// explicitly call check_for_unsuspend.
num_times_called_back = 0;
advance_secs(5);
watchdog.check_for_unsuspend();
assert.equal(num_times_called_back, 0);
// Simulate another suspension.
advance_secs(21);
watchdog.check_for_unsuspend();
assert.equal(num_times_called_back, 1);
});
MockDate.reset();

View File

@@ -12,9 +12,9 @@ import * as people from "./people";
import * as pm_list from "./pm_list";
import * as popovers from "./popovers";
import * as presence from "./presence";
import * as server_events from "./server_events";
import {UserSearch} from "./user_search";
import * as user_status from "./user_status";
import * as watchdog from "./watchdog";
export let user_cursor;
export let user_filter;
@@ -195,7 +195,7 @@ export function send_presence_to_server(want_redraw) {
return;
}
server_events.check_for_unsuspend();
watchdog.check_for_unsuspend();
channel.post({
url: "/json/users/me/presence",

View File

@@ -11,6 +11,7 @@ import * as reload_state from "./reload_state";
import * as sent_messages from "./sent_messages";
import * as server_events_dispatch from "./server_events_dispatch";
import * as ui_report from "./ui_report";
import * as watchdog from "./watchdog";
// Docs: https://zulip.readthedocs.io/en/latest/subsystems/events-system.html
@@ -302,26 +303,8 @@ export function home_view_loaded() {
$(document).trigger("home_view_loaded.zulip");
}
let watchdog_time = Date.now();
export function check_for_unsuspend() {
const new_time = Date.now();
if (new_time - watchdog_time > 20000) {
// 20 seconds.
// Defensively reset watchdog_time here in case there's an
// exception in one of the event handlers
watchdog_time = new_time;
// Our app's JS wasn't running, which probably means the machine was
// asleep.
$(document).trigger("unsuspend");
}
watchdog_time = new_time;
}
setInterval(check_for_unsuspend, 5000);
export function initialize() {
$(document).on("unsuspend", () => {
watchdog.on_unsuspend(() => {
// Immediately poll for new events on unsuspend
blueslip.log("Restarting get_events due to unsuspend");
get_events_failures = 0;

34
static/js/watchdog.js Normal file
View File

@@ -0,0 +1,34 @@
const unsuspend_callbacks = [];
let watchdog_time = Date.now();
/*
Our watchdog code checks every 5 seconds to make sure that we
haven't gone 20 seconds since the last "5-second-ago" check.
This sounds confusing, but it is just is a way to detect that
the machine has gone to sleep.
When we detect the condition we call back to server_events code
to reset ourselves accordingly.
*/
export function check_for_unsuspend() {
const new_time = Date.now();
if (new_time - watchdog_time > 20000) {
// 20 seconds.
// Defensively reset watchdog_time here in case there's an
// exception in one of the event handlers
watchdog_time = new_time;
// Our app's JS wasn't running, which probably means the machine was
// asleep.
for (const callback of unsuspend_callbacks) {
callback();
}
}
watchdog_time = new_time;
}
export function on_unsuspend(f) {
unsuspend_callbacks.push(f);
}
setInterval(check_for_unsuspend, 5000);