Author: dpfister
Date: Fri Aug 24 15:54:51 2012
New Revision: 1376976
URL: http://svn.apache.org/viewvc?rev=1376976&view=rev
Log:
OAK-276 - potential clash of commit id's after restart
- added test case
Added:
jackrabbit/oak/trunk/oak-mk/src/test/java/org/apache/jackrabbit/mk/MicroKernelImplTest.java
(with props)
Modified:
jackrabbit/oak/trunk/oak-mk/pom.xml
jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/persistence/H2Persistence.java
jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/persistence/InMemPersistence.java
jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/persistence/Persistence.java
jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/store/DefaultRevisionStore.java
Modified: jackrabbit/oak/trunk/oak-mk/pom.xml
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk/pom.xml?rev=1376976&r1=1376975&r2=1376976&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mk/pom.xml (original)
+++ jackrabbit/oak/trunk/oak-mk/pom.xml Fri Aug 24 15:54:51 2012
@@ -134,6 +134,12 @@
<version>1.0.1</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ <version>1.4</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
</project>
Modified: jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/persistence/H2Persistence.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/persistence/H2Persistence.java?rev=1376976&r1=1376975&r2=1376976&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/persistence/H2Persistence.java
(original)
+++ jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/persistence/H2Persistence.java
Fri Aug 24 15:54:51 2012
@@ -70,6 +70,7 @@ public class H2Persistence implements GC
try {
Statement stmt = con.createStatement();
stmt.execute("create table if not exists REVS(ID binary primary key, DATA binary,
TIME timestamp)");
+ stmt.execute("create table if not exists NODES(ID binary primary key, DATA binary,
TIME timestamp)");
stmt.execute("create table if not exists HEAD(ID binary) as select null");
stmt.execute("create sequence if not exists DATASTORE_ID");
/*
@@ -86,7 +87,16 @@ public class H2Persistence implements GC
cp.dispose();
}
- public Id readHead() throws Exception {
+ public Id[] readIds() throws Exception {
+ Id lastCommitId = null;
+ Id headId = readHead();
+ if (headId != null) {
+ lastCommitId = readLastCommitId();
+ }
+ return new Id[] { headId, lastCommitId };
+ }
+
+ private Id readHead() throws Exception {
Connection con = cp.getConnection();
try {
PreparedStatement stmt = con.prepareStatement("select * from HEAD");
@@ -102,6 +112,22 @@ public class H2Persistence implements GC
}
}
+ private Id readLastCommitId() throws Exception {
+ Connection con = cp.getConnection();
+ try {
+ PreparedStatement stmt = con.prepareStatement("select MAX(ID) from REVS");
+ ResultSet rs = stmt.executeQuery();
+ byte[] rawId = null;
+ if (rs.next()) {
+ rawId = rs.getBytes(1);
+ }
+ stmt.close();
+ return rawId == null ? null : new Id(rawId);
+ } finally {
+ con.close();
+ }
+ }
+
public void writeHead(Id id) throws Exception {
Connection con = cp.getConnection();
try {
@@ -118,7 +144,7 @@ public class H2Persistence implements GC
Id id = node.getId();
Connection con = cp.getConnection();
try {
- PreparedStatement stmt = con.prepareStatement("select DATA from REVS where ID
= ?");
+ PreparedStatement stmt = con.prepareStatement("select DATA from NODES where ID
= ?");
try {
stmt.setBytes(1, id.getBytes());
ResultSet rs = stmt.executeQuery();
@@ -148,7 +174,7 @@ public class H2Persistence implements GC
try {
PreparedStatement stmt = con
.prepareStatement(
- "insert into REVS (ID, DATA, TIME) select ?, ?, ? where not exists
(select 1 from REVS where ID = ?)");
+ "insert into NODES (ID, DATA, TIME) select ?, ?, ? where not
exists (select 1 from NODES where ID = ?)");
try {
stmt.setBytes(1, rawId);
stmt.setBytes(2, bytes);
@@ -212,7 +238,7 @@ public class H2Persistence implements GC
public ChildNodeEntriesMap readCNEMap(Id id) throws NotFoundException, Exception {
Connection con = cp.getConnection();
try {
- PreparedStatement stmt = con.prepareStatement("select DATA from REVS where ID
= ?");
+ PreparedStatement stmt = con.prepareStatement("select DATA from NODES where ID
= ?");
try {
stmt.setBytes(1, id.getBytes());
ResultSet rs = stmt.executeQuery();
@@ -241,7 +267,7 @@ public class H2Persistence implements GC
try {
PreparedStatement stmt = con
.prepareStatement(
- "insert into REVS (ID, DATA, TIME) select ?, ?, ? where not exists
(select 1 from REVS where ID = ?)");
+ "insert into NODES (ID, DATA, TIME) select ?, ?, ? where not
exists (select 1 from NODES where ID = ?)");
try {
stmt.setBytes(1, rawId);
stmt.setBytes(2, bytes);
@@ -264,7 +290,7 @@ public class H2Persistence implements GC
@Override
public boolean markCommit(Id id) throws Exception {
- return touch(id, gcStart);
+ return touch("REVS", id, gcStart);
}
@Override
@@ -292,22 +318,23 @@ public class H2Persistence implements GC
@Override
public boolean markNode(Id id) throws Exception {
- return touch(id, gcStart);
+ return touch("NODES", id, gcStart);
}
@Override
public boolean markCNEMap(Id id) throws Exception {
- return touch(id, gcStart);
+ return touch("NODES", id, gcStart);
}
- private boolean touch(Id id, long timeMillis) throws Exception {
+ private boolean touch(String table, Id id, long timeMillis) throws Exception {
Timestamp ts = new Timestamp(timeMillis);
Connection con = cp.getConnection();
try {
- PreparedStatement stmt = con
- .prepareStatement(
- "update REVS set TIME = ? where ID = ? and TIME < ?");
+ PreparedStatement stmt = con.prepareStatement(
+ String.format("update %s set TIME = ? where ID = ? and TIME < ?",
+ table));
+
try {
stmt.setTimestamp(1, ts);
stmt.setBytes(2, id.getBytes());
@@ -324,20 +351,29 @@ public class H2Persistence implements GC
@Override
public int sweep() throws Exception {
Timestamp ts = new Timestamp(gcStart);
+ int swept = 0;
Connection con = cp.getConnection();
try {
- PreparedStatement stmt = con
- .prepareStatement(
- "delete REVS where TIME < ?");
+ PreparedStatement stmt = con.prepareStatement("delete REVS where TIME < ?");
+ try {
+ stmt.setTimestamp(1, ts);
+ swept += stmt.executeUpdate();
+ } finally {
+ stmt.close();
+ }
+
+ stmt = con.prepareStatement("delete NODES where TIME < ?");
+
try {
stmt.setTimestamp(1, ts);
- return stmt.executeUpdate();
+ swept += stmt.executeUpdate();
} finally {
stmt.close();
}
} finally {
con.close();
}
+ return swept;
}
}
\ No newline at end of file
Modified: jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/persistence/InMemPersistence.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/persistence/InMemPersistence.java?rev=1376976&r1=1376975&r2=1376976&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/persistence/InMemPersistence.java
(original)
+++ jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/persistence/InMemPersistence.java
Fri Aug 24 15:54:51 2012
@@ -42,7 +42,6 @@ public class InMemPersistence implements
private final Map<Id, byte[]> objects = Collections.synchronizedMap(new HashMap<Id,
byte[]>());
private final Map<Id, byte[]> marked = Collections.synchronizedMap(new HashMap<Id,
byte[]>());
- private Id head;
private long gcStart;
// TODO: make this configurable
@@ -53,12 +52,13 @@ public class InMemPersistence implements
// nothing to initialize
}
- public Id readHead() {
- return head;
+ @Override
+ public Id[] readIds() throws Exception {
+ return new Id[2];
}
public void writeHead(Id id) {
- head = id;
+
}
public void readNode(StoredNode node) throws NotFoundException, Exception {
Modified: jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/persistence/Persistence.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/persistence/Persistence.java?rev=1376976&r1=1376975&r2=1376976&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/persistence/Persistence.java
(original)
+++ jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/persistence/Persistence.java
Fri Aug 24 15:54:51 2012
@@ -36,7 +36,19 @@ public interface Persistence extends Clo
public void initialize(File homeDir) throws Exception;
- Id readHead() throws Exception;
+ /**
+ * Return an array of ids, where the first is the head id (as stored
+ * with {@link #writeHead(Id)}) and the second is the highest commit
+ * id found or {@code null}.
+ * <p/>
+ * This method is not guaranteed to deliver "live" results, after
+ * something is written to the storage, so it should better be used
+ * once after initialization.
+ *
+ * @return array of ids
+ * @throws Exception if an error occurs
+ */
+ Id[] readIds() throws Exception;
void writeHead(Id id) throws Exception;
Modified: jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/store/DefaultRevisionStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/store/DefaultRevisionStore.java?rev=1376976&r1=1376975&r2=1376976&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/store/DefaultRevisionStore.java
(original)
+++ jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/store/DefaultRevisionStore.java
Fri Aug 24 15:54:51 2012
@@ -134,7 +134,8 @@ public class DefaultRevisionStore extend
cache = Collections.synchronizedMap(SimpleLRUCache.<Id, Object> newInstance(initialCacheSize));
// make sure we've got a HEAD commit
- head = pm.readHead();
+ Id[] ids = pm.readIds();
+ head = ids[0];
if (head == null || head.getBytes().length == 0) {
// assume virgin repository
byte[] rawHead = Id.fromLong(commitCounter.incrementAndGet())
@@ -148,7 +149,11 @@ public class DefaultRevisionStore extend
pm.writeCommit(head, initialCommit);
pm.writeHead(head);
} else {
- commitCounter.set(Long.parseLong(head.toString(), 16));
+ Id lastCommitId = head;
+ if (ids[1] != null && ids[1].compareTo(lastCommitId) > 0) {
+ lastCommitId = ids[1];
+ }
+ commitCounter.set(Long.parseLong(lastCommitId.toString(), 16));
}
if (gcpm != null) {
Added: jackrabbit/oak/trunk/oak-mk/src/test/java/org/apache/jackrabbit/mk/MicroKernelImplTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk/src/test/java/org/apache/jackrabbit/mk/MicroKernelImplTest.java?rev=1376976&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-mk/src/test/java/org/apache/jackrabbit/mk/MicroKernelImplTest.java
(added)
+++ jackrabbit/oak/trunk/oak-mk/src/test/java/org/apache/jackrabbit/mk/MicroKernelImplTest.java
Fri Aug 24 15:54:51 2012
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.mk;
+
+import java.io.File;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.jackrabbit.mk.core.MicroKernelImpl;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+public class MicroKernelImplTest {
+
+ private File homeDir;
+ private MicroKernelImpl mk;
+
+ @Before
+ public void setup() throws Exception {
+ homeDir = new File("target/mk");
+ if (homeDir.exists()) {
+ FileUtils.cleanDirectory(homeDir);
+ }
+ mk = new MicroKernelImpl(homeDir.getPath());
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mk != null) {
+ mk.dispose();
+ }
+ }
+
+ /**
+ * OAK-276: potential clash of commit id's after restart.
+ */
+ @Test
+ public void potentialClashOfCommitIds() {
+ String headRev = mk.commit("/", "+\"a\" : {}", mk.getHeadRevision(), null);
+ String branchRev = mk.branch(mk.getHeadRevision());
+
+ mk.dispose();
+ mk = new MicroKernelImpl(homeDir.getPath());
+ assertEquals("Stored head should be equal", headRev, mk.getHeadRevision());
+
+ headRev = mk.commit("/", "+\"b\" : {}", mk.getHeadRevision(), null);
+ assertFalse("Commit must not have same id as branch", headRev.equals(branchRev));
+ }
+}
Propchange: jackrabbit/oak/trunk/oak-mk/src/test/java/org/apache/jackrabbit/mk/MicroKernelImplTest.java
------------------------------------------------------------------------------
svn:eol-style = native
|