| | import { Time } from '@/constants'; |
| | import { TaskRunnerRestartLoopError } from '@/task-runners/errors/task-runner-restart-loop-error'; |
| | import type { TaskRunnerProcess } from '@/task-runners/task-runner-process'; |
| | import { TypedEmitter } from '@/typed-emitter'; |
| |
|
| | const MAX_RESTARTS = 5; |
| | const RESTARTS_WINDOW = 2 * Time.seconds.toMilliseconds; |
| |
|
| | type TaskRunnerProcessRestartLoopDetectorEventMap = { |
| | 'restart-loop-detected': TaskRunnerRestartLoopError; |
| | }; |
| |
|
| | |
| | |
| | |
| | export class TaskRunnerProcessRestartLoopDetector extends TypedEmitter<TaskRunnerProcessRestartLoopDetectorEventMap> { |
| | |
| | |
| | |
| | |
| | private readonly maxCount = MAX_RESTARTS; |
| |
|
| | |
| | |
| | |
| | |
| | private readonly restartsWindow = RESTARTS_WINDOW; |
| |
|
| | private numRestarts = 0; |
| |
|
| | |
| | private firstRestartedAt = Date.now(); |
| |
|
| | constructor(private readonly taskRunnerProcess: TaskRunnerProcess) { |
| | super(); |
| |
|
| | this.taskRunnerProcess.on('exit', () => { |
| | this.increment(); |
| |
|
| | if (this.isMaxCountExceeded()) { |
| | this.emit( |
| | 'restart-loop-detected', |
| | new TaskRunnerRestartLoopError(this.numRestarts, this.msSinceFirstIncrement()), |
| | ); |
| | } |
| | }); |
| | } |
| |
|
| | |
| | |
| | |
| | private increment() { |
| | const now = Date.now(); |
| | if (now > this.firstRestartedAt + this.restartsWindow) { |
| | this.reset(); |
| | } |
| |
|
| | this.numRestarts++; |
| | } |
| |
|
| | private reset() { |
| | this.numRestarts = 0; |
| | this.firstRestartedAt = Date.now(); |
| | } |
| |
|
| | private isMaxCountExceeded() { |
| | return this.numRestarts >= this.maxCount; |
| | } |
| |
|
| | private msSinceFirstIncrement() { |
| | return Date.now() - this.firstRestartedAt; |
| | } |
| | } |
| |
|