From d890645f873ec5503b41f4c819f4d2eca1d25034 Mon Sep 17 00:00:00 2001 From: Paul Mestemaker Date: Tue, 24 May 2022 18:41:39 -0700 Subject: [PATCH 1/4] repro: Race condition for pouchdb-memory-adapter This is a simple repro for a problem discovered in PouchDB 7.3.0. This was originally discovered in RxDB. Discussion here: https://github.com/pubkey/rxdb/pull/3807. ``` 134 passing (2s) 3 pending 1 failing 1) test.memory-adapter.js Race condition initially discovered with PouchDB in-memory-adapter 7.3.0: Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. at listOnTimeout (node:internal/timers:559:17) at processTimers (node:internal/timers:502:7) ``` --- tests/unit/test.memory-adapter.js | 79 +++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 tests/unit/test.memory-adapter.js diff --git a/tests/unit/test.memory-adapter.js b/tests/unit/test.memory-adapter.js new file mode 100644 index 0000000000..d165f79f31 --- /dev/null +++ b/tests/unit/test.memory-adapter.js @@ -0,0 +1,79 @@ +var PouchDB = require('../../packages/node_modules/pouchdb-for-coverage'); +var memoryAdapter = require('../../packages/node_modules/pouchdb-adapter-memory'); +PouchDB.plugin(memoryAdapter); + +describe('test.memory-adapter.js', () => { + it('Race condition initially discovered with PouchDB in-memory-adapter 7.3.0', async () => { + const func1 = async () => { + const pouch1 = new PouchDB('func1db', { + adapter: 'memory' + }); + const docId = 'func1doc1'; + + // insert + await pouch1.bulkDocs({ + docs: [{ + _id: docId, + value: 1, + _rev: '1-51b2fae5721cc4d3cf7392f19e6cc118' + }] + }, { + new_edits: false + }); + + // update + let getDocs = await pouch1.bulkGet({ + docs: [{id: docId}], + revs: true, + latest: true + }); + const useRevs = (getDocs). + results[0].docs[0].ok._revisions; + useRevs.start = useRevs.start + 1; + useRevs.ids.unshift('a723631364fbfa906c5ffa8203ac9725'); + + await pouch1.bulkDocs({ + docs: [{ + _id: docId, + value: 2, + _rev: '2-a723631364fbfa906c5ffa8203ac9725', + _revisions: useRevs + }] + }, { + new_edits: false + }); + + // delete + getDocs = await pouch1.bulkGet({ + docs: [{id: docId}], + revs: true, + latest: true + }); + + // same via .get + const getDoc = await pouch1.get(docId); + // if this is switched to pouch1.destroy(); ... this test will pass. + pouch1.close(); + }; + + const func2 = async () => { + const pouch2 = new PouchDB( + 'func2db', { + adapter: 'memory', + }); + + await pouch2.createIndex({ + index: { + fields: ['foo'] + } + }); + pouch2.destroy(); + }; + + // func1 succeeds when run alone. + // func2 succeeds when run alone. + // As of PouchDB 7.3.0, when running these functions in parallel, there is a race condition where func2 gets + // impacted by func1. The result: func2 will hang and the test will timeout. + await Promise.all([func1(), func2()]); + }); +}); From ff7d51065e432c4fa868c2022ce1c4dd15b870cf Mon Sep 17 00:00:00 2001 From: Paul Mestemaker Date: Wed, 7 Dec 2022 10:18:39 -0800 Subject: [PATCH 2/4] fix: eslint warning --- tests/unit/test.memory-adapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/test.memory-adapter.js b/tests/unit/test.memory-adapter.js index d165f79f31..1d3ea3f9f5 100644 --- a/tests/unit/test.memory-adapter.js +++ b/tests/unit/test.memory-adapter.js @@ -51,7 +51,7 @@ describe('test.memory-adapter.js', () => { }); // same via .get - const getDoc = await pouch1.get(docId); + await pouch1.get(docId); // if this is switched to pouch1.destroy(); ... this test will pass. pouch1.close(); }; From d04021f019096878919020962db808f6e9674c4a Mon Sep 17 00:00:00 2001 From: Paul Mestemaker Date: Wed, 7 Dec 2022 10:19:10 -0800 Subject: [PATCH 3/4] fix: apply fix from #8515 --- packages/node_modules/pouchdb-adapter-leveldb-core/src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/pouchdb-adapter-leveldb-core/src/index.js b/packages/node_modules/pouchdb-adapter-leveldb-core/src/index.js index 0ec271a7cf..ac61f768b4 100644 --- a/packages/node_modules/pouchdb-adapter-leveldb-core/src/index.js +++ b/packages/node_modules/pouchdb-adapter-leveldb-core/src/index.js @@ -1176,7 +1176,7 @@ function LevelPouch(opts, callback) { var adapterName = functionName(leveldown); var adapterStore = dbStores.get(adapterName); - var keys = [...adapterStore.keys()].filter(k => k.includes("-mrview-")); + var keys = [...adapterStore.keys()].filter(k => k.includes(name)).filter(k => k.includes("-mrview-")); keys.forEach(key => { var eventEmitter = adapterStore.get(key); eventEmitter.removeAllListeners(); From 00e12e6ece82096db9acee3b1fd27e4c596bb818 Mon Sep 17 00:00:00 2001 From: Paul Mestemaker Date: Wed, 7 Dec 2022 15:40:20 -0800 Subject: [PATCH 4/4] Addressing PR feedback: https://github.com/pouchdb/pouchdb/pull/8513#pullrequestreview-1209172650 --- .../node_modules/pouchdb-adapter-leveldb-core/src/index.js | 4 +++- tests/unit/test.memory-adapter.js | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/node_modules/pouchdb-adapter-leveldb-core/src/index.js b/packages/node_modules/pouchdb-adapter-leveldb-core/src/index.js index ac61f768b4..a1f84875ab 100644 --- a/packages/node_modules/pouchdb-adapter-leveldb-core/src/index.js +++ b/packages/node_modules/pouchdb-adapter-leveldb-core/src/index.js @@ -4,6 +4,7 @@ import { obj as through } from 'through2'; import getArguments from 'argsarray'; import Deque from 'double-ended-queue'; import bufferFrom from 'buffer-from'; // ponyfill for Node <6 +import PouchDB from 'pouchdb-core'; import { clone, changesHandler as Changes, @@ -1176,7 +1177,8 @@ function LevelPouch(opts, callback) { var adapterName = functionName(leveldown); var adapterStore = dbStores.get(adapterName); - var keys = [...adapterStore.keys()].filter(k => k.includes(name)).filter(k => k.includes("-mrview-")); + var viewNamePrefix = PouchDB.prefix + name + "-mrview-"; + var keys = [...adapterStore.keys()].filter(k => k.includes(viewNamePrefix)); keys.forEach(key => { var eventEmitter = adapterStore.get(key); eventEmitter.removeAllListeners(); diff --git a/tests/unit/test.memory-adapter.js b/tests/unit/test.memory-adapter.js index 1d3ea3f9f5..927deafa7b 100644 --- a/tests/unit/test.memory-adapter.js +++ b/tests/unit/test.memory-adapter.js @@ -5,7 +5,7 @@ PouchDB.plugin(memoryAdapter); describe('test.memory-adapter.js', () => { it('Race condition initially discovered with PouchDB in-memory-adapter 7.3.0', async () => { const func1 = async () => { - const pouch1 = new PouchDB('func1db', { + const pouch1 = new PouchDB('test-db', { adapter: 'memory' }); const docId = 'func1doc1'; @@ -58,7 +58,7 @@ describe('test.memory-adapter.js', () => { const func2 = async () => { const pouch2 = new PouchDB( - 'func2db', { + 'test-db-2', { adapter: 'memory', });