Index: oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/NodeDocument.java =================================================================== --- oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/NodeDocument.java (revision 1867174) +++ oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/NodeDocument.java (working copy) @@ -1866,6 +1866,13 @@ revision.toString()); } + public static void hasLastRev(@NotNull UpdateOp op, + @NotNull Revision revision) { + checkNotNull(op).equals(LAST_REV, + new Revision(0, 0, revision.getClusterId()), + revision.toString()); + } + //----------------------------< internal >---------------------------------- private void previousDocumentNotFound(String prevId, Revision rev) { Index: oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/UnsavedModifications.java =================================================================== --- oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/UnsavedModifications.java (revision 1867174) +++ oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/UnsavedModifications.java (working copy) @@ -206,7 +206,15 @@ NodeDocument.setSweepRevision(rootUpdate, sweepRev); LOG.debug("Updating _sweepRev to {}", sweepRev); } - store.findAndUpdate(NODES, rootUpdate); + // ensure lastRev is not updated by someone else in the meantime + Revision lastRev = Utils.getRootDocument(store).getLastRev().get(rootRev.getClusterId()); + if (lastRev != null) { + NodeDocument.hasLastRev(rootUpdate, lastRev); + } + if (store.findAndUpdate(NODES, rootUpdate) == null) { + throw new DocumentStoreException("Update of root document to _lastRev " + + rootRev + " failed. Detected concurrent update"); + } stats.calls++; map.remove(Path.ROOT, rootRev); LOG.debug("Updated _lastRev to {} on {}", rootRev, Path.ROOT); Index: oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreBackgroundUpdateTest.java =================================================================== --- oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreBackgroundUpdateTest.java (revision 1867175) +++ oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreBackgroundUpdateTest.java (working copy) @@ -31,7 +31,6 @@ import org.apache.jackrabbit.oak.stats.Clock; import org.junit.AfterClass; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; @@ -43,7 +42,6 @@ import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; -@Ignore("OAK-8627") public class DocumentNodeStoreBackgroundUpdateTest { @Rule Index: oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/JournalTest.java =================================================================== --- oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/JournalTest.java (revision 1867174) +++ oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/JournalTest.java (working copy) @@ -422,6 +422,13 @@ ready.countDown(); start.await(); recovery.recover(Lists.newArrayList(x1,z1), c2Id); + } catch (DocumentStoreException e) { + if (e.getMessage().matches("Update of root document to _lastRev .* failed. Detected concurrent update")) { + // we have to accept this exception to happen + end.countDown(); + } else { + exceptions.add(e); + } } catch (Exception e) { exceptions.add(e); } finally { Index: oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/LastRevRecoveryAgentTest.java =================================================================== --- oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/LastRevRecoveryAgentTest.java (revision 1867174) +++ oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/LastRevRecoveryAgentTest.java (working copy) @@ -29,6 +29,7 @@ import org.junit.Test; import static org.apache.jackrabbit.oak.plugins.document.Collection.NODES; +import static org.apache.jackrabbit.oak.plugins.document.TestUtils.disposeQuietly; import static org.apache.jackrabbit.oak.plugins.document.util.Utils.getIdFromPath; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -79,6 +80,9 @@ assertEquals(zlastRev2, getDocument(ds1, "/x/y").getLastRev().get(c2Id)); assertEquals(zlastRev2, getDocument(ds1, "/x").getLastRev().get(c2Id)); assertEquals(zlastRev2, getDocument(ds1, "/").getLastRev().get(c2Id)); + + // dispose ds2 quietly because it may now throw an exception + disposeQuietly(ds2); } //OAK-5337 @@ -152,6 +156,9 @@ assertTrue(ds1.getLastRevRecoveryAgent().isRecoveryNeeded()); ds1.getLastRevRecoveryAgent().performRecoveryIfNeeded(); assertFalse(ds1.getLastRevRecoveryAgent().isRecoveryNeeded()); + + // dispose ds2 quietly because it may now throw an exception + disposeQuietly(ds2); } @Test @@ -193,6 +200,9 @@ b1 = ds1.getRoot().builder(); b1.child("x").child("y").setProperty("p", "v11"); merge(ds1, b1); + + // dispose ds2 quietly because it may now throw an exception + disposeQuietly(ds2); } @Test Index: oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/TestUtils.java =================================================================== --- oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/TestUtils.java (revision 1867174) +++ oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/TestUtils.java (working copy) @@ -124,4 +124,12 @@ } return finalUpdate; } + + public static void disposeQuietly(DocumentNodeStore ns) { + try { + ns.dispose(); + } catch (Exception e) { + // ignore + } + } }