Skip to content

Commit

Permalink
GH-1155: Exposed API, made the status service more extensible.
Browse files Browse the repository at this point in the history
From now on, we do not schedule a new `alive` request until the previous on has been processed. Doubled the execution timeout if the previous request has failed.

Signed-off-by: Akos Kitta <kittaakos@gmail.com>
  • Loading branch information
kittaakos committed Feb 5, 2018
1 parent 0206077 commit 59b22c0
Showing 1 changed file with 53 additions and 22 deletions.
75 changes: 53 additions & 22 deletions packages/core/src/browser/frontend-connection-status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export class ConnectionStatusOptions {
static DEFAULT: ConnectionStatusOptions = {
requestTimeout: 1000,
retry: 5,
retryInterval: 2000,
retryInterval: 1000,
};

/**
Expand All @@ -76,25 +76,68 @@ export class ConnectionStatusOptions {
@injectable()
export class FrontendConnectionStatusService implements ConnectionStatusService, FrontendApplicationContribution {

static readonly MAX_RETRY_INTERVAL = 30000;

protected readonly statusChangeEmitter: Emitter<ConnectionStatusChangeEvent>;
private readonly endpointUrl: string;

private connectionState: ConnectionStateMachine;
private timer: number | undefined;
private retryInterval: number;

constructor(
@inject(ConnectionStatusOptions) @optional() protected readonly options: ConnectionStatusOptions = ConnectionStatusOptions.DEFAULT,
@inject(ILogger) protected readonly logger: ILogger
) {
this.statusChangeEmitter = new Emitter<ConnectionStatusChangeEvent>();
this.endpointUrl = new Endpoint().getRestUrl().toString();
this.connectionState = new ConnectionStateMachine({ threshold: this.options.retry });
this.retryInterval = this.options.retryInterval;
}

onStart() {
this.timer = window.setInterval(() => {
this.schedule(this.checkAlive.bind(this));
this.fireStatusChange(this.connectionState);
}

onStop() {
if (this.timer !== undefined) {
window.clearInterval(this.timer);
this.timer = undefined;
}
}

get onStatusChange() {
return this.statusChangeEmitter.event;
}

protected schedule(checkAlive: () => Promise<boolean>) {
const tick = async () => {
this.logger.debug(`Checking backend connection status. Scheduled an alive request with ${this.retryInterval} ms timeout.`);
const success = await checkAlive();
this.logger.debug(success ? `Connected to the backend.` : `Cannot reach the backend.`);
const event = this.updateStatus(success);
this.fireStatusChange(event);
// In case of a timeout, we increase the execution (and not the connection) timeout.
this.retryInterval = success ? this.options.retryInterval : Math.min(this.retryInterval * 2, FrontendConnectionStatusService.MAX_RETRY_INTERVAL);
this.timer = window.setTimeout(tick, this.retryInterval);
};
this.timer = window.setTimeout(tick, this.retryInterval);
}

protected updateStatus(success: boolean): ConnectionStatusChangeEvent {
this.connectionState = this.connectionState.next(success);
return this.connectionState;
}

protected fireStatusChange(event: ConnectionStatusChangeEvent) {
this.statusChangeEmitter.fire(event);
}

protected checkAlive(): Promise<boolean> {
return new Promise<boolean>(resolve => {
const handle = (success: boolean) => {
this.connectionState = this.connectionState.next(success);
this.statusChangeEmitter.fire(this.connectionState);
return resolve(success);
};
const xhr = new XMLHttpRequest();
xhr.timeout = this.options.requestTimeout;
Expand All @@ -112,30 +155,18 @@ export class FrontendConnectionStatusService implements ConnectionStatusService,
} catch {
handle(false);
}
}, this.options.retryInterval);
this.statusChangeEmitter.fire(this.connectionState);
}

onStop() {
if (this.timer !== undefined) {
window.clearInterval(this.timer);
this.timer = undefined;
}
}

get onStatusChange() {
return this.statusChangeEmitter.event;
});
}

}

/**
* Just in case we need to support a bit more sophisticated state transitions than having connection or not.
* For instance, `pending`, `reconnecting`.
* Just in case we need to support a bit more sophisticated state transitions than having `online` and `offline`.
* For instance, `pending`, `reconnecting`, etc...
*/
class ConnectionStateMachine implements ConnectionStatusChangeEvent {
export class ConnectionStateMachine implements ConnectionStatusChangeEvent {

static readonly MAX_HISTORY = 100;
private static readonly MAX_HISTORY = 100;

public readonly health: number;

Expand All @@ -162,7 +193,7 @@ class ConnectionStateMachine implements ConnectionStatusChangeEvent {
return new ConnectionStateMachine(this.props, online ? ConnectionState.ONLINE : ConnectionState.OFFLINE, newHistory);
}

private updateHistory(success: boolean) {
protected updateHistory(success: boolean) {
const updated = [...this.history, success];
if (updated.length > ConnectionStateMachine.MAX_HISTORY) {
updated.shift();
Expand Down

0 comments on commit 59b22c0

Please sign in to comment.