-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(bus): implement exponential backoff retry strategy (#14)
closes #13
- Loading branch information
1 parent
5c02ac6
commit 5c75843
Showing
11 changed files
with
1,324 additions
and
1,141 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,6 +36,6 @@ | |
"bus" | ||
], | ||
"peerDependencies": { | ||
"@secbox/core": "^0.2.0" | ||
"@secbox/core": "^0.3.0" | ||
} | ||
} |
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
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,10 @@ | ||
import { ExponentialBackoffRetryStrategy } from './retry-strategies'; | ||
import { container } from 'tsyringe'; | ||
import { RetryStrategy } from '@secbox/core'; | ||
|
||
container.register(RetryStrategy, { | ||
useFactory: () => new ExponentialBackoffRetryStrategy({ maxDepth: 5 }) | ||
}); | ||
|
||
export * from './brokers'; | ||
export * from './retry-strategies'; |
89 changes: 89 additions & 0 deletions
89
packages/bus/src/retry-strategies/ExponentialBackoffRetryStrategy.spec.ts
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import { ExponentialBackoffRetryStrategy } from './ExponentialBackoffRetryStrategy'; | ||
|
||
describe('ExponentialBackoffRetryStrategy', () => { | ||
const findArg = <R>( | ||
args: [unknown, unknown], | ||
expected: 'function' | 'number' | ||
): R => (typeof args[0] === expected ? args[0] : args[1]) as R; | ||
|
||
beforeEach(() => { | ||
jest.useFakeTimers(); | ||
|
||
const mockedImplementation = jest | ||
.spyOn(global, 'setTimeout') | ||
.getMockImplementation(); | ||
|
||
jest | ||
.spyOn(global, 'setTimeout') | ||
.mockImplementation((...args: [unknown, unknown]) => { | ||
// ADHOC: depending on implementation (promisify vs raw), the method signature will be different | ||
const callback = findArg<(..._: unknown[]) => void>(args, 'function'); | ||
const ms = findArg<number>(args, 'number'); | ||
const timer = mockedImplementation?.(callback, ms); | ||
|
||
jest.runAllTimers(); | ||
|
||
return timer as NodeJS.Timeout; | ||
}); | ||
}); | ||
|
||
afterEach(() => { | ||
jest.useRealTimers(); | ||
jest.resetAllMocks(); | ||
}); | ||
|
||
it('should not retry if function does not throw error', async () => { | ||
const retryStrategy = new ExponentialBackoffRetryStrategy({ maxDepth: 1 }); | ||
const input = jest.fn().mockResolvedValue(undefined); | ||
|
||
await retryStrategy.acquire(input); | ||
|
||
expect(input).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it('should return a result execution immediately', async () => { | ||
const retryStrategy = new ExponentialBackoffRetryStrategy({ maxDepth: 1 }); | ||
const input = jest.fn().mockReturnValue(undefined); | ||
|
||
await retryStrategy.acquire(input); | ||
|
||
expect(input).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it('should prevent retries if error does not have a correct code', async () => { | ||
const retryStrategy = new ExponentialBackoffRetryStrategy({ maxDepth: 1 }); | ||
const input = jest.fn().mockRejectedValue(new Error('Unhandled error')); | ||
|
||
const result = retryStrategy.acquire(input); | ||
|
||
await expect(result).rejects.toThrow('Unhandled error'); | ||
expect(input).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it('should retry two times and throw an error', async () => { | ||
const retryStrategy = new ExponentialBackoffRetryStrategy({ maxDepth: 2 }); | ||
const error = new Error('Unhandled error'); | ||
(error as any).code = 'ECONNRESET'; | ||
const input = jest.fn().mockRejectedValue(error); | ||
|
||
const result = retryStrategy.acquire(input); | ||
|
||
await expect(result).rejects.toThrow(error); | ||
expect(input).toHaveBeenCalledTimes(3); | ||
}); | ||
|
||
it('should return a result execution after a two retries', async () => { | ||
const retryStrategy = new ExponentialBackoffRetryStrategy({ maxDepth: 2 }); | ||
const error = new Error('Unhandled error'); | ||
(error as any).code = 'ECONNRESET'; | ||
const input = jest | ||
.fn() | ||
.mockRejectedValueOnce(error) | ||
.mockRejectedValueOnce(error) | ||
.mockResolvedValue(undefined); | ||
|
||
await retryStrategy.acquire(input); | ||
|
||
expect(input).toHaveBeenCalledTimes(3); | ||
}); | ||
}); |
Oops, something went wrong.