Skip to content

Commit d6a7a2e

Browse files
committed
Diagnostics: connectivity: add settings for check interval
This adds a setting to adjust how often to do the network connectivity check, in milliseconds, where setting it to zero disables the check. Also update the check itself to use lower-level APIs to avoid the issue with Electron not handling redirects correctly (which seems to actually cause the check to always pass). Signed-off-by: Mark Yen <[email protected]>
1 parent 249ac5a commit d6a7a2e

File tree

6 files changed

+71
-58
lines changed

6 files changed

+71
-58
lines changed

pkg/rancher-desktop/assets/specs/command-api.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -792,6 +792,17 @@ components:
792792
# TODO It is not possible to modify this setting via `rdctl set`.
793793
x-rd-usage: diagnostic ids that have been muted
794794
additionalProperties: true
795+
connectivity:
796+
type: object
797+
properties:
798+
interval:
799+
type: integer
800+
x-rd-usage: >-
801+
Number of milliseconds before polling for network access;
802+
set this to zero to disable background connectivity checking.
803+
timeout:
804+
type: integer
805+
x-rd-usage: Number of milliseconds to wait before timing out
795806

796807
diagnostics:
797808
type: object

pkg/rancher-desktop/config/settings.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,12 @@ export const defaultSettings = {
118118
namespace: 'default',
119119
},
120120
diagnostics: {
121-
showMuted: false,
122-
mutedChecks: {} as Record<string, boolean>,
121+
showMuted: false,
122+
mutedChecks: {} as Record<string, boolean>,
123+
connectivity: {
124+
interval: 5_000,
125+
timeout: 5_000,
126+
},
123127
},
124128
/**
125129
* Experimental settings

pkg/rancher-desktop/main/commandServer/settingsValidator.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,12 @@ export default class SettingsValidator {
158158
namespace: this.checkString,
159159
},
160160
diagnostics: {
161-
mutedChecks: this.checkBooleanMapping,
162-
showMuted: this.checkBoolean,
161+
mutedChecks: this.checkBooleanMapping,
162+
showMuted: this.checkBoolean,
163+
connectivity: {
164+
interval: this.checkNumber(0, 2 ** 31 - 1),
165+
timeout: this.checkNumber(1, 2 ** 31 - 1),
166+
},
163167
},
164168
};
165169
this.canonicalizeSynonyms(newSettings);

pkg/rancher-desktop/main/diagnostics/connectedToInternet.ts

Lines changed: 37 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,62 +2,56 @@ import { net } from 'electron';
22

33
import { DiagnosticsCategory, DiagnosticsChecker } from './types';
44

5+
import mainEvents from '@pkg/main/mainEvents';
56
import Logging from '@pkg/utils/logging';
67

78
const console = Logging.diagnostics;
89

9-
// Returns the timeout, in milliseconds, for the network connectivity check.
10-
function getTimeout(): number {
11-
if (process.env.RD_CONNECTED_TO_INTERNET_TIMEOUT) {
12-
const parsedTimeout = parseInt(process.env.RD_CONNECTED_TO_INTERNET_TIMEOUT);
10+
let pollingInterval: NodeJS.Timeout;
11+
let timeout = 5_000;
1312

14-
if (parsedTimeout > 0) {
15-
return parsedTimeout;
16-
}
17-
}
13+
// Since this is just a status check, it's fine to just reset the timer every
14+
// time _any_ setting has been updated.
15+
mainEvents.on('settings-update', settings => {
16+
clearInterval(pollingInterval);
1817

19-
return 5000;
20-
}
18+
const { timeout: localTimeout, interval } = settings.diagnostics.connectivity;
19+
20+
timeout = localTimeout;
21+
if (interval > 0) {
22+
pollingInterval = setInterval(() => {
23+
mainEvents.invoke('diagnostics-trigger', CheckConnectedToInternet.id);
24+
}, interval);
25+
}
26+
});
2127

2228
/**
2329
* Checks whether we can perform an HTTP request to a host on the internet,
2430
* with a reasonably short timeout.
2531
*/
2632
async function checkNetworkConnectivity(): Promise<boolean> {
27-
const controller = new AbortController();
28-
const timeout = getTimeout();
29-
const timeoutId = setTimeout(() => controller.abort(), timeout);
30-
let connected: boolean;
31-
32-
try {
33+
const request = net.request({
3334
// Using HTTP request that returns a 301 redirect response instead of a 20+ kB web page
34-
const resp = await net.fetch('http://docs.rancherdesktop.io/', { signal: controller.signal, redirect: 'manual' });
35-
const location = resp.headers.get('Location') || '';
36-
37-
// Verify that we get the original redirect and not a captive portal
38-
if (resp.status !== 301 || !location.includes('docs.rancherdesktop.io')) {
39-
throw new Error(`expected status 301 (was ${ resp.status }) and location including docs.rancherdesktop.io (was ${ location })`);
40-
}
41-
connected = true;
42-
} catch (error: any) {
43-
let errorMessage = error;
44-
45-
if (/Redirect was cancelled/.test(error)) {
46-
// Electron does not currently handle manual redirects correctly.
47-
// https://github.com/electron/electron/issues/43715
48-
connected = true;
49-
} else {
50-
if (error.name === 'AbortError') {
51-
errorMessage = `timed out after ${ timeout } ms`;
52-
}
53-
console.log(`Got error while checking connectivity: ${ errorMessage }`);
54-
connected = false;
55-
}
35+
url: 'http://docs.rancherdesktop.io/',
36+
credentials: 'omit',
37+
redirect: 'manual',
38+
cache: 'no-cache',
39+
});
40+
const timeoutId = setTimeout(() => {
41+
console.log(`${ CheckConnectedToInternet.id }: aborting due to timeout after ${ timeout } milliseconds.`);
42+
request.abort();
43+
}, timeout);
44+
try {
45+
return await new Promise<boolean>(resolve => {
46+
request.on('response', () => resolve(true));
47+
request.on('redirect', () => resolve(true));
48+
request.on('error', () => resolve(false));
49+
request.on('abort', () => resolve(false));
50+
request.end();
51+
});
5652
} finally {
5753
clearTimeout(timeoutId);
5854
}
59-
60-
return connected;
6155
}
6256

6357
/**
@@ -71,7 +65,9 @@ const CheckConnectedToInternet: DiagnosticsChecker = {
7165
return Promise.resolve(true);
7266
},
7367
async check() {
74-
if (await checkNetworkConnectivity()) {
68+
const connected = await checkNetworkConnectivity();
69+
mainEvents.emit('diagnostics-event', { id: 'network-connectivity', connected });
70+
if (connected) {
7571
return {
7672
description: 'The application can reach the internet successfully.',
7773
passed: true,

pkg/rancher-desktop/main/mainEvents.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ interface MainEventNames {
174174
type DiagnosticsEventPayload =
175175
{ id: 'integrations-windows', distro?: string, key: string, error?: Error } |
176176
{ id: 'kube-versions-available', available: boolean } |
177+
{ id: 'network-connectivity', connected: boolean } |
177178
{ id: 'path-management', fileName: string; error: Error | undefined };
178179

179180
/**

pkg/rancher-desktop/main/tray.ts

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ export class Tray {
3636
private currentNetworkStatus: networkStatus = networkStatus.CHECKING;
3737
private static instance: Tray;
3838
private networkState: boolean | undefined;
39-
private networkInterval: NodeJS.Timeout;
4039
private runBuildFromConfigTimer: NodeJS.Timeout | null = null;
4140
private kubeConfigWatchers: fs.FSWatcher[] = [];
4241
private fsWatcherInterval: NodeJS.Timeout;
@@ -199,25 +198,24 @@ export class Tray {
199198
mainEvents.on('k8s-check-state', this.k8sStateChangedEvent);
200199
mainEvents.on('settings-update', this.settingsUpdateEvent);
201200

202-
// This triggers the CONNECTED_TO_INTERNET diagnostic at a set interval and
203-
// updates the network status in the tray if there's a change in the network
204-
// state.
205-
this.networkInterval = setInterval(async() => {
206-
let networkDiagnostic = await mainEvents.invoke('diagnostics-trigger', 'CONNECTED_TO_INTERNET');
207-
208-
if (Array.isArray(networkDiagnostic)) {
209-
networkDiagnostic = networkDiagnostic.shift();
201+
// If the network connectivity diagnostic changes results, update it here.
202+
mainEvents.on('diagnostics-event', payload => {
203+
if (payload.id !== 'network-connectivity') {
204+
return;
210205
}
211-
if (this.networkState === networkDiagnostic?.passed) {
206+
207+
const { connected } = payload;
208+
209+
if (this.networkState === connected) {
212210
return; // network state hasn't changed since last check
213211
}
214212

215-
this.networkState = !!networkDiagnostic?.passed;
213+
this.networkState = connected;
216214

217215
this.handleUpdateNetworkStatus(this.networkState).catch((err: any) => {
218216
console.log('Error updating network status: ', err);
219217
});
220-
}, 5000);
218+
});
221219
}
222220

223221
private backendStateEvent = (backendIsLocked: string) => {
@@ -258,7 +256,6 @@ export class Tray {
258256
mainEvents.off('settings-update', this.settingsUpdateEvent);
259257
ipcMainProxy.removeListener('update-network-status', this.updateNetworkStatusEvent);
260258
clearInterval(this.fsWatcherInterval);
261-
clearInterval(this.networkInterval);
262259
if (this.runBuildFromConfigTimer) {
263260
clearTimeout(this.runBuildFromConfigTimer);
264261
this.runBuildFromConfigTimer = null;

0 commit comments

Comments
 (0)