Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
153 additions
and
7 deletions.
There are no files selected for viewing
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
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 |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import { withDbLock } from './db-lock'; | ||
import { getDbConfig } from '../../test/e2e/helpers/database-config'; | ||
import { IDBOption } from '../types'; | ||
import { Logger } from '../logger'; | ||
|
||
test('should lock access to any action', async () => { | ||
const lock = withDbLock(getDbConfig() as IDBOption); | ||
|
||
const asyncAction = (input: string) => Promise.resolve(`result: ${input}`); | ||
|
||
const result = await lock(asyncAction)('data'); | ||
|
||
expect(result).toBe('result: data'); | ||
}); | ||
|
||
const ms = (millis: number) => | ||
new Promise((resolve) => { | ||
setTimeout(() => resolve('time'), millis); | ||
}); | ||
|
||
test('should await other actions on lock', async () => { | ||
const lock = withDbLock(getDbConfig() as IDBOption); | ||
|
||
const results: string[] = []; | ||
const slowAsyncAction = (input: string) => { | ||
return new Promise((resolve) => { | ||
setTimeout(() => { | ||
results.push(input); | ||
resolve(input); | ||
}, 200); | ||
}); | ||
}; | ||
const fastAction = async (input: string) => { | ||
results.push(input); | ||
}; | ||
|
||
const lockedAction = lock(slowAsyncAction); | ||
const lockedAnotherAction = lock(fastAction); | ||
|
||
// deliberately skipped await to simulate another server running slow operation | ||
lockedAction('first'); | ||
await ms(100); // start fast action after slow action established DB connection | ||
await lockedAnotherAction('second'); | ||
|
||
await expect(results).toStrictEqual(['first', 'second']); | ||
}); | ||
|
||
test('should handle lock timeout', async () => { | ||
const timeoutMs = 1; | ||
let loggedError = ''; | ||
const lock = withDbLock(getDbConfig() as IDBOption, { | ||
lockKey: 1, | ||
timeout: timeoutMs, | ||
logger: { | ||
error(msg: string) { | ||
loggedError = msg; | ||
}, | ||
} as unknown as Logger, | ||
}); | ||
|
||
// the query should fail because of the timeout. This one is a fallback when timeout | ||
// was not triggered in the integration test | ||
const asyncAction = () => Promise.reject(new Error('Query read timeout')); | ||
|
||
await expect(lock(asyncAction)()).rejects.toStrictEqual( | ||
new Error('Query read timeout'), | ||
); | ||
expect(loggedError).toBe('Locking error: Query read timeout'); | ||
}); |
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,43 @@ | ||
import { Client } from 'pg'; | ||
import { IDBOption } from '../types'; | ||
import { Logger } from '../logger'; | ||
|
||
export const defaultLockKey = 479341; | ||
export const defaultTimeout = 5000; | ||
|
||
interface IDbLockOptions { | ||
timeout: number; | ||
lockKey: number; | ||
logger: Logger; | ||
} | ||
|
||
const defaultOptions: IDbLockOptions = { | ||
timeout: defaultTimeout, | ||
lockKey: defaultLockKey, | ||
logger: { ...console, fatal: console.error }, | ||
}; | ||
|
||
export const withDbLock = | ||
(dbConfig: IDBOption, config = defaultOptions) => | ||
<A extends any[], R>(fn: (...args: A) => Promise<R>) => | ||
async (...args: A): Promise<R> => { | ||
const client = new Client({ | ||
...dbConfig, | ||
query_timeout: config.timeout, | ||
}); | ||
try { | ||
await client.connect(); | ||
// wait to obtain a lock | ||
await client.query('SELECT pg_advisory_lock($1)', [config.lockKey]); | ||
const result = await fn(...args); | ||
return result; | ||
} catch (e) { | ||
config.logger.error(`Locking error: ${e.message}`); | ||
throw e; | ||
} finally { | ||
await client.query('SELECT pg_advisory_unlock($1)', [ | ||
config.lockKey, | ||
]); | ||
await client.end(); | ||
} | ||
}; |
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
8654c9e
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
unleash-monorepo-frontend – ./frontend
unleash-monorepo-frontend-unleash-team.vercel.app
unleash-monorepo-frontend.vercel.app
unleash-monorepo-frontend-git-main-unleash-team.vercel.app