Use jest.useFakeTimers

This commit is contained in:
Adria Navarro 2023-11-30 18:34:56 +01:00
parent bd89633e61
commit fb72b77ac1
3 changed files with 24 additions and 29 deletions

View File

@ -101,20 +101,20 @@ function getLockName(opts: LockOptions) {
return name return name
} }
export const autoExtendPollingMs = Duration.fromSeconds(10).toMs()
export async function doWithLock<T>( export async function doWithLock<T>(
opts: LockOptions, opts: LockOptions,
task: () => Promise<T> task: () => Promise<T>
): Promise<RedlockExecution<T>> { ): Promise<RedlockExecution<T>> {
const redlock = await getClient(opts.type, opts.customOptions) const redlock = await getClient(opts.type, opts.customOptions)
let lock: Redlock.Lock | undefined let lock: Redlock.Lock | undefined
let timeout let timeout: NodeJS.Timeout | undefined
try { try {
const name = getLockName(opts) const name = getLockName(opts)
const ttl = const ttl =
opts.type === LockType.AUTO_EXTEND opts.type === LockType.AUTO_EXTEND ? autoExtendPollingMs : opts.ttl
? Duration.fromSeconds(10).toMs()
: opts.ttl
// create the lock // create the lock
lock = await redlock.lock(name, ttl) lock = await redlock.lock(name, ttl)
@ -123,21 +123,9 @@ export async function doWithLock<T>(
// We keep extending the lock while the task is running // We keep extending the lock while the task is running
const extendInIntervals = (): void => { const extendInIntervals = (): void => {
timeout = setTimeout(async () => { timeout = setTimeout(async () => {
let isExpired = false lock = await lock!.extend(ttl, () => opts.onExtend && opts.onExtend())
try {
lock = await lock!.extend(ttl)
} catch (err: any) {
isExpired = err.message.includes("Cannot extend lock on resource")
if (isExpired) {
console.error("The lock expired", { name })
} else {
throw err
}
}
if (!isExpired) {
extendInIntervals() extendInIntervals()
}
}, ttl / 2) }, ttl / 2)
} }

View File

@ -1,14 +1,15 @@
import { LockName, LockType, LockOptions } from "@budibase/types" import { LockName, LockType, LockOptions } from "@budibase/types"
import tk from "timekeeper" import { autoExtendPollingMs, doWithLock } from "../redlockImpl"
import { doWithLock } from "../redlockImpl"
import { DBTestConfiguration, generator } from "../../../tests" import { DBTestConfiguration, generator } from "../../../tests"
tk.reset()
describe("redlockImpl", () => { describe("redlockImpl", () => {
beforeEach(() => {
jest.useFakeTimers()
})
describe("doWithLock", () => { describe("doWithLock", () => {
const config = new DBTestConfiguration() const config = new DBTestConfiguration()
const lockTtl = 50 const lockTtl = autoExtendPollingMs
function runLockWithExecutionTime({ function runLockWithExecutionTime({
opts, opts,
@ -21,7 +22,10 @@ describe("redlockImpl", () => {
}) { }) {
return config.doInTenant(() => return config.doInTenant(() =>
doWithLock(opts, async () => { doWithLock(opts, async () => {
await new Promise<void>(r => setTimeout(() => r(), executionTimeMs)) const interval = lockTtl / 10
for (let i = executionTimeMs; i > 0; i -= interval) {
await jest.advanceTimersByTimeAsync(interval)
}
return task() return task()
}) })
) )
@ -33,7 +37,7 @@ describe("redlockImpl", () => {
const expectedResult = generator.guid() const expectedResult = generator.guid()
const mockTask = jest.fn().mockResolvedValue(expectedResult) const mockTask = jest.fn().mockResolvedValue(expectedResult)
const opts = { const opts: LockOptions = {
name: LockName.PERSIST_WRITETHROUGH, name: LockName.PERSIST_WRITETHROUGH,
type: lockType, type: lockType,
ttl: lockTtl, ttl: lockTtl,
@ -54,22 +58,24 @@ describe("redlockImpl", () => {
it("should extend when type is autoextend", async () => { it("should extend when type is autoextend", async () => {
const expectedResult = generator.guid() const expectedResult = generator.guid()
const mockTask = jest.fn().mockResolvedValue(expectedResult) const mockTask = jest.fn().mockResolvedValue(expectedResult)
const mockOnExtend = jest.fn()
const opts = { const opts: LockOptions = {
name: LockName.PERSIST_WRITETHROUGH, name: LockName.PERSIST_WRITETHROUGH,
type: LockType.AUTO_EXTEND, type: LockType.AUTO_EXTEND,
ttl: lockTtl, onExtend: mockOnExtend,
} }
const result = await runLockWithExecutionTime({ const result = await runLockWithExecutionTime({
opts, opts,
task: mockTask, task: mockTask,
executionTimeMs: lockTtl * 3, executionTimeMs: lockTtl * 2.5,
}) })
expect(result.executed).toBe(true) expect(result.executed).toBe(true)
expect(result.executed && result.result).toBe(expectedResult) expect(result.executed && result.result).toBe(expectedResult)
expect(mockTask).toHaveBeenCalledTimes(1) expect(mockTask).toHaveBeenCalledTimes(1)
expect(mockOnExtend).toHaveBeenCalledTimes(5)
}) })
it.each(Object.values(LockType).filter(t => t !== LockType.AUTO_EXTEND))( it.each(Object.values(LockType).filter(t => t !== LockType.AUTO_EXTEND))(
@ -77,7 +83,7 @@ describe("redlockImpl", () => {
async (lockType: LockType) => { async (lockType: LockType) => {
const mockTask = jest.fn().mockResolvedValue("mockResult") const mockTask = jest.fn().mockResolvedValue("mockResult")
const opts = { const opts: LockOptions = {
name: LockName.PERSIST_WRITETHROUGH, name: LockName.PERSIST_WRITETHROUGH,
type: lockType, type: lockType,
ttl: lockTtl, ttl: lockTtl,

View File

@ -54,5 +54,6 @@ export type LockOptions = {
} }
| { | {
type: LockType.AUTO_EXTEND type: LockType.AUTO_EXTEND
onExtend?: () => void
} }
) )