105 lines
3.0 KiB
JavaScript
105 lines
3.0 KiB
JavaScript
import { describe, it, expect, beforeEach } from 'vitest'
|
|
import { acquire, clearLocks } from '../../server/utils/asyncLock.js'
|
|
|
|
describe('asyncLock', () => {
|
|
beforeEach(() => {
|
|
clearLocks()
|
|
})
|
|
|
|
it('executes callback immediately when no lock exists', async () => {
|
|
const executed = { value: false }
|
|
await acquire('test', async () => {
|
|
executed.value = true
|
|
return 42
|
|
})
|
|
expect(executed.value).toBe(true)
|
|
})
|
|
|
|
it('returns callback result', async () => {
|
|
const result = await acquire('test', async () => {
|
|
return { value: 123 }
|
|
})
|
|
expect(result).toEqual({ value: 123 })
|
|
})
|
|
|
|
it('serializes concurrent operations on same key', async () => {
|
|
const results = []
|
|
const promises = Array.from({ length: 5 }, (_, i) =>
|
|
acquire('same-key', async () => {
|
|
results.push(`start-${i}`)
|
|
await new Promise(resolve => setTimeout(resolve, 10))
|
|
results.push(`end-${i}`)
|
|
return i
|
|
}),
|
|
)
|
|
|
|
await Promise.all(promises)
|
|
|
|
// Operations should be serialized: start-end pairs should not interleave
|
|
expect(results.length).toBe(10)
|
|
Array.from({ length: 5 }, (_, i) => {
|
|
expect(results[i * 2]).toBe(`start-${i}`)
|
|
expect(results[i * 2 + 1]).toBe(`end-${i}`)
|
|
})
|
|
})
|
|
|
|
it('allows parallel operations on different keys', async () => {
|
|
const results = []
|
|
const promises = Array.from({ length: 5 }, (_, i) =>
|
|
acquire(`key-${i}`, async () => {
|
|
results.push(`start-${i}`)
|
|
await new Promise(resolve => setTimeout(resolve, 10))
|
|
results.push(`end-${i}`)
|
|
return i
|
|
}),
|
|
)
|
|
|
|
await Promise.all(promises)
|
|
|
|
// Different keys can run in parallel
|
|
expect(results.length).toBe(10)
|
|
// All starts should come before all ends (parallel execution)
|
|
const starts = results.filter(r => r.startsWith('start'))
|
|
const ends = results.filter(r => r.startsWith('end'))
|
|
expect(starts.length).toBe(5)
|
|
expect(ends.length).toBe(5)
|
|
})
|
|
|
|
it('handles errors and releases lock', async () => {
|
|
const callCount = { value: 0 }
|
|
try {
|
|
await acquire('error-key', async () => {
|
|
callCount.value++
|
|
throw new Error('Test error')
|
|
})
|
|
}
|
|
catch (error) {
|
|
expect(error.message).toBe('Test error')
|
|
}
|
|
|
|
// Lock should be released, next operation should execute
|
|
await acquire('error-key', async () => {
|
|
callCount.value++
|
|
return 'success'
|
|
})
|
|
|
|
expect(callCount.value).toBe(2)
|
|
})
|
|
|
|
it('maintains lock ordering', async () => {
|
|
const order = []
|
|
const promises = Array.from({ length: 3 }, (_, i) =>
|
|
acquire('ordered', async () => {
|
|
order.push(`before-${i}`)
|
|
await new Promise(resolve => setTimeout(resolve, 5))
|
|
order.push(`after-${i}`)
|
|
}),
|
|
)
|
|
|
|
await Promise.all(promises)
|
|
|
|
// Should execute in order: before-0, after-0, before-1, after-1, before-2, after-2
|
|
expect(order).toEqual(['before-0', 'after-0', 'before-1', 'after-1', 'before-2', 'after-2'])
|
|
})
|
|
})
|