You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by am...@apache.org on 2017/10/18 09:41:47 UTC
svn commit: r1812483 - in /jackrabbit/oak/trunk/oak-blob-plugins/src:
main/java/org/apache/jackrabbit/oak/plugins/blob/datastore/
test/java/org/apache/jackrabbit/oak/plugins/blob/datastore/
Author: amitj
Date: Wed Oct 18 09:41:47 2017
New Revision: 1812483
URL: http://svn.apache.org/viewvc?rev=1812483&view=rev
Log:
OAK-6827: Consistency check fails with active deletions
Active deleted tracker to keep track of deleted files and provides methods to filter and reconcile
Added:
jackrabbit/oak/trunk/oak-blob-plugins/src/test/java/org/apache/jackrabbit/oak/plugins/blob/datastore/ActiveDeletionTrackerStoreTest.java (with props)
Modified:
jackrabbit/oak/trunk/oak-blob-plugins/src/main/java/org/apache/jackrabbit/oak/plugins/blob/datastore/BlobIdTracker.java
jackrabbit/oak/trunk/oak-blob-plugins/src/main/java/org/apache/jackrabbit/oak/plugins/blob/datastore/BlobTracker.java
Modified: jackrabbit/oak/trunk/oak-blob-plugins/src/main/java/org/apache/jackrabbit/oak/plugins/blob/datastore/BlobIdTracker.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-blob-plugins/src/main/java/org/apache/jackrabbit/oak/plugins/blob/datastore/BlobIdTracker.java?rev=1812483&r1=1812482&r2=1812483&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-blob-plugins/src/main/java/org/apache/jackrabbit/oak/plugins/blob/datastore/BlobIdTracker.java (original)
+++ jackrabbit/oak/trunk/oak-blob-plugins/src/main/java/org/apache/jackrabbit/oak/plugins/blob/datastore/BlobIdTracker.java Wed Oct 18 09:41:47 2017
@@ -29,13 +29,14 @@ import java.util.concurrent.ScheduledExe
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
+import javax.annotation.Nullable;
+
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Stopwatch;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import com.google.common.io.Files;
-import org.apache.commons.io.FileUtils;
import org.apache.jackrabbit.core.data.DataRecord;
import org.apache.jackrabbit.oak.commons.FileIOUtils.FileLineDifferenceIterator;
import org.apache.jackrabbit.oak.commons.concurrent.ExecutorCloser;
@@ -62,6 +63,7 @@ import static org.apache.commons.io.File
import static org.apache.commons.io.FileUtils.forceDelete;
import static org.apache.commons.io.FileUtils.forceMkdir;
import static org.apache.commons.io.FileUtils.lineIterator;
+import static org.apache.commons.io.FileUtils.touch;
import static org.apache.commons.io.FilenameUtils.concat;
import static org.apache.commons.io.FilenameUtils.removeExtension;
import static org.apache.commons.io.IOUtils.closeQuietly;
@@ -84,9 +86,9 @@ public class BlobIdTracker implements Cl
/**
* System property to skip tracker. If set will skip:
- * * Snapshots (No-op)
- * * Retrieve (return empty)
- * * Add (No-op)
+ * * Snapshots (No-op)
+ * * Retrieve (return empty)
+ * * Add (No-op)
*/
private final boolean SKIP_TRACKER = Boolean.getBoolean("oak.datastore.skipTracker");
@@ -100,6 +102,7 @@ public class BlobIdTracker implements Cl
private final SharedDataStore datastore;
private final long snapshotInterval;
+ private final ActiveDeletionTracker deleteTracker;
protected BlobIdStore store;
@@ -109,15 +112,14 @@ public class BlobIdTracker implements Cl
private File rootDir;
- public BlobIdTracker(String path, String repositoryId,
- long snapshotIntervalSecs, SharedDataStore datastore) throws IOException {
- this(path, repositoryId, newSingleThreadScheduledExecutor(),
- snapshotIntervalSecs, snapshotIntervalSecs, datastore);
+ public BlobIdTracker(String path, String repositoryId, long snapshotIntervalSecs, SharedDataStore datastore)
+ throws IOException {
+ this(path, repositoryId, newSingleThreadScheduledExecutor(), snapshotIntervalSecs, snapshotIntervalSecs,
+ datastore);
}
- public BlobIdTracker(String path, String repositoryId, ScheduledExecutorService scheduler,
- long snapshotDelaySecs, long snapshotIntervalSecs, SharedDataStore datastore)
- throws IOException {
+ public BlobIdTracker(String path, String repositoryId, ScheduledExecutorService scheduler, long snapshotDelaySecs,
+ long snapshotIntervalSecs, SharedDataStore datastore) throws IOException {
String root = concat(path, datastoreMeta);
this.rootDir = new File(root);
this.datastore = datastore;
@@ -127,9 +129,9 @@ public class BlobIdTracker implements Cl
forceMkdir(rootDir);
prefix = fileNamePrefix + "-" + repositoryId;
this.store = new BlobIdStore(rootDir, prefix);
- scheduler.scheduleAtFixedRate(new SnapshotJob(),
- SECONDS.toMillis(snapshotDelaySecs),
+ scheduler.scheduleAtFixedRate(new SnapshotJob(), SECONDS.toMillis(snapshotDelaySecs),
SECONDS.toMillis(snapshotIntervalSecs), MILLISECONDS);
+ this.deleteTracker = new ActiveDeletionTracker(rootDir, prefix);
} catch (IOException e) {
LOG.error("Error initializing blob tracker", e);
close();
@@ -137,34 +139,42 @@ public class BlobIdTracker implements Cl
}
}
- @Override
- public void remove(File recs) throws IOException {
+ public ActiveDeletionTracker getDeleteTracker() {
+ return deleteTracker;
+ }
+
+ @Override public void remove(File recs, Options options) throws IOException {
+ if (options == Options.ACTIVE_DELETION) {
+ get();
+ deleteTracker.track(recs);
+ }
+ store.removeRecords(recs);
+ snapshot(true);
+ }
+
+ @Override public void remove(File recs) throws IOException {
store.removeRecords(recs);
snapshot(true);
}
- @Override
- public void remove(Iterator<String> recs) throws IOException {
+ @Override public void remove(Iterator<String> recs) throws IOException {
store.removeRecords(recs);
snapshot(true);
}
- @Override
- public void add(String id) throws IOException {
+ @Override public void add(String id) throws IOException {
if (!SKIP_TRACKER) {
store.addRecord(id);
}
}
- @Override
- public void add(Iterator<String> recs) throws IOException {
+ @Override public void add(Iterator<String> recs) throws IOException {
if (!SKIP_TRACKER) {
store.addRecords(recs);
}
}
- @Override
- public void add(File recs) throws IOException {
+ @Override public void add(File recs) throws IOException {
if (!SKIP_TRACKER) {
store.addRecords(recs);
}
@@ -175,14 +185,13 @@ public class BlobIdTracker implements Cl
* them to the local store and then returns an iterator over it.
* This way the ids returned are as recent as the snapshots taken on all
* instances/repositories connected to the DataStore.
- *
+ * <p>
* The iterator returned ia a Closeable instance and should be closed by calling #close().
*
* @return iterator over all the blob ids available
* @throws IOException
*/
- @Override
- public Iterator<String> get() throws IOException {
+ @Override public Iterator<String> get() throws IOException {
try {
if (!SKIP_TRACKER) {
globalMerge();
@@ -195,8 +204,7 @@ public class BlobIdTracker implements Cl
}
}
- @Override
- public File get(String path) throws IOException {
+ @Override public File get(String path) throws IOException {
if (!SKIP_TRACKER) {
globalMerge();
return store.getRecords(path);
@@ -218,31 +226,26 @@ public class BlobIdTracker implements Cl
Iterable<DataRecord> refRecords = datastore.getAllMetadataRecords(fileNamePrefix);
// Download all the corresponding files for the records
- List<File> refFiles = newArrayList(
- transform(refRecords,
- new Function<DataRecord, File>() {
- @Override
- public File apply(DataRecord input) {
- InputStream inputStream = null;
- try {
- inputStream = input.getStream();
- return copy(inputStream);
- } catch (Exception e) {
- LOG.warn("Error copying data store file locally {}",
- input.getIdentifier(), e);
- } finally {
- closeQuietly(inputStream);
- }
- return null;
- }
- }));
+ List<File> refFiles = newArrayList(transform(refRecords, new Function<DataRecord, File>() {
+ @Override public File apply(DataRecord input) {
+ InputStream inputStream = null;
+ try {
+ inputStream = input.getStream();
+ return copy(inputStream);
+ } catch (Exception e) {
+ LOG.warn("Error copying data store file locally {}", input.getIdentifier(), e);
+ } finally {
+ closeQuietly(inputStream);
+ }
+ return null;
+ }
+ }));
LOG.info("Retrieved all blob id files in [{}]", watch.elapsed(TimeUnit.MILLISECONDS));
// Merge all the downloaded files in to the local store
watch = Stopwatch.createStarted();
store.merge(refFiles, true);
- LOG.info("Merged all retrieved blob id files in [{}]",
- watch.elapsed(TimeUnit.MILLISECONDS));
+ LOG.info("Merged all retrieved blob id files in [{}]", watch.elapsed(TimeUnit.MILLISECONDS));
// Remove all the data store records as they have been merged
watch = Stopwatch.createStarted();
@@ -250,8 +253,7 @@ public class BlobIdTracker implements Cl
datastore.deleteMetadataRecord(rec.getIdentifier().toString());
LOG.debug("Deleted metadata record {}", rec.getIdentifier().toString());
}
- LOG.info("Deleted all blob id metadata files in [{}]",
- watch.elapsed(TimeUnit.MILLISECONDS));
+ LOG.info("Deleted all blob id metadata files in [{}]", watch.elapsed(TimeUnit.MILLISECONDS));
} catch (IOException e) {
LOG.error("Error in merging blob records iterator from the data store", e);
throw e;
@@ -280,17 +282,15 @@ public class BlobIdTracker implements Cl
watch = Stopwatch.createStarted();
File recs = store.getBlobRecordsFile();
- datastore.addMetadataRecord(recs,
- (prefix + instanceId + System.currentTimeMillis() + mergedFileSuffix));
- LOG.info("Added blob id metadata record in DataStore in [{}]",
- watch.elapsed(TimeUnit.MILLISECONDS));
+ datastore.addMetadataRecord(recs, (prefix + instanceId + System.currentTimeMillis() + mergedFileSuffix));
+ LOG.info("Added blob id metadata record in DataStore in [{}]", watch.elapsed(TimeUnit.MILLISECONDS));
try {
forceDelete(recs);
LOG.info("Deleted blob record file after snapshot and upload {}", recs);
// Update the timestamp for the snapshot marker
- FileUtils.touch(getSnapshotMarkerFile());
+ touch(getSnapshotMarkerFile());
LOG.info("Updated snapshot marker");
} catch (IOException e) {
LOG.debug("Failed to in cleaning up {}", recs, e);
@@ -312,13 +312,122 @@ public class BlobIdTracker implements Cl
*
* @throws IOException
*/
- @Override
- public void close() throws IOException {
+ @Override public void close() throws IOException {
store.close();
new ExecutorCloser(scheduler).close();
}
/**
+ * Tracking any active deletions store for managing the blob reference
+ */
+ public static class ActiveDeletionTracker {
+ /* Suffix for tracking file */
+ private static final String DEL_SUFFIX = ".del";
+
+ /* deletion tracking file */
+ private File delFile;
+
+ public static final String DELIM = ",";
+
+ /* Lock for operations on the active deletions file */
+ private final ReentrantLock lock;
+
+ private static final Function<String, String> transformer = new Function<String, String>() {
+ @Nullable
+ @Override
+ public String apply(@Nullable String input) {
+ if (input != null) {
+ return input.split(DELIM)[0];
+ }
+ return "";
+ }};
+
+ ActiveDeletionTracker(File rootDir, String prefix) throws IOException {
+ delFile = new File(rootDir, prefix + DEL_SUFFIX);
+ touch(delFile);
+ lock = new ReentrantLock();
+ }
+
+ /**
+ * Adds the ids in the file provided to the tracked deletions.
+ * @param recs the deleted ids to track
+ */
+ public void track(File recs) throws IOException {
+ lock.lock();
+ try {
+ append(Lists.newArrayList(recs), delFile, false);
+ sort(delFile);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public File retrieve(String path) throws IOException {
+ File copiedRecsFile = new File(path);
+ try {
+ copyFile(delFile, copiedRecsFile);
+ return copiedRecsFile;
+ } catch (IOException e) {
+ LOG.error("Error in retrieving active deletions file", e);
+ throw e;
+ }
+ }
+
+ /**
+ * Remove ids given by the file in parameter from the deletions being tracked.
+ *
+ * @param recs the sorted file containing ids to be removed from tracker
+ */
+ public void reconcile(File recs) throws IOException {
+ lock.lock();
+ try {
+ // Remove and spool the remaining ids into a temp file
+ File toBeRemoved = createTempFile("toBeRemoved", null);
+ File removed = createTempFile("removed", null);
+
+ FileLineDifferenceIterator toBeRemovedIterator = null;
+ FileLineDifferenceIterator removeIterator = null;
+ try {
+ // Gather all records which are not required to be tracked anymore
+ toBeRemovedIterator = new FileLineDifferenceIterator(recs, delFile, null);
+ writeStrings(toBeRemovedIterator, toBeRemoved, false);
+
+ // Remove records not to be tracked
+ removeIterator = new FileLineDifferenceIterator(toBeRemoved, delFile, null);
+ writeStrings(removeIterator, removed, false);
+ } finally {
+ if (toBeRemovedIterator != null) {
+ toBeRemovedIterator.close();
+ }
+
+ if (removeIterator != null) {
+ removeIterator.close();
+ }
+
+ if (toBeRemoved != null) {
+ toBeRemoved.delete();
+ }
+ }
+
+ move(removed, delFile);
+ LOG.trace("removed active delete records");
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Return any ids not existing in the deletions being tracked from the ids in file parameter.
+ *
+ * @param recs the file to search for ids existing in the deletions here
+ * @return
+ */
+ public Iterator<String> filter(File recs) throws IOException {
+ return new FileLineDifferenceIterator(delFile, recs, transformer);
+ }
+ }
+
+ /**
* Local store for managing the blob reference
*/
static class BlobIdStore implements Closeable {
Modified: jackrabbit/oak/trunk/oak-blob-plugins/src/main/java/org/apache/jackrabbit/oak/plugins/blob/datastore/BlobTracker.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-blob-plugins/src/main/java/org/apache/jackrabbit/oak/plugins/blob/datastore/BlobTracker.java?rev=1812483&r1=1812482&r2=1812483&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-blob-plugins/src/main/java/org/apache/jackrabbit/oak/plugins/blob/datastore/BlobTracker.java (original)
+++ jackrabbit/oak/trunk/oak-blob-plugins/src/main/java/org/apache/jackrabbit/oak/plugins/blob/datastore/BlobTracker.java Wed Oct 18 09:41:47 2017
@@ -66,6 +66,13 @@ public interface BlobTracker extends Clo
void remove(File recs) throws IOException;
/**
+ * Remove the ids in the given file and deletes the file.
+ *
+ * @param recs
+ * @throws IOException
+ */
+ void remove(File recs, Options options) throws IOException;
+ /**
* Fetches an iterator of records available.
*
* @return
@@ -81,4 +88,6 @@ public interface BlobTracker extends Clo
* @throws IOException
*/
File get(String path) throws IOException;
+
+ enum Options {DEFAULT, ACTIVE_DELETION}
}
Added: jackrabbit/oak/trunk/oak-blob-plugins/src/test/java/org/apache/jackrabbit/oak/plugins/blob/datastore/ActiveDeletionTrackerStoreTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-blob-plugins/src/test/java/org/apache/jackrabbit/oak/plugins/blob/datastore/ActiveDeletionTrackerStoreTest.java?rev=1812483&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-blob-plugins/src/test/java/org/apache/jackrabbit/oak/plugins/blob/datastore/ActiveDeletionTrackerStoreTest.java (added)
+++ jackrabbit/oak/trunk/oak-blob-plugins/src/test/java/org/apache/jackrabbit/oak/plugins/blob/datastore/ActiveDeletionTrackerStoreTest.java Wed Oct 18 09:41:47 2017
@@ -0,0 +1,208 @@
+/*
+ * 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.oak.plugins.blob.datastore;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import org.apache.jackrabbit.oak.commons.FileIOUtils;
+import org.apache.jackrabbit.oak.plugins.blob.SharedDataStore;
+import org.apache.jackrabbit.oak.plugins.blob.datastore.BlobIdTracker.ActiveDeletionTracker;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static java.lang.String.valueOf;
+import static java.util.UUID.randomUUID;
+import static org.apache.jackrabbit.oak.commons.FileIOUtils.readStringsAsSet;
+import static org.apache.jackrabbit.oak.plugins.blob.datastore.DataStoreUtils.getBlobStore;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeNoException;
+import static org.junit.Assume.assumeThat;
+
+/**
+ * Test for BlobIdTracker.ActiveDeletionTracker to test tracking removed blob ids.
+ */
+public class ActiveDeletionTrackerStoreTest {
+ private static final Logger log = LoggerFactory.getLogger(ActiveDeletionTrackerStoreTest.class);
+
+ File root;
+ SharedDataStore dataStore;
+ ActiveDeletionTracker tracker;
+
+ @Rule
+ public TemporaryFolder folder = new TemporaryFolder(new File("target"));
+ private String repoId;
+
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+ try {
+ assumeThat(getBlobStore(), instanceOf(SharedDataStore.class));
+ } catch (Exception e) {
+ assumeNoException(e);
+ }
+ }
+
+ @Before
+ public void setup() throws Exception {
+ this.root = folder.newFolder();
+ if (dataStore == null) {
+ dataStore = getBlobStore(root);
+ }
+ this.repoId = randomUUID().toString();
+ this.tracker = initTracker();
+ }
+
+ private ActiveDeletionTracker initTracker() throws IOException {
+ return new ActiveDeletionTracker(root, repoId);
+ }
+
+ @After
+ public void tearDown() throws IOException {
+ folder.delete();
+ }
+
+ @Test
+ public void track() throws Exception {
+ Set<String> initAdd = add(tracker, range(0, 20), folder);
+ Set<String> retrieved = retrieve(tracker, folder);
+
+ assertEquals("Incorrect elements after add snapshot", initAdd, retrieved);
+ }
+
+ @Test
+ public void filter() throws Exception {
+ add(tracker, range(0, 20), folder);
+ File toFilter = create(range(7, 10), folder);
+ Iterator<String> filtered = tracker.filter(toFilter);
+
+ assertTrue("More elements after filtering", Lists.newArrayList(filtered).isEmpty());
+ }
+
+ @Test
+ public void noFilter() throws Exception {
+ add(tracker, range(5, 20), folder);
+ List<String> toFilter = combine(range(7, 10), range(0, 4));
+ File toFilterFile = create(toFilter, folder);
+ Iterator<String> filtered = tracker.filter(toFilterFile);
+
+ assertEquals("Incorrect elements after filtering", range(0, 4), Lists.newArrayList(filtered));
+ }
+
+ @Test
+ public void filterWithExtraElements() throws Exception {
+ add(tracker, range(5, 20), folder);
+ List<String> toFilter = combine(combine(range(7, 10), range(0, 4)), range(21, 25));
+ File toFilterFile = create(toFilter, folder);
+ Iterator<String> filtered = tracker.filter(toFilterFile);
+
+ assertEquals("Incorrect elements after filtering",
+ combine(range(0, 4), range(21, 25)), Lists.newArrayList(filtered));
+ }
+
+ @Test
+ public void reconcileNone() throws Exception {
+ Set<String> initAdd = add(tracker, range(0, 20), folder);
+ List toReconcile = Lists.newArrayList();
+
+ File toFilter = create(toReconcile, folder);
+
+ tracker.reconcile(toFilter);
+ Set<String> retrieved = retrieve(tracker, folder);
+
+ assertEquals("Incorrect elements with after reconciliation", Sets.newHashSet(toReconcile), retrieved);
+ }
+
+ @Test
+ public void reconcile() throws Exception {
+ Set<String> initAdd = add(tracker, range(0, 20), folder);
+ List<String> toReconcile = combine(range(7, 10), range(1, 4));
+
+ File toFilter = create(toReconcile, folder);
+
+ tracker.reconcile(toFilter);
+ Set<String> retrieved = retrieve(tracker, folder);
+
+ assertEquals("Incorrect elements with after reconciliation", Sets.newHashSet(toReconcile), retrieved);
+ }
+
+ @Test
+ public void addCloseRestart() throws IOException {
+ Set<String> initAdd = add(tracker, range(0, 10), folder);
+ this.tracker = initTracker();
+ Set<String> retrieved = retrieve(tracker, folder);
+ assertEquals("Incorrect elements after safe restart", initAdd, retrieved);
+ }
+
+ private static Set<String> add(ActiveDeletionTracker store, List<String> ints, TemporaryFolder folder) throws IOException {
+ File f = folder.newFile();
+ FileIOUtils.writeStrings(ints.iterator(), f, false);
+ store.track(f);
+ return Sets.newHashSet(ints);
+ }
+
+ private static File create(List<String> ints, TemporaryFolder folder) throws IOException {
+ File f = folder.newFile();
+ FileIOUtils.writeStrings(ints.iterator(), f, false);
+ return f;
+ }
+
+ private static Set<String> retrieve(ActiveDeletionTracker store, TemporaryFolder folder) throws IOException {
+ File f = folder.newFile();
+ Set<String> retrieved = readStringsAsSet(
+ new FileInputStream(store.retrieve(f.getAbsolutePath())), false);
+ return retrieved;
+ }
+
+ private static List<String> range(int min, int max) {
+ List<String> list = newArrayList();
+ for (int i = min; i <= max; i++) {
+ list.add(Strings.padStart(valueOf(i), 2, '0'));
+ }
+ return list;
+ }
+
+ private static List<String> combine(List<String> first, List<String> second) {
+ first.addAll(second);
+ Collections.sort(first, new Comparator<String>() {
+ @Override public int compare(String s1, String s2) {
+ return Integer.valueOf(s1).compareTo(Integer.valueOf(s2));
+ }
+ });
+ return first;
+ }
+}
+
Propchange: jackrabbit/oak/trunk/oak-blob-plugins/src/test/java/org/apache/jackrabbit/oak/plugins/blob/datastore/ActiveDeletionTrackerStoreTest.java
------------------------------------------------------------------------------
svn:eol-style = native