mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-18 00:46:45 +00:00
fix(sync): handle duplicate operation IDs gracefully in appendBatch
When concurrent sync triggers occur (especially on Android), a race condition between filterNewOps() and appendBatch() can cause ConstraintError due to duplicate operation IDs in the byId unique index. Instead of failing sync, catch ConstraintError and return empty array - the ops were already inserted by a concurrent sync. Fixes #6213
This commit is contained in:
parent
ebe72c14df
commit
ab6df00a72
2 changed files with 26 additions and 2 deletions
|
|
@ -724,6 +724,23 @@ describe('OperationLogStoreService', () => {
|
|||
|
||||
expect(seqs).toEqual([]);
|
||||
});
|
||||
|
||||
it('should throw on duplicate operation IDs', async () => {
|
||||
const ops = [createTestOperation(), createTestOperation()];
|
||||
|
||||
// Insert first time - should succeed
|
||||
const seqs1 = await service.appendBatch(ops, 'remote');
|
||||
expect(seqs1.length).toBe(2);
|
||||
|
||||
// Insert same ops again - should throw
|
||||
await expectAsync(service.appendBatch(ops, 'remote')).toBeRejectedWithError(
|
||||
/Duplicate operation detected/,
|
||||
);
|
||||
|
||||
// Original ops should still be in store
|
||||
const allOps = await service.getOpsAfterSeq(0);
|
||||
expect(allOps.length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getOpById', () => {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import {
|
|||
OPS_INDEXES,
|
||||
} from './db-keys.const';
|
||||
import { runDbUpgrade } from './db-upgrade';
|
||||
import { Log } from '../../core/log';
|
||||
|
||||
/**
|
||||
* Vector clock entry stored in the vector_clock object store.
|
||||
|
|
@ -274,6 +275,12 @@ export class OperationLogStoreService {
|
|||
if (e instanceof DOMException && e.name === 'QuotaExceededError') {
|
||||
throw new StorageQuotaExceededError();
|
||||
}
|
||||
// ConstraintError: duplicate operation ID - see issue #6213
|
||||
if (e instanceof DOMException && e.name === 'ConstraintError') {
|
||||
throw new Error(
|
||||
'[OpLogStore] Duplicate operation detected (likely race condition). See #6213.',
|
||||
);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
|
@ -320,7 +327,7 @@ export class OperationLogStoreService {
|
|||
} catch (e) {
|
||||
// Fallback for databases created before version 3 index migration
|
||||
// This handles the case where the bySourceAndStatus index doesn't exist
|
||||
console.warn(
|
||||
Log.warn(
|
||||
'OperationLogStoreService: bySourceAndStatus index not found, using fallback scan',
|
||||
);
|
||||
const allOps = await this.db.getAll(STORE_NAMES.OPS);
|
||||
|
|
@ -707,7 +714,7 @@ export class OperationLogStoreService {
|
|||
);
|
||||
} catch (e) {
|
||||
// Fallback for databases created before version 3 index migration
|
||||
console.warn(
|
||||
Log.warn(
|
||||
'OperationLogStoreService: bySourceAndStatus index not found, using fallback scan',
|
||||
);
|
||||
const allOps = await this.db.getAll(STORE_NAMES.OPS);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue