Skip to content

Commit d3e9c12

Browse files
committed
fix(db): honor migration runner lock wait policy
1 parent 6139b80 commit d3e9c12

2 files changed

Lines changed: 67 additions & 2 deletions

File tree

packages/db/script-migrations/0013_backfill_legacy_knowledge_base_workspaces.postgres.test.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import {
22
backfillLegacyKnowledgeBaseWorkspaces,
33
createPostgresLegacyKnowledgeBaseWorkspaceStore,
4+
type LegacyKnowledgeBaseMoveOutcome,
45
selectLegacyKnowledgeBaseWorkspace,
56
} from '@sim/db/script-migrations/0013_backfill_legacy_knowledge_base_workspaces'
7+
import { sleep } from '@sim/utils/helpers'
68
import { generateId } from '@sim/utils/id'
79
import postgres, { type Sql } from 'postgres'
810
import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest'
@@ -286,6 +288,71 @@ describe.runIf(Boolean(databaseUrl))('legacy KB workspace backfill in PostgreSQL
286288
expect(await userBytes('owner')).toBe(300)
287289
})
288290

291+
it('waits for an application-held workspace lock instead of aborting the migration', async () => {
292+
await workspace('destination')
293+
await kb()
294+
await document('kb', 100)
295+
let markWriterReady!: (pid: number) => void
296+
const writerReady = new Promise<number>((resolve) => {
297+
markWriterReady = resolve
298+
})
299+
let releaseWriter!: () => void
300+
const writerReleased = new Promise<void>((resolve) => {
301+
releaseWriter = resolve
302+
})
303+
const writer = sql.begin(async (tx) => {
304+
await tx`SELECT id FROM workspace WHERE id = 'destination' FOR NO KEY UPDATE`
305+
const [connection] = await tx<{ pid: number }[]>`SELECT pg_backend_pid() AS pid`
306+
markWriterReady(connection.pid)
307+
await writerReleased
308+
})
309+
let move: Promise<LegacyKnowledgeBaseMoveOutcome> | undefined
310+
try {
311+
const writerPid = await Promise.race([
312+
writerReady,
313+
writer.then(() => {
314+
throw new Error('Workspace writer finished before the concurrency check')
315+
}),
316+
])
317+
let settled = false
318+
move = subject.moveCandidate('kb')
319+
void move.then(
320+
() => {
321+
settled = true
322+
},
323+
() => {
324+
settled = true
325+
}
326+
)
327+
let waiting = false
328+
const deadline = Date.now() + 2_000
329+
while (!settled && Date.now() < deadline) {
330+
const [waiter] = await admin<{ waiting: boolean }[]>`
331+
SELECT EXISTS (SELECT 1 FROM pg_stat_activity
332+
WHERE ${writerPid} = ANY(pg_blocking_pids(pid)) AND wait_event_type = 'Lock') AS waiting
333+
`
334+
if (waiter.waiting) {
335+
waiting = true
336+
break
337+
}
338+
await sleep(1)
339+
}
340+
expect(waiting, 'The migration must wait for the workspace writer').toBe(true)
341+
/** Real lock contention must outlast the former five-second timeout. */
342+
await sleep(5_100)
343+
expect(settled).toBe(false)
344+
releaseWriter()
345+
await writer
346+
expect(await move).toBe('moved')
347+
expect((await scopedKb()).workspace_id).toBe('destination')
348+
expect(await userBytes('owner')).toBe(100)
349+
} finally {
350+
releaseWriter()
351+
await writer
352+
await move?.catch(() => undefined)
353+
}
354+
}, 15_000)
355+
289356
it('rolls back scope, names, and accounting together on failure and resumes safely', async () => {
290357
await workspace('destination')
291358
await kb()

packages/db/script-migrations/0013_backfill_legacy_knowledge_base_workspaces.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,6 @@ async function moveKnowledgeBase(
183183
tx: TransactionSql,
184184
knowledgeBaseId: string
185185
): Promise<LegacyKnowledgeBaseMoveOutcome> {
186-
await tx.unsafe("SET LOCAL lock_timeout = '5s'")
187-
await tx.unsafe("SET LOCAL statement_timeout = '30s'")
188186
const [kb] = await tx<Array<{ user_id: string }>>`
189187
SELECT user_id FROM knowledge_base
190188
WHERE id = ${knowledgeBaseId} AND workspace_id IS NULL AND organization_id IS NULL

0 commit comments

Comments
 (0)