deno.land / x / cockatiel@v3.1.2 / src / CircuitBreakerPolicy.ts

CircuitBreakerPolicy.ts
نووسراو ببینە
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
import { IBreaker } from './breaker/Breaker';import { neverAbortedSignal } from './common/abort';import { EventEmitter } from './common/Event';import { ExecuteWrapper, returnOrThrow } from './common/Executor';import { BrokenCircuitError, TaskCancelledError } from './errors/Errors';import { IsolatedCircuitError } from './errors/IsolatedCircuitError';import { FailureReason, IDefaultPolicyContext, IPolicy } from './Policy';
export enum CircuitState { /** * Normal operation. Execution of actions allowed. */ Closed,
/** * The automated controller has opened the circuit. Execution of actions blocked. */ Open,
/** * Recovering from open state, after the automated break duration has * expired. Execution of actions permitted. Success of subsequent action/s * controls onward transition to Open or Closed state. */ HalfOpen,
/** * Circuit held manually in an open state. Execution of actions blocked. */ Isolated,}
export interface ICircuitBreakerOptions { breaker: IBreaker; halfOpenAfter: number;}
type InnerState = | { value: CircuitState.Closed } | { value: CircuitState.Isolated; counters: number } | { value: CircuitState.Open; openedAt: number } | { value: CircuitState.HalfOpen; test: Promise<any> };
export class CircuitBreakerPolicy implements IPolicy { declare readonly _altReturn: never;
private readonly breakEmitter = new EventEmitter<FailureReason<unknown> | { isolated: true }>(); private readonly resetEmitter = new EventEmitter<void>(); private readonly halfOpenEmitter = new EventEmitter<void>(); private readonly stateChangeEmitter = new EventEmitter<CircuitState>(); private innerLastFailure?: FailureReason<unknown>; private innerState: InnerState = { value: CircuitState.Closed };
/** * Event emitted when the circuit breaker opens. */ public readonly onBreak = this.breakEmitter.addListener;
/** * Event emitted when the circuit breaker resets. */ public readonly onReset = this.resetEmitter.addListener;
/** * Event emitted when the circuit breaker is half open (running a test call). * Either `onBreak` on `onReset` will subsequently fire. */ public readonly onHalfOpen = this.halfOpenEmitter.addListener;
/** * Fired whenever the circuit breaker state changes. */ public readonly onStateChange = this.stateChangeEmitter.addListener;
/** * @inheritdoc */ public readonly onSuccess = this.executor.onSuccess;
/** * @inheritdoc */ public readonly onFailure = this.executor.onFailure;
/** * Gets the current circuit breaker state. */ public get state(): CircuitState { return this.innerState.value; }
/** * Gets the last reason the circuit breaker failed. */ public get lastFailure() { return this.innerLastFailure; }
constructor( private readonly options: ICircuitBreakerOptions, private readonly executor: ExecuteWrapper, ) {}
/** * Manually holds open the circuit breaker. * @returns A handle that keeps the breaker open until `.dispose()` is called. */ public isolate() { if (this.innerState.value !== CircuitState.Isolated) { this.innerState = { value: CircuitState.Isolated, counters: 0 }; this.breakEmitter.emit({ isolated: true }); this.stateChangeEmitter.emit(CircuitState.Isolated); }
this.innerState.counters++;
let disposed = false; return { dispose: () => { if (disposed) { return; }
disposed = true; if (this.innerState.value === CircuitState.Isolated && !--this.innerState.counters) { this.innerState = { value: CircuitState.Closed }; this.resetEmitter.emit(); this.stateChangeEmitter.emit(CircuitState.Closed); } }, }; }
/** * Executes the given function. * @param fn Function to run * @throws a {@link BrokenCircuitError} if the circuit is open * @throws a {@link IsolatedCircuitError} if the circuit is held * open via {@link CircuitBreakerPolicy.isolate} * @returns a Promise that resolves or rejects with the function results. */ public async execute<T>( fn: (context: IDefaultPolicyContext) => PromiseLike<T> | T, signal = neverAbortedSignal, ): Promise<T> { const state = this.innerState; switch (state.value) { case CircuitState.Closed: const result = await this.executor.invoke(fn, { signal }); if ('success' in result) { this.options.breaker.success(state.value); } else { this.innerLastFailure = result; if (this.options.breaker.failure(state.value)) { this.open(result); } }
return returnOrThrow(result);
case CircuitState.HalfOpen: await state.test.catch(() => undefined); if (this.state === CircuitState.Closed && signal.aborted) { throw new TaskCancelledError(); }
return this.execute(fn);
case CircuitState.Open: if (Date.now() - state.openedAt < this.options.halfOpenAfter) { throw new BrokenCircuitError(); } const test = this.halfOpen(fn, signal); this.innerState = { value: CircuitState.HalfOpen, test }; this.stateChangeEmitter.emit(CircuitState.HalfOpen); return test;
case CircuitState.Isolated: throw new IsolatedCircuitError();
default: throw new Error(`Unexpected circuit state ${state}`); } }
private async halfOpen<T>( fn: (context: IDefaultPolicyContext) => PromiseLike<T> | T, signal: AbortSignal, ): Promise<T> { this.halfOpenEmitter.emit();
try { const result = await this.executor.invoke(fn, { signal }); if ('success' in result) { this.options.breaker.success(CircuitState.HalfOpen); this.close(); } else { this.innerLastFailure = result; this.options.breaker.failure(CircuitState.HalfOpen); this.open(result); }
return returnOrThrow(result); } catch (err) { // It's an error, but not one the circuit is meant to retry, so // for our purposes it's a success. Task failed successfully! this.close(); throw err; } }
private open(reason: FailureReason<unknown>) { if (this.state === CircuitState.Isolated || this.state === CircuitState.Open) { return; }
this.innerState = { value: CircuitState.Open, openedAt: Date.now() }; this.breakEmitter.emit(reason); this.stateChangeEmitter.emit(CircuitState.Open); }
private close() { if (this.state === CircuitState.HalfOpen) { this.innerState = { value: CircuitState.Closed }; this.resetEmitter.emit(); this.stateChangeEmitter.emit(CircuitState.Closed); } }}
cockatiel

Version Info

Tagged at
5 months ago