Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/SegmentNodeStore.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/SegmentNodeStore.java (revision 1712586) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/SegmentNodeStore.java (working copy) @@ -93,6 +93,27 @@ private long maximumBackoff = MILLISECONDS.convert(10, SECONDS); + /** + * Sets the number of times checkpoint creation will be tried with an + * optimistic locking behavior + */ + int checkpointsRetryCount = Integer.getInteger( + "oak.checkpoints.retryCount", 5); + + /** + * The value of this flag determines if the checkpoint creation should try a + * forced attempt to create a checkpoint. + */ + boolean checkpointsForceAfterFail = Boolean + .getBoolean("oak.checkpoints.forceAfterFail"); + + /** + * Sets the number of seconds to wait for the attempt to grab the lock to + * create a checkpoint + */ + int checkpointsLockWaitTime = Integer.getInteger( + "oak.checkpoints.lockWaitTime", 60); + @Nonnull public static SegmentNodeStoreBuilder newSegmentNodeStore( @Nonnull SegmentStore store) { @@ -279,57 +300,82 @@ checkArgument(lifetime > 0); checkNotNull(properties); String name = UUID.randomUUID().toString(); - long now = System.currentTimeMillis(); - // try 5 times - for (int i = 0; i < 5; i++) { - if (commitSemaphore.tryAcquire()) { - try { - refreshHead(); + try { + CPCreator cpc = new CPCreator(name, lifetime, properties); + for (int i = 0; i < checkpointsRetryCount; i++) { + if (locked(cpc)) { + return name; + } + } - SegmentNodeState state = head.get(); - SegmentNodeBuilder builder = state.builder(); + if (checkpointsForceAfterFail) { + if (checkpointsRetryCount > 0) { + log.debug( + "Failed to create checkpoint {} in {} tries, will force checkpoint creation.", + name, checkpointsRetryCount); + } + if (locked(cpc, checkpointsLockWaitTime, TimeUnit.SECONDS)) { + return name; + } + } + log.debug("Failed to create checkpoint {}.", name); - NodeBuilder checkpoints = builder.child("checkpoints"); - for (String n : checkpoints.getChildNodeNames()) { - NodeBuilder cp = checkpoints.getChildNode(n); - PropertyState ts = cp.getProperty("timestamp"); - if (ts == null - || ts.getType() != Type.LONG - || now > ts.getValue(Type.LONG)) { - cp.remove(); - } - } + } catch (Exception ex) { + log.error("Failed to create checkpoint {}.", name, ex); + } + return name; + } - NodeBuilder cp = checkpoints.child(name); - cp.setProperty("timestamp", now + lifetime); - cp.setProperty("created", now); + private final class CPCreator implements Callable { - NodeBuilder props = cp.setChildNode("properties"); - for (Entry p : properties.entrySet()) { - props.setProperty(p.getKey(), p.getValue()); - } + private final String name; + private final long lifetime; + private final Map properties; - cp.setChildNode(ROOT, state.getChildNode(ROOT)); + CPCreator(String name, long lifetime, Map properties) { + this.name = name; + this.lifetime = lifetime; + this.properties = properties; + } - SegmentNodeState newState = builder.getNodeState(); - if (store.setHead(state, newState)) { - refreshHead(); - return name; - } else { - log.debug( - "Unable to update the head state for checkpoint {} ({}/5)", - new Object[] { name, i + 1 }); - } + @Override + public Boolean call() { + long now = System.currentTimeMillis(); - } finally { - commitSemaphore.release(); + refreshHead(); + + SegmentNodeState state = head.get(); + SegmentNodeBuilder builder = state.builder(); + + NodeBuilder checkpoints = builder.child("checkpoints"); + for (String n : checkpoints.getChildNodeNames()) { + NodeBuilder cp = checkpoints.getChildNode(n); + PropertyState ts = cp.getProperty("timestamp"); + if (ts == null || ts.getType() != Type.LONG + || now > ts.getValue(Type.LONG)) { + cp.remove(); } } - } - log.debug("Failed to create checkpoint {}", name); - return name; + NodeBuilder cp = checkpoints.child(name); + cp.setProperty("timestamp", now + lifetime); + cp.setProperty("created", now); + + NodeBuilder props = cp.setChildNode("properties"); + for (Entry p : properties.entrySet()) { + props.setProperty(p.getKey(), p.getValue()); + } + cp.setChildNode(ROOT, state.getChildNode(ROOT)); + + SegmentNodeState newState = builder.getNodeState(); + if (store.setHead(state, newState)) { + refreshHead(); + return true; + } else { + return false; + } + } } @Override @Nonnull