Skip to content

Instantly share code, notes, and snippets.

@BharatKalluri
Created September 20, 2021 17:10
Show Gist options
  • Save BharatKalluri/a7946bd24ea617c50fcee05e022672ec to your computer and use it in GitHub Desktop.
Save BharatKalluri/a7946bd24ea617c50fcee05e022672ec to your computer and use it in GitHub Desktop.
Fuse: A simple circuit breaker in deno
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