mirror of
https://github.com/stablyai/orca.git
synced 2026-11-04 11:07:42 +00:00
* [comment audit] Align legacy failure-read counts with cached display * [comment audit] Condense shared provider lifecycle and browser recovery comments [skip ci] * [comment audit] Condense shared generation, path and preview comments [skip ci] * [comment audit] Condense host, file and Git contract rationale [skip ci] * [comment audit] Tighten Git lifecycle and provider contract comments [skip ci] * [comment audit] Clarify shared host authority and launch isolation [skip ci] * [comment audit] simplify local access mobile and native chat contracts [skip ci] * [comment audit] simplify native chat presentation and state contracts [skip ci] * [comment audit] simplify native runtime and provider contracts [skip ci] * [comment audit] simplify migration and orchestration contracts [skip ci] * [comment audit] simplify plugin trust and process capture rationale [skip ci] * [comment audit] simplify terminal delivery and remote compatibility rationale [skip ci] * [comment audit] clarify remote recovery and launch wire rationale [skip ci] * [comment audit] shorten shared RPC compatibility and runtime evidence comments [skip ci] * [comment audit] simplify secure storage shell startup and sidebar rationale [skip ci] * [comment audit] clarify sidebar, skill and SSH invariants [skip ci] * [comment audit] condense structured chat delivery and timing rationale [skip ci] * [comment audit] condense terminal replay and telemetry invariants [skip ci] * [comment audit] clarify launch permission and terminal delivery rationale [skip ci] * [comment audit] clarify workspace ownership and layout contracts [skip ci] * [comment audit] wait for fitted and painted SSH terminal links * [comment audit] clarify layout persistence and session ownership [skip ci] * [comment audit] clarify host routing and workspace lifecycle contracts [skip ci] * [comment audit] clarify startup persistence and app lifetime comments [skip ci] * [comment audit] clarify recovery and activity boundaries [skip ci] * [comment audit] condense activity and artifact invariants [skip ci] * [comment audit] condense automation authority contracts [skip ci] * [comment audit] simplify automation scheduling and history contracts [skip ci] * [comment audit] clarify browser chrome and annotation lifecycles [skip ci] * [comment audit] clarify browser routing and retained guest ownership [skip ci] * [comment audit] clarify browser recovery and document ownership comments [skip ci] * [comment audit] restore hosted runners required by native IME fixtures * [comment audit] shorten preview, palette and dashboard invariants [skip ci] * [comment audit] clarify terminal preview, dashboard and retention comments [skip ci] * [comment audit] clarify diff-note recovery and editor lifecycle comments [skip ci] * [comment audit] shorten rich-text and combined-diff ownership comments [skip ci] * [comment audit] shorten autosave, draft recovery and model ownership comments [skip ci] * [comment audit] shorten save-queue, notebook and Markdown constraints [skip ci] * [comment audit] shorten Monaco, PDF and rich Markdown safeguards [skip ci] * [comment audit] clarify rich Markdown source and selection safeguards [skip ci] * [comment audit] clarify editor loading and emulator stream lifetimes [skip ci] * [comment audit] trim emulator and feature tour narration [skip ci] * [comment audit] clarify floating workspace and GitHub cache ownership [skip ci] * [comment audit] trim provider controls and Linear filter explanations [skip ci] * [comment audit] clarify pairing and chat resume lifetimes [skip ci] * [comment audit] clarify chat delivery and transcript lifetimes [skip ci] * [comment audit] clarify session options and host-owned status [skip ci] * [comment audit] clarify draft durability and live-status precedence [skip ci] * [comment audit] Trim chat send and model ownership explanations [skip ci] * [comment audit] Shorten transcript ownership and send recovery comments [skip ci] * [comment audit] Simplify native chat composer and transcript hook comments [skip ci] * [comment audit] Shorten native chat scroll and structured session explanations [skip ci] * [comment audit] Trim workspace picker and onboarding commentary [skip ci] * [comment audit] Simplify pet rendering, review cache and quick open comments [skip ci] * [comment audit] Trim sidebar and session history explanations [skip ci] * [comment audit] Condense session resume and review checks comments [skip ci] * [comment audit] Shorten checks state and file explorer refresh comments [skip ci] * [comment audit] Trim plugin bridge and source-control action rationale [skip ci] * [comment audit] Trim source-control listing and review rationale [skip ci] * [comment audit] Shorten sidebar lifecycle and explorer safeguards [skip ci] * [comment audit] Condense Settings account and appearance rationale [skip ci] * [comment audit] Trim pairing and repository Settings explanations [skip ci] * [comment audit] Condense Settings search SSH and terminal safeguards [skip ci] * [comment audit] Shorten Settings voice and provider ownership comments [skip ci] * [comment audit] Condense Settings draft persistence and host-state rationale [skip ci] * [comment audit] Trim Settings hooks and sidebar setup rationale [skip ci] * [comment audit] Condense sidebar deletion and board-filter safeguards [skip ci] * [comment audit] Condense worktree cards, host ownership and Markdown contracts [skip ci] * [comment audit] Condense sidebar drag, folder and filtering contracts [skip ci] * [comment audit] Condense sidebar parity, sleep and host ownership contracts [skip ci] * [comment audit] Prune focus-read baseline after comment cleanup * [comment audit] Clarify sidebar visibility, board drops and agent evidence [skip ci] * [comment audit] Tighten sidebar listing, drag and reveal contracts [skip ci] * [comment audit] Clarify viewport geometry and skill update safeguards [skip ci] * [comment audit] Tighten skill inventory, usage and host status comments [skip ci] * [comment audit] Clarify usage ownership and resource kill safeguards [skip ci] * [comment audit] Tighten tab selection, rename and search contracts [skip ci] * [comment audit] Tighten tab-strip geometry and drag ownership comments [skip ci] * [comment audit] Tighten Tasks recovery, pagination and mutation comments [skip ci] * [comment audit] Tighten provider loading and terminal recovery comments [skip ci] * [comment audit] Tighten completion, cadence and PTY ownership comments [skip ci] * [comment audit] Clarify terminal restore and parked-pane boundaries [skip ci] * [comment audit] Tighten PTY startup and replay explanations [skip ci] * [comment audit] Clarify PTY identity and delivery ownership [skip ci] * [comment audit] Shorten remote reconnect and input replay rationale [skip ci] * [comment audit] Condense terminal parking clipboard and file link comments [skip ci] * [comment audit] Clarify IME ownership and trim trace history [skip ci] * [comment audit] Shorten pane recovery and parked watcher explanations [skip ci] * [comment audit] Condense snapshot replay and visibility recovery rationale [skip ci] * [comment audit] Shorten terminal parking and keyboard ownership rationale [skip ci] * [comment audit] Trim renderer lifecycle and menu rationale [skip ci] * [comment audit] Condense workspace cleanup ownership and settlement notes [skip ci] * [comment audit] Clarify renderer hook evidence and source ownership [skip ci] * [comment audit] Tighten IPC ownership and reconnect explanations [skip ci] * [comment audit] Trim renderer focus, paste and attention comments [skip ci] * [comment audit] Shorten scroll, locale and agent lifecycle comments [skip ci] * [comment audit] Trim agent launch, resume and ownership commentary [skip ci] * [comment audit] Shorten account, browser and editor comments [skip ci] * [comment audit] Clarify host-mirror waits, previews and input ownership [skip ci] * [comment audit] Condense launch recovery and editor tokenization comments [skip ci] * [comment audit] Condense workspace selection and terminal pane rationale [skip ci] * [comment audit] Condense terminal fitting and rendering recovery comments [skip ci] * [comment audit] Condense terminal input and host routing explanations [skip ci] * [comment audit] Condense ownership and sleeping-session recovery rationale [skip ci] * [comment audit] Condense structured launch and terminal link explanations [skip ci] * [comment audit] Condense terminal ownership and workspace file routing rationale [skip ci] * [comment audit] Condense session persistence and workspace activation rationale [skip ci] * [comment audit] Condense workspace routing and browser ownership rationale [skip ci] * [comment audit] Condense mirror recovery and layout authority rationale [skip ci] * [comment audit] Condense runtime transport and file safety rationale [skip ci] * [comment audit] Condense runtime graph and structured session rationale [skip ci] * [comment audit] Condense browser creation and remote tab recovery rationale [skip ci] * [comment audit] Condense snapshot authority and publisher recovery rationale [skip ci] * [comment audit] Condense frame, startup and provider cache rationale [skip ci] * [comment audit] Condense host twin and agent lifecycle rationale [skip ci] * [comment audit] Condense browser authority and editor cleanup rationale [skip ci] * [comment audit] Condense editor buffer identity and review cache rationale [skip ci] * [comment audit] Condense provider lifetime and repository ownership rationale [skip ci] * [comment audit] Condense reconnect, hydration and terminal retirement rationale [skip ci] * [comment audit] Condense recovery, reconciliation and persistence rationale [skip ci] * [comment audit] Condense cleanup ownership and listing admission rationale [skip ci] * [comment audit] Condense teardown ordering and terminal ownership rationale [skip ci] * [comment audit] Condense recovery ledger and web host-boundary rationale [skip ci] * [comment audit] Condense web reconnect and credential lifecycle rationale [skip ci] * [comment audit] Condense cloud rollout and assignment safety rationale [skip ci] * [comment audit] Condense relay session and database recovery rationale [skip ci] * [comment audit] Condense relay placement and protocol compatibility rationale [skip ci] * [comment audit] Condense build and packaging constraints [skip ci] * [comment audit] Condense CI gates and validation isolation rationale [skip ci] * [comment audit] Condense catalog identity and browser validation rationale [skip ci] * [comment audit] Condense mobile browser and frame-budget rationale [skip ci] * [comment audit] Shorten mobile route and browser test explanations [skip ci] * [comment audit] Condense editor and session seam test rationale [skip ci] * [comment audit] Trim session and terminal closure history [skip ci] * [comment audit] Shorten terminal browser and census explanations [skip ci] * [comment audit] Preserve root-only E2E diagnostic baseline * [comment audit] Condense mobile seam and HTML preview test rationale [skip ci] * [comment audit] Condense preview instrumentation and render harness rationale [skip ci] * [comment audit] Condense bundle contracts and native-build constraints [skip ci] * [comment audit] Shorten native packaging and CI routing rationale [skip ci] * [comment audit] Condense release tooling and benchmark constraints [skip ci] * [comment audit] Condense packaging and platform gate rationale [skip ci] * [comment audit] Shorten mobile route and history rationale [skip ci] * [comment audit] Condense mobile browser and component rationale [skip ci] * [comment audit] Shorten drawer and workspace-form rationale [skip ci] * [comment audit] Trim mobile review and editor-document explanations [skip ci] * [comment audit] Condense editor lifecycle and Markdown explanations [skip ci] * [comment audit] Trim mobile file, diagnostics and dictation commentary [skip ci] * [comment audit] Simplify mobile host-list and workspace-view explanations [skip ci] * [comment audit] Clarify mobile shell lifetime and bridge delivery constraints [skip ci] * [comment audit] Condense bridge stream, native request and audio rationale [skip ci] * [comment audit] Reduce bridge schema and page-client commentary [skip ci] * [comment audit] Tighten mobile page bridge contracts [skip ci] * [comment audit] Clarify mobile cache and session constraints [skip ci] * [comment audit] Condense mobile route and lifecycle rationale [skip ci] * [comment audit] Tighten mobile shell hook timing rationale [skip ci] * [comment audit] Clarify route handoff and notification compatibility [skip ci] * [comment audit] Clarify push registration and dictation cleanup constraints [skip ci] * [comment audit] Condense keyboard geometry and native media ownership rationale [skip ci] * [comment audit] Tighten mobile chat scroll and composer concurrency rationale [skip ci] * [comment audit] Condense mobile reply compatibility and upload ownership rationale [skip ci] * [comment audit] Condense optimistic echo and scroll ownership rationale [skip ci] * [comment audit] Tighten stream replay and queued session ownership rationale [skip ci] * [comment audit] Condense mobile composer and reply-schema constraints [skip ci] * [comment audit] Clarify mobile transcript recovery and session ownership [skip ci] * [comment audit] Clarify mobile queue and source-control compatibility [skip ci] * [comment audit] Condense storage and task launch constraints [skip ci] * [comment audit] Clarify task schemas and terminal document ownership [skip ci] * [comment audit] Tighten terminal input and viewport rationale [skip ci] * [comment audit] Shorten terminal and RPC recorder contracts [skip ci] * [comment audit] Condense recorder engine and connection contracts [skip ci] * [comment audit] Shorten host credential and relay recovery comments [skip ci] * [comment audit] Tighten pairing recovery and bundle transport rationale [skip ci] * [comment audit] Clarify RPC delivery and workspace catalog boundaries [skip ci] * [comment audit] Condense catalog and relay entry contracts [skip ci] * [comment audit] Shorten shutdown and SSH relay delivery contracts [skip ci] * [comment audit] Condense WSL relay and E2E regression rationale [skip ci] * [comment audit] Shorten cross-version harness and renderer fixture explanations [skip ci] * [comment audit] Condense paired-host and E2E lifecycle rationale [skip ci] * [comment audit] Shorten layout oracle and regression explanations [skip ci] * [comment audit] Condense paired recovery and ownership regression contracts [skip ci] * [comment audit] Shorten restart, sidebar and SSH lifecycle contracts [skip ci] * [comment audit] Condense terminal rendering and IME regression rationale [skip ci] * [comment audit] Shorten terminal recovery and benchmark contracts [skip ci] * [comment audit] Condense tooling and Windows lifecycle proof rationale [skip ci] * [comment audit] Condense workflow routing and release rationale [skip ci] * [comment audit] Condense mobile security and installer contracts [skip ci] * [comment audit] Shorten native rationale and stylesheet workarounds [skip ci] * [comment audit] Shorten Windows visibility probe rationale [skip ci] * [comment audit] Condense embedded release and signing rationale [skip ci] * [comment audit] Retain current main test behavior in final reconciliation [skip ci] * [comment audit] Complete final comment review across current source inventory * [comment audit] Handle consolidated audit reports before final CI * [comment audit] Preserve credential storage census detail [skip ci] * [comment audit] Scale broad changed E2E selections with verified timing shards * [comment audit] Align E2E assertions with browser close and settled session metadata * [comment audit] Match existing E2Es to current resource and windowing contracts * [comment audit] Observe the shared server outage notice in reconnect coverage * [comment audit] Make existing renderer probes measure settled tab and PTY work * [comment audit] Align palette E2E selector with task search * [comment audit] Refresh remaining palette search selectors * [comment audit] Keep selector correction within selected audit specs * [comment audit] Await emoji fixture settings inside renderer evaluation * [comment audit] Cancel fixture fit retries between terminal tests --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
297 lines
11 KiB
TypeScript
297 lines
11 KiB
TypeScript
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
|
import { RelayAssignmentStore } from './assignment-store.js'
|
|
import { openRelayDatabase, type RelayDatabase } from './database.js'
|
|
|
|
const databaseUrl = process.env.ORCA_RELAY_TEST_POSTGRES_URL
|
|
const describePostgres = databaseUrl ? describe : describe.skip
|
|
|
|
// Three cells: the inventory lock covers more than the rows a move touches, and
|
|
// a high-to-low move exposes any lock taken out of cell_id order.
|
|
const cells = [
|
|
{
|
|
id: 'rebind-inventory-postgres-a',
|
|
url: 'https://rebind-inventory-postgres-a.example.com',
|
|
capacityRequests: 1_000,
|
|
connectionHardCap: 600 as const,
|
|
connectionUnobservedBound: 50
|
|
},
|
|
{
|
|
id: 'rebind-inventory-postgres-b',
|
|
url: 'https://rebind-inventory-postgres-b.example.com',
|
|
capacityRequests: 1_000,
|
|
connectionHardCap: 600 as const,
|
|
connectionUnobservedBound: 50
|
|
},
|
|
{
|
|
id: 'rebind-inventory-postgres-c',
|
|
url: 'https://rebind-inventory-postgres-c.example.com',
|
|
capacityRequests: 1_000,
|
|
connectionHardCap: 600 as const,
|
|
connectionUnobservedBound: 50
|
|
}
|
|
]
|
|
const identity = { userId: 'rebind-inventory-postgres-user', relayHostId: 'rebindinvhost001' }
|
|
|
|
function heartbeat(cell: (typeof cells)[number]) {
|
|
return {
|
|
cellId: cell.id,
|
|
cellUrl: cell.url,
|
|
cellIncarnation: '11111111-1111-4111-8111-111111111111',
|
|
startedAt: 50,
|
|
ready: true,
|
|
observedRequests: 0,
|
|
totalConnections: 0,
|
|
inFlightConnections: 0,
|
|
reservedConnectionUnits: 0,
|
|
enforcedConnectionUnits: 0,
|
|
connectionInclusionWatermark: 1,
|
|
connectionHardCap: 600 as const,
|
|
connectionUnobservedBound: 50
|
|
}
|
|
}
|
|
|
|
// Same-cell rebind must proceed while another cell row is held; fleet-wide locking formerly waited to COMMIT and hit request 55P03.
|
|
describePostgres('PostgreSQL control rebind under a held cell row', () => {
|
|
const databases: RelayDatabase[] = []
|
|
|
|
beforeAll(async () => {
|
|
databases.push(
|
|
await openRelayDatabase({ databaseUrl, dataDir: '' }),
|
|
await openRelayDatabase({ databaseUrl, dataDir: '' })
|
|
)
|
|
})
|
|
|
|
async function removeTestRows(database: RelayDatabase): Promise<void> {
|
|
await database.query(
|
|
`DELETE FROM relay_control_connection_reservations WHERE user_id = ?`,
|
|
[identity.userId]
|
|
)
|
|
for (const table of [
|
|
'relay_assignment_activity_leases',
|
|
'relay_post_drain_migration_pins',
|
|
'relay_assignment_migration_incarnations',
|
|
'relay_assignment_migrations',
|
|
'relay_assignments'
|
|
]) {
|
|
await database.query(`DELETE FROM ${table} WHERE user_id = ?`, [identity.userId])
|
|
}
|
|
for (const cell of cells) {
|
|
for (const table of [
|
|
'relay_cell_connection_snapshots',
|
|
'relay_cell_connection_runtime',
|
|
'relay_cell_connection_limits',
|
|
'relay_cell_runtime',
|
|
'relay_cells'
|
|
]) {
|
|
await database.query(`DELETE FROM ${table} WHERE cell_id = ?`, [cell.id])
|
|
}
|
|
}
|
|
}
|
|
|
|
afterAll(async () => {
|
|
if (databases[0]) await removeTestRows(databases[0])
|
|
for (const connection of databases) await connection.close()
|
|
})
|
|
|
|
it("rebinds and supersedes a control while another cell's row is held", async () => {
|
|
// A prior aborted run leaves connection snapshots that reject a replayed watermark.
|
|
await removeTestRows(databases[0]!)
|
|
const store = new RelayAssignmentStore(databases[0]!, () => 100)
|
|
await store.reconcileCells(cells)
|
|
for (const cell of cells) await store.recordCellHeartbeat(heartbeat(cell))
|
|
// Pin the host to cell A so placement is deterministic.
|
|
await store.setCellEnabled(cells[1]!.id, false)
|
|
await store.setCellEnabled(cells[2]!.id, false)
|
|
const assignment = await store.assign(identity)
|
|
expect(assignment.cellId).toBe(cells[0]!.id)
|
|
await store.setCellEnabled(cells[1]!.id, true)
|
|
await store.setCellEnabled(cells[2]!.id, true)
|
|
await store.activateControl(identity, {
|
|
cellId: cells[0]!.id,
|
|
assignmentEpoch: assignment.assignmentEpoch,
|
|
generation: 1,
|
|
connectionInclusionWatermark: 10
|
|
})
|
|
|
|
// Hold only cell B's row on a second connection, the way a rebind on B
|
|
// does, for longer than the request-path lock bound.
|
|
let releaseInventory!: () => void
|
|
const inventoryReleased = new Promise<void>((resolve) => {
|
|
releaseInventory = resolve
|
|
})
|
|
let inventoryHeld!: () => void
|
|
const inventoryHeldPromise = new Promise<void>((resolve) => {
|
|
inventoryHeld = resolve
|
|
})
|
|
const holder = databases[1]!.transaction(async (transaction) => {
|
|
await transaction.queryLocked(`SELECT * FROM relay_cells WHERE cell_id = ?`, [cells[1]!.id])
|
|
inventoryHeld()
|
|
await inventoryReleased
|
|
})
|
|
let deadline: ReturnType<typeof setTimeout> | undefined
|
|
let reboundWork: Promise<string> | undefined
|
|
try {
|
|
await inventoryHeldPromise
|
|
|
|
// A generation-2 rebind on cell A supersedes generation 1. It must not
|
|
// wait on cell B's row.
|
|
const startedAt = Date.now()
|
|
const blockedStatement = async (): Promise<string> => {
|
|
const rows = await databases[1]!.query(
|
|
`SELECT left(query, 160) AS q FROM pg_stat_activity
|
|
WHERE datname = current_database() AND wait_event_type = 'Lock'`
|
|
)
|
|
return rows.map((row) => String(row.q)).join(' | ')
|
|
}
|
|
const timeout = new Promise<never>((_, reject) => {
|
|
deadline = setTimeout(
|
|
() =>
|
|
void blockedStatement()
|
|
.then((statement) =>
|
|
reject(new Error(`rebind on cell A blocked behind cell B's row: ${statement}`))
|
|
)
|
|
.catch(reject),
|
|
2_000
|
|
)
|
|
})
|
|
reboundWork = store.activateControl(identity, {
|
|
cellId: cells[0]!.id,
|
|
assignmentEpoch: assignment.assignmentEpoch,
|
|
generation: 2,
|
|
connectionInclusionWatermark: 11
|
|
})
|
|
const rebound = await Promise.race([reboundWork, timeout])
|
|
const elapsedMs = Date.now() - startedAt
|
|
|
|
expect(rebound).toBe(`control:${cells[0]!.id}:2`)
|
|
expect(elapsedMs).toBeLessThan(2_000)
|
|
const controls = await databases[0]!.query(
|
|
`SELECT activity_id FROM relay_assignment_activity_leases
|
|
WHERE user_id = ? AND activity_kind = 'control' ORDER BY activity_id`,
|
|
[identity.userId]
|
|
)
|
|
expect(controls).toEqual([{ activity_id: `control:${cells[0]!.id}:2` }])
|
|
const reserved = await databases[0]!.query(
|
|
`SELECT reserved_requests FROM relay_cells WHERE cell_id = ?`,
|
|
[cells[0]!.id]
|
|
)
|
|
expect(Number(reserved[0]!.reserved_requests)).toBe(1)
|
|
} finally {
|
|
clearTimeout(deadline)
|
|
releaseInventory()
|
|
try {
|
|
await holder
|
|
} finally {
|
|
await Promise.allSettled(reboundWork ? [reboundWork] : [])
|
|
}
|
|
}
|
|
}, 15_000)
|
|
|
|
// Client activity IDs can migrate across cells; high→low moves expose lock cycles against ascending placement order.
|
|
it('moves an activity from a higher cell to a lower one in cell_id order', async () => {
|
|
await removeTestRows(databases[0]!)
|
|
const [cellA, cellB, cellC] = cells as [(typeof cells)[0], (typeof cells)[0], (typeof cells)[0]]
|
|
const store = new RelayAssignmentStore(databases[0]!, () => 100)
|
|
await store.reconcileCells(cells)
|
|
for (const cell of cells) await store.recordCellHeartbeat(heartbeat(cell))
|
|
await store.setCellEnabled(cellA.id, false)
|
|
await store.setCellEnabled(cellB.id, false)
|
|
const assignment = await store.assign(identity)
|
|
expect(assignment.cellId).toBe(cellC.id)
|
|
await store.setCellEnabled(cellA.id, true)
|
|
await store.setCellEnabled(cellB.id, true)
|
|
const activityId = 'splice:rebind-inventory-postgres'
|
|
await store.acquireActivity(identity, { activityId, kind: 'splice', cellId: cellC.id })
|
|
// The migration makes B authoritative; the lease still sits on C.
|
|
const migration = await store.startEvacuation(identity, cellB.id)
|
|
expect(migration.targetCellId).toBe(cellB.id)
|
|
|
|
const movingDatabase = await openRelayDatabase({
|
|
databaseUrl,
|
|
dataDir: '',
|
|
applicationName: 'orca-relay/cloud3-rebind-move'
|
|
})
|
|
const moveStore = new RelayAssignmentStore(movingDatabase, () => 100)
|
|
// Hold B: ordered B→C move waits with no other lock; C-first or fleet-wide locking would hold C or A.
|
|
let releaseRow!: () => void
|
|
const rowReleased = new Promise<void>((resolve) => {
|
|
releaseRow = resolve
|
|
})
|
|
let rowHeld!: () => void
|
|
const rowHeldPromise = new Promise<void>((resolve) => {
|
|
rowHeld = resolve
|
|
})
|
|
const heldWhileMoverWaits: string[] = []
|
|
const holder = databases[1]!.transaction(async (transaction) => {
|
|
await transaction.queryLocked(`SELECT * FROM relay_cells WHERE cell_id = ?`, [cellB.id])
|
|
rowHeld()
|
|
await rowReleased
|
|
for (const cell of [cellA, cellC]) {
|
|
try {
|
|
await transaction.queryLocked(`SELECT * FROM relay_cells WHERE cell_id = ?`, [cell.id], {
|
|
failIfUnavailable: true
|
|
})
|
|
} catch {
|
|
heldWhileMoverWaits.push(cell.id)
|
|
}
|
|
}
|
|
})
|
|
await rowHeldPromise
|
|
const move = moveStore.acquireActivity(identity, {
|
|
activityId,
|
|
kind: 'splice',
|
|
cellId: cellB.id
|
|
})
|
|
let moved = false
|
|
const observedMove = move.then(() => {
|
|
moved = true
|
|
})
|
|
try {
|
|
await expect
|
|
.poll(
|
|
async () => {
|
|
const rows = await databases[0]!.query(
|
|
`SELECT count(*) AS waiting FROM pg_stat_activity
|
|
WHERE datname = current_database() AND application_name = ? AND wait_event_type = 'Lock'`,
|
|
['orca-relay/cloud3-rebind-move']
|
|
)
|
|
return Number(rows[0]!.waiting)
|
|
},
|
|
{ timeout: 2_000, interval: 10 }
|
|
)
|
|
.toBe(1)
|
|
expect(moved).toBe(false)
|
|
releaseRow()
|
|
await holder
|
|
await observedMove
|
|
expect(heldWhileMoverWaits).toEqual([])
|
|
} finally {
|
|
releaseRow()
|
|
try {
|
|
await holder
|
|
} finally {
|
|
try {
|
|
await observedMove
|
|
} finally {
|
|
await movingDatabase.close()
|
|
}
|
|
}
|
|
}
|
|
|
|
const reservations = await databases[0]!.query(
|
|
`SELECT cell_id, reserved_requests FROM relay_cells
|
|
WHERE cell_id IN (?, ?, ?) ORDER BY cell_id ASC`,
|
|
[cellA.id, cellB.id, cellC.id]
|
|
)
|
|
const reserved = reservations.map((row) => [String(row.cell_id), Number(row.reserved_requests)])
|
|
expect(reserved).toEqual([
|
|
[cellA.id, 0],
|
|
// Migration grant plus the moved splice, as in the SQLite origin-scoped
|
|
// reservation case: the lock change did not alter accounting.
|
|
[cellB.id, 6],
|
|
// The sticky grant stays on the source until the migration completes.
|
|
[cellC.id, 1]
|
|
])
|
|
}, 15_000)
|
|
})
|