mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-09-08 13:44:53 +08:00
[client-app] Detect when the worker window is unavailable
Summary: Periodically ping client-sync's /health endpoint and store the latest sync activity. If we get an ECONNREFUSED error, the worker window is unavailable. Report the last known activity and restart the worker window. Part of T7681. Test Plan: manual, specs Reviewers: evan, mark, juan Reviewed By: juan Differential Revision: https://phab.nylas.com/D4263
This commit is contained in:
parent
619c69a522
commit
73f0f3eeda
4 changed files with 135 additions and 0 deletions
|
@ -0,0 +1,9 @@
|
|||
import SyncHealthChecker from './sync-health-checker'
|
||||
|
||||
export function activate() {
|
||||
SyncHealthChecker.start()
|
||||
}
|
||||
|
||||
export function deactivate() {
|
||||
SyncHealthChecker.stop()
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
import {ipcRenderer} from 'electron'
|
||||
import {AccountStore, Actions, NylasAPI, NylasAPIRequest} from 'nylas-exports'
|
||||
|
||||
const CHECK_HEALTH_INTERVAL = 5 * 60 * 1000;
|
||||
|
||||
class SyncHealthChecker {
|
||||
constructor() {
|
||||
this._lastSyncActivity = null
|
||||
this._interval = null
|
||||
}
|
||||
|
||||
start() {
|
||||
if (this._interval) {
|
||||
console.warn('SyncHealthChecker has already been started')
|
||||
} else {
|
||||
this._interval = setInterval(this._checkSyncHealth, CHECK_HEALTH_INTERVAL)
|
||||
}
|
||||
}
|
||||
|
||||
stop() {
|
||||
clearInterval(this._interval)
|
||||
this._interval = null
|
||||
}
|
||||
|
||||
// This is a separate function so the request can be manipulated in the specs
|
||||
_buildRequest = () => {
|
||||
return new NylasAPIRequest({
|
||||
api: NylasAPI,
|
||||
options: {
|
||||
accountId: AccountStore.accounts()[0].id,
|
||||
path: `/health`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
_checkSyncHealth = async () => {
|
||||
try {
|
||||
const request = this._buildRequest()
|
||||
const response = await request.run()
|
||||
this._lastSyncActivity = response
|
||||
} catch (err) {
|
||||
if (/ECONNREFUSED/i.test(err.toString())) {
|
||||
this._onWorkerWindowUnavailable()
|
||||
} else {
|
||||
err.message = `Error checking sync health: ${err.message}`
|
||||
NylasEnv.reportError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_onWorkerWindowUnavailable() {
|
||||
let extraData = {};
|
||||
|
||||
// Extract data that we want to report. We'll report the entire
|
||||
// _lastSyncActivity object, but it'll probably be useful if we can segment
|
||||
// by the data in the oldest or newest entry, so we report those as
|
||||
// individual values too.
|
||||
const lastActivityEntries = Object.entries(this._lastSyncActivity || {})
|
||||
if (lastActivityEntries.length > 0) {
|
||||
const times = lastActivityEntries.map((entry) => entry[1].time)
|
||||
const now = Date.now()
|
||||
|
||||
const maxTime = Math.max(...times)
|
||||
const mostRecentEntry = lastActivityEntries.find((entry) => entry[1].time === maxTime)
|
||||
const [mostRecentActivityAccountId, {
|
||||
activity: mostRecentActivity,
|
||||
time: mostRecentActivityTime,
|
||||
}] = mostRecentEntry;
|
||||
const mostRecentDuration = now - mostRecentActivityTime
|
||||
|
||||
const minTime = Math.min(...times)
|
||||
const leastRecentEntry = lastActivityEntries.find((entry) => entry[1].time === minTime)
|
||||
const [leastRecentActivityAccountId, {
|
||||
activity: leastRecentActivity,
|
||||
time: leastRecentActivityTime,
|
||||
}] = leastRecentEntry;
|
||||
const leastRecentDuration = now - leastRecentActivityTime
|
||||
|
||||
extraData = {
|
||||
mostRecentActivity,
|
||||
mostRecentActivityTime,
|
||||
mostRecentActivityAccountId,
|
||||
mostRecentDuration,
|
||||
leastRecentActivity,
|
||||
leastRecentActivityTime,
|
||||
leastRecentActivityAccountId,
|
||||
leastRecentDuration,
|
||||
}
|
||||
}
|
||||
|
||||
NylasEnv.reportError(new Error('Worker window was unavailable'), {
|
||||
// This information isn't as useful in Sentry, but include it here until
|
||||
// the data is actually sent to Mixpanel. (See the TODO below)
|
||||
lastActivityPerAccount: this._lastSyncActivity,
|
||||
...extraData,
|
||||
})
|
||||
|
||||
// TODO: This doesn't make it to Mixpanel because our analytics process
|
||||
// lives in the worker window. We should move analytics to the main process.
|
||||
// https://phab.nylas.com/T8029
|
||||
Actions.recordUserEvent('Worker Window Unavailable', {
|
||||
lastActivityPerAccount: this._lastSyncActivity,
|
||||
...extraData,
|
||||
})
|
||||
|
||||
console.log(`Detected worker window was unavailable. Restarting it.`, this._lastSyncActivity)
|
||||
ipcRenderer.send('ensure-worker-window')
|
||||
}
|
||||
}
|
||||
|
||||
export default new SyncHealthChecker()
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"name": "sync-health-checker",
|
||||
"version": "0.1.0",
|
||||
"main": "./lib/main",
|
||||
"description": "Periodically ping the sync process to ensure it's running",
|
||||
"license": "GPL-3.0",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"nylas": "*"
|
||||
}
|
||||
}
|
|
@ -465,6 +465,10 @@ export default class Application extends EventEmitter {
|
|||
}
|
||||
});
|
||||
|
||||
ipcMain.on('ensure-worker-window', () => {
|
||||
this.windowManager.ensureWindow(WindowManager.WORK_WINDOW)
|
||||
})
|
||||
|
||||
ipcMain.on('inline-style-parse', (event, {html, key}) => {
|
||||
const juice = require('juice');
|
||||
let out = null;
|
||||
|
|
Loading…
Add table
Reference in a new issue