fix(api): дождаться запросов перед операцией с базой
This commit is contained in:
1 parent
9264b6636a
commit
8682f2a7a5
12 files changed
+183
-5
No files matched your search
@@ -0,0 +1,39 @@
|
||||
function conflict(message: string): Error & { statusCode: number } {
|
||||
return Object.assign(new Error(message), { statusCode: 409 })
|
||||
}
|
||||
|
||||
/** Handler lifetime, not socket lifetime: disconnect does not finish an active write. */
|
||||
export class DatabaseRequestGate {
|
||||
private paused = false
|
||||
private active = new Map<symbol, (() => void) | undefined>()
|
||||
private idle: (() => void) | undefined
|
||||
|
||||
async request<T>(run: () => Promise<T>, cancelStream?: () => void): Promise<T> {
|
||||
if (this.paused) throw conflict("Выполняется операция с базой приложения; повторите запрос позже")
|
||||
const lease = Symbol()
|
||||
this.active.set(lease, cancelStream)
|
||||
try { return await run() }
|
||||
finally {
|
||||
this.active.delete(lease)
|
||||
if (!this.active.size) this.idle?.()
|
||||
}
|
||||
}
|
||||
|
||||
async exclusive<T>(run: () => Promise<T>, timeoutMs = 30_000): Promise<T> {
|
||||
if (this.paused) throw conflict("Операция с базой приложения уже выполняется")
|
||||
this.paused = true
|
||||
let timer: ReturnType<typeof setTimeout> | undefined
|
||||
try {
|
||||
for (const cancel of this.active.values()) cancel?.()
|
||||
if (this.active.size) await new Promise<void>((resolve, reject) => {
|
||||
this.idle = resolve
|
||||
timer = setTimeout(() => reject(conflict("Не удалось дождаться завершения API-запросов; операция с базой отменена")), timeoutMs)
|
||||
})
|
||||
return await run()
|
||||
} finally {
|
||||
if (timer) clearTimeout(timer)
|
||||
this.idle = undefined
|
||||
this.paused = false
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user