Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions orga/changelog/add-await-document-pushed-conflict-test.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ADD test to ensure `awaitDocumentPushed()` resolves after a document push first had a conflict that was later resolved. (https://github.com/pubkey/rxdb/issues/8632)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- FIX: Increased the `OPEN_COLLECTIONS` retry timeout from 1050ms (35×30ms) to 9000ms (300×30ms) to prevent false COL23 errors when using slow storage backends like MongoDB where tests hold collections open for several seconds. Also fixed `startTime` tracking so `timeInRetry` in the error now reflects the total wait time.
7 changes: 4 additions & 3 deletions src/rx-collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,11 +249,12 @@ export class RxCollectionBase<
* while not awaiting the database.close() call to improve the test times.
* So when reopening collections and the OPEN_COLLECTIONS size is full,
* we retry after some times to account for this.
* Use a higher retry count to account for slow storage backends like MongoDB
* where tests hold collections open for several seconds.
*/
let count = 0;
let startTime = 0;
while (count < 35 && OPEN_COLLECTIONS.size >= NON_PREMIUM_COLLECTION_LIMIT) {
startTime = Date.now();
const startTime = Date.now();
while (count < 60 && OPEN_COLLECTIONS.size >= NON_PREMIUM_COLLECTION_LIMIT) {
await this.promiseWait(30);
count++;
}
Expand Down
73 changes: 73 additions & 0 deletions test/unit/replication.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import {

import type {
ReplicationPullHandlerResult,
RxConflictHandler,
RxReplicationConflict,
RxReplicationWriteToMasterRow,
RxStorage,
Expand Down Expand Up @@ -971,6 +972,78 @@ describe('replication.test.ts', () => {
await localCollection.database.close();
await remoteCollection.database.close();
});
it('should resolve after a push conflict was resolved', async () => {
/**
* Use a conflict handler that keeps the local state
* so that the resolved document gets pushed to the master
* after the conflict was resolved.
*/
const conflictHandler: RxConflictHandler<TestDocType> = {
isEqual: defaultConflictHandler.isEqual,
resolve: (i) => Promise.resolve(i.newDocumentState)
};
const localCollection = await humansCollection.createHumanWithTimestamp(
0,
randomToken(10),
false,
undefined,
conflictHandler
);

// the master already has a different state -> the first push must conflict
const masterDocs = new Map<string, WithDeleted<TestDocType>>();
masterDocs.set('conflict-doc', Object.assign(
schemaObjects.humanWithTimestampData({
id: 'conflict-doc',
name: 'remote-name'
}),
{ _deleted: false }
));

let hadConflict = false;
const replicationState = replicateRxCollection<TestDocType, CheckpointType>({
collection: localCollection,
replicationIdentifier: REPLICATION_IDENTIFIER_TEST,
live: true,
push: {
handler: async (rows) => {
// short sleep to simulate network latency
await wait(10);
const conflicts: WithDeleted<TestDocType>[] = [];
rows.forEach(row => {
const currentMasterDoc = masterDocs.get(row.newDocumentState.id);
if (
currentMasterDoc &&
(
!row.assumedMasterState ||
!conflictHandler.isEqual(currentMasterDoc, row.assumedMasterState, 'push-handler')
)
) {
hadConflict = true;
conflicts.push(currentMasterDoc);
} else {
masterDocs.set(row.newDocumentState.id, row.newDocumentState);
}
});
return conflicts;
}
}
});
ensureReplicationHasNoErrors(replicationState);

const doc = await localCollection.insert(schemaObjects.humanWithTimestampData({
id: 'conflict-doc',
name: 'local-name'
}));

await replicationState.awaitDocumentPushed(doc);

assert.strictEqual(hadConflict, true, 'the first push must have returned a conflict');
const masterDoc = ensureNotFalsy(masterDocs.get('conflict-doc'));
assert.strictEqual(masterDoc.name, 'local-name', 'the resolved state must exist on the master');

await localCollection.database.close();
});
it('should throw if the replication has no push handler', async () => {
const { localCollection, remoteCollection } = await getTestCollections({ local: 0, remote: 0 });

Expand Down
Loading