test(session): cover remaining preparation races
This commit is contained in:
@@ -802,19 +802,8 @@ export class PersistenceCoordinator<TornMarker = unknown> {
|
||||
}
|
||||
|
||||
/** Read, repair in memory, validate, and freeze one cold source once. */
|
||||
private async prepareCore(
|
||||
id: SessionId,
|
||||
signal?: AbortSignal,
|
||||
): Promise<PreparedSessionSource<TornMarker>> {
|
||||
signal?.throwIfAborted()
|
||||
let stored: StoredPrefix<TornMarker> | undefined
|
||||
try {
|
||||
stored = await this.backend.loadStored(id, signal)
|
||||
} catch (error: unknown) {
|
||||
if (signal?.aborted) signal.throwIfAborted()
|
||||
throw error
|
||||
}
|
||||
signal?.throwIfAborted()
|
||||
private async prepareCore(id: SessionId): Promise<PreparedSessionSource<TornMarker>> {
|
||||
const stored = await this.backend.loadStored(id)
|
||||
if (stored === undefined) throw new Error(`session "${id}" not found`)
|
||||
try {
|
||||
const { meta, events, revision, tornMarker } = stored
|
||||
|
||||
@@ -441,8 +441,9 @@ describe('PersistenceCoordinator session preparations', () => {
|
||||
const prepareId = SessionId('prepare-became-live')
|
||||
const loadId = SessionId('load-became-live')
|
||||
const inspectId = SessionId('inspect-became-live')
|
||||
const validatedInspectId = SessionId('validated-inspect-became-live')
|
||||
const failedInspectId = SessionId('failed-inspect-became-live')
|
||||
for (const id of [prepareId, loadId, inspectId]) {
|
||||
for (const id of [prepareId, loadId, inspectId, validatedInspectId]) {
|
||||
backend.store.set(id, { meta: meta(id), events: oneTurnLog() })
|
||||
}
|
||||
let coordinator!: PersistenceCoordinator<never>
|
||||
@@ -472,6 +473,15 @@ describe('PersistenceCoordinator session preparations', () => {
|
||||
await expect(coordinator.inspect(inspectId)).resolves.toMatchObject({ meta: { id: inspectId } })
|
||||
inspectGet.mockRestore()
|
||||
|
||||
const validatedInspectLive = Session.create(validatedInspectId, oneTurnLog(), meta(validatedInspectId))
|
||||
const validatedInspectGet = vi.spyOn(ctx.sessions, 'get')
|
||||
.mockReturnValueOnce(undefined)
|
||||
.mockReturnValueOnce(undefined)
|
||||
.mockReturnValueOnce(validatedInspectLive)
|
||||
await expect(coordinator.inspect(validatedInspectId))
|
||||
.resolves.toMatchObject({ meta: { id: validatedInspectId } })
|
||||
validatedInspectGet.mockRestore()
|
||||
|
||||
const failedInspectLive = Session.create(failedInspectId, oneTurnLog(), meta(failedInspectId))
|
||||
backend.beforeLoadStored = () => Promise.reject(new Error('load failed'))
|
||||
const failedInspectGet = vi.spyOn(ctx.sessions, 'get')
|
||||
@@ -705,6 +715,38 @@ describe('PersistenceCoordinator session preparations', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('retries cold append adoption when the prepared revision becomes stale', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SessionStore)
|
||||
const backend = new ControlledBackend()
|
||||
const id = SessionId('append-adoption-revision-refresh')
|
||||
backend.store.set(id, { meta: meta(id), events: oneTurnLog() })
|
||||
const readStoredRevision = backend.readStoredRevision.bind(backend)
|
||||
vi.spyOn(backend, 'readStoredRevision')
|
||||
.mockResolvedValueOnce(SessionPersistenceRevision('stale-revision'))
|
||||
.mockImplementation(readStoredRevision)
|
||||
let coordinator!: PersistenceCoordinator<never>
|
||||
const fiber = await ctx.plugin(Object.assign((inner: Context) => {
|
||||
coordinator = new PersistenceCoordinator(inner, backend)
|
||||
}, { inject: ['sessions'] }))
|
||||
|
||||
try {
|
||||
await coordinator.append(id, [{
|
||||
type: 'turn/start',
|
||||
seq: oneTurnLog().length,
|
||||
time: 7,
|
||||
data: { turn: 2 },
|
||||
}])
|
||||
|
||||
expect(backend.loadAttempts).toBe(2)
|
||||
expect(backend.appendAttempts).toBe(1)
|
||||
expect(backend.store.get(id)?.events).toHaveLength(oneTurnLog().length + 1)
|
||||
} finally {
|
||||
await fiber.dispose()
|
||||
await ctx.fiber.dispose()
|
||||
}
|
||||
})
|
||||
|
||||
it('inspects an open live turn without balancing it', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SessionStore)
|
||||
|
||||
Reference in New Issue
Block a user