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:
committed by
Facebook GitHub Bot
parent
bdbf79e3e1
commit
81d0057a8d
@@ -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;
|
||||
|
||||
|
||||
86
desktop/flipper-server-core/src/utils/StateMachine.tsx
Normal file
86
desktop/flipper-server-core/src/utils/StateMachine.tsx
Normal 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);
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user