Fix potential race conditions for starting/stopping server add-ons

Reviewed By: mweststrate

Differential Revision: D34301593

fbshipit-source-id: 2950de8a8567318cd3e87eff176657df5ba8fd1b
This commit is contained in:
Andrey Goncharov
2022-02-28 03:50:34 -08:00
committed by Facebook GitHub Bot
parent bdbf79e3e1
commit 81d0057a8d
6 changed files with 317 additions and 123 deletions

View File

@@ -7,9 +7,9 @@
* @format
*/
import EventEmitter from 'events';
import {sleep} from 'flipper-common';
import {assertNotNull} from '../comms/Utilities';
import {StateMachine} from './StateMachine';
export const RESTART_CNT = 3;
const RESTART_SLEEP = 100;
@@ -21,84 +21,13 @@ export type DeviceLogListenerState =
| 'inactive'
| 'fatal'
| 'zombie';
class State {
private _currentState: DeviceLogListenerState = 'inactive';
private _error?: Error;
private valueEmitter = new EventEmitter();
get error() {
return this._error;
}
get currentState() {
return this._currentState;
}
set<T extends DeviceLogListenerState>(
...[newState, error]: T extends 'fatal' | 'zombie' ? [T, Error] : [T]
) {
this._currentState = newState;
this._error = error;
this.valueEmitter.emit(newState);
}
once(
state: DeviceLogListenerState | DeviceLogListenerState[],
cb: () => void,
): () => void {
return this.subscribe(state, cb, {once: true});
}
on(
state: DeviceLogListenerState | DeviceLogListenerState[],
cb: () => void,
): () => void {
return this.subscribe(state, cb);
}
is(targetState: DeviceLogListenerState | DeviceLogListenerState[]) {
if (!Array.isArray(targetState)) {
targetState = [targetState];
}
return targetState.includes(this._currentState);
}
private subscribe(
state: DeviceLogListenerState | DeviceLogListenerState[],
cb: () => void,
{once}: {once?: boolean} = {},
): () => void {
const statesNormalized = Array.isArray(state) ? state : [state];
if (statesNormalized.includes(this._currentState)) {
cb();
return () => {};
}
let executed = false;
const wrappedCb = () => {
if (!executed) {
executed = true;
cb();
}
};
const fn = once ? 'once' : 'on';
statesNormalized.forEach((item) => {
this.valueEmitter[fn](item, wrappedCb);
});
return () => {
statesNormalized.forEach((item) => {
this.valueEmitter.off(item, wrappedCb);
});
};
}
}
export abstract class DeviceListener {
private name: string = this.constructor.name;
protected _state = new State();
protected _state = new StateMachine<
DeviceLogListenerState,
'fatal' | 'zombie'
>('inactive');
private stopLogListener?: () => Promise<void> | void;

View File

@@ -0,0 +1,86 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import {EventEmitter} from 'events';
export class StateMachine<TState extends string, TError extends TState> {
private _error?: Error;
private valueEmitter = new EventEmitter();
constructor(private _currentState: TState) {}
get error() {
return this._error;
}
get currentState() {
return this._currentState;
}
set<T extends TState>(
...[newState, error]: T extends TError ? [T, Error] : [T]
) {
this._currentState = newState as TState;
this._error = error;
this.valueEmitter.emit(newState as TState);
}
wait<T extends TState | TState[]>(state: T): Promise<void> {
return new Promise((resolve) => {
this.once(state, resolve);
});
}
once(state: TState | TState[], cb: () => void): () => void {
return this.subscribe(state, cb, {once: true});
}
on(state: TState | TState[], cb: () => void): () => void {
return this.subscribe(state, cb);
}
is(targetState: TState | TState[]) {
if (!Array.isArray(targetState)) {
targetState = [targetState];
}
return targetState.includes(this._currentState);
}
private subscribe(
state: TState | TState[],
cb: () => void,
{once}: {once?: boolean} = {},
): () => void {
const statesNormalized = Array.isArray(state) ? state : [state];
if (statesNormalized.includes(this._currentState)) {
cb();
return () => {};
}
let executed = false;
const wrappedCb = () => {
if (!executed) {
executed = true;
cb();
}
};
const fn = once ? 'once' : 'on';
statesNormalized.forEach((item) => {
this.valueEmitter[fn](item, wrappedCb);
});
return () => {
statesNormalized.forEach((item) => {
this.valueEmitter.off(item, wrappedCb);
});
};
}
}