Created
September 20, 2021 17:10
-
-
Save BharatKalluri/a7946bd24ea617c50fcee05e022672ec to your computer and use it in GitHub Desktop.
Fuse: A simple circuit breaker in deno
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
interface IOptions { | |
bucketSize: number, | |
failurePercent: number | |
canaryRequestInMilliSeconds: number | |
minRecordings: number | |
} | |
interface IEvent { | |
success: boolean | |
createdAt: number | |
} | |
enum CircuitStatus { | |
OPEN = 'OPEN', | |
CLOSED = 'CLOSED', | |
} | |
function getLatestFailure(bucketContents: Array<IEvent>): IEvent | undefined { | |
const latestFailure = bucketContents.filter(el => el.success === false).slice(-1) | |
return latestFailure.length > 0 ? latestFailure[0] : undefined | |
} | |
function addEventToBucket(bucketContents: Array<IEvent>, event: IEvent, bucketSize: number): Array<IEvent> | IEvent[] { | |
const bucketLen = bucketContents.push(event) | |
if (bucketLen > bucketSize) { | |
return bucketContents.slice(-bucketSize) | |
} | |
return bucketContents | |
} | |
function getFailureRate(bucketContents: Array<IEvent>) { | |
const failureCount = bucketContents.filter(el => el.success === false).length | |
const totalCount = bucketContents.length | |
return (failureCount / totalCount) * 100 | |
} | |
class CircuitBreaker { | |
bucket: Array<IEvent> = [] | |
options: IOptions = { | |
bucketSize: 100, | |
failurePercent: 50, | |
canaryRequestInMilliSeconds: 10000, | |
minRecordings: 10 | |
} | |
circuitStatus = CircuitStatus.OPEN | |
fnToRun: Function | |
constructor(fnToRun: Function, options: IOptions) { | |
this.fnToRun = fnToRun | |
this.options = options | |
} | |
async recordEvent(isSuccess: boolean): Promise<void> { | |
this.bucket = addEventToBucket(this.bucket, { | |
createdAt: Date.now(), | |
success: isSuccess | |
}, this.options.bucketSize) | |
} | |
async run(fnArgs: any) { | |
const latestFailure = getLatestFailure(this.bucket) | |
const isRunCanary = this.circuitStatus === CircuitStatus.CLOSED && | |
latestFailure && | |
(Date.now() - latestFailure.createdAt) > this.options.canaryRequestInMilliSeconds | |
if (isRunCanary || this.circuitStatus === CircuitStatus.OPEN) { | |
try { | |
const resultOfExecution = await this.fnToRun(fnArgs) | |
await this.recordEvent(true) | |
if (isRunCanary) { | |
console.log('Canary worked! Opening circuit') | |
this.circuitStatus = CircuitStatus.OPEN | |
} | |
return resultOfExecution; | |
} catch (e: unknown) { | |
await this.recordEvent(false) | |
if ( | |
this.bucket.length > this.options.minRecordings && | |
getFailureRate(this.bucket) > this.options.failurePercent | |
) { | |
this.circuitStatus = CircuitStatus.CLOSED | |
} | |
return | |
} | |
} else if (this.circuitStatus === CircuitStatus.CLOSED) { | |
console.log('Circuit closed!') | |
return | |
} else { | |
throw Error('state not expected!') | |
} | |
} | |
} | |
const randomlyFailingFn: (probabilityOfFailure: number) => Promise<void> = async (probabilityOfFailure: number) => { | |
const randomNumber: number = Math.random() | |
if (probabilityOfFailure > randomNumber) { | |
console.log('Function will fail now!') | |
throw Error('Randomly failed') | |
} | |
} | |
const circuitBreaker = new CircuitBreaker( | |
randomlyFailingFn, | |
{ | |
bucketSize: 100, | |
failurePercent: 5, | |
canaryRequestInMilliSeconds: 30000, | |
minRecordings: 50, | |
} | |
) | |
function sleep(ms: number) { | |
return new Promise(resolve => setTimeout(resolve, ms)); | |
} | |
for (let i = 0; i < 1000; i++) { | |
await sleep(1000) | |
await circuitBreaker.run(0.2) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment