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 al...@apache.org on 2015/08/27 14:13:29 UTC

svn commit: r1698132 - in /jackrabbit/oak/trunk/oak-core/src: main/java/org/apache/jackrabbit/oak/plugins/segment/file/ test/java/org/apache/jackrabbit/oak/plugins/segment/file/

Author: alexparvulescu
Date: Thu Aug 27 12:13:28 2015
New Revision: 1698132

URL: http://svn.apache.org/r1698132
Log:
OAK-3294 Read-only live FileStore implementation


Modified:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/BackgroundThread.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/FileStore.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/TarReader.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/TarWriter.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/segment/file/FileStoreTest.java

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/BackgroundThread.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/BackgroundThread.java?rev=1698132&r1=1698131&r2=1698132&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/BackgroundThread.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/BackgroundThread.java Thu Aug 27 12:13:28 2015
@@ -18,6 +18,7 @@ package org.apache.jackrabbit.oak.plugin
 
 import static java.lang.System.currentTimeMillis;
 
+import java.io.Closeable;
 import java.util.Date;
 
 import org.slf4j.Logger;
@@ -30,7 +31,7 @@ import org.slf4j.LoggerFactory;
  * This class also measures and logs the time taken by the Runnable.run()
  * method.
  */
-class BackgroundThread extends Thread {
+class BackgroundThread extends Thread implements Closeable {
 
     /** Logger instance */
     private static final Logger log =
@@ -89,7 +90,8 @@ class BackgroundThread extends Thread {
         trigger(false);
     }
 
-    void close() {
+    @Override
+    public void close() {
         try {
             trigger(true);
             join();

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/FileStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/FileStore.java?rev=1698132&r1=1698131&r2=1698132&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/FileStore.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/FileStore.java Thu Aug 27 12:13:28 2015
@@ -32,6 +32,7 @@ import static org.apache.jackrabbit.oak.
 import static org.apache.jackrabbit.oak.plugins.segment.CompactionMap.sum;
 import static org.apache.jackrabbit.oak.plugins.segment.compaction.CompactionStrategy.NO_COMPACTION;
 
+import java.io.Closeable;
 import java.io.File;
 import java.io.IOException;
 import java.io.RandomAccessFile;
@@ -55,10 +56,11 @@ import javax.annotation.Nonnull;
 
 import com.google.common.base.Stopwatch;
 import com.google.common.collect.Maps;
+
 import org.apache.jackrabbit.oak.api.Blob;
 import org.apache.jackrabbit.oak.plugins.blob.BlobStoreBlob;
-import org.apache.jackrabbit.oak.plugins.segment.Compactor;
 import org.apache.jackrabbit.oak.plugins.segment.CompactionMap;
+import org.apache.jackrabbit.oak.plugins.segment.Compactor;
 import org.apache.jackrabbit.oak.plugins.segment.PersistedCompactionMap;
 import org.apache.jackrabbit.oak.plugins.segment.RecordId;
 import org.apache.jackrabbit.oak.plugins.segment.Segment;
@@ -292,14 +294,14 @@ public class FileStore implements Segmen
         @Nonnull
         public FileStore create() throws IOException {
             return new FileStore(
-                    blobStore, directory, root, maxFileSize, cacheSize, memoryMapping, gcMonitor);
+                    blobStore, directory, root, maxFileSize, cacheSize, memoryMapping, gcMonitor, false);
         }
     }
 
     @Deprecated
     public FileStore(BlobStore blobStore, File directory, int maxFileSizeMB, boolean memoryMapping)
             throws IOException {
-        this(blobStore, directory, EMPTY_NODE, maxFileSizeMB, 0, memoryMapping, GCMonitor.EMPTY);
+        this(blobStore, directory, EMPTY_NODE, maxFileSizeMB, 0, memoryMapping, GCMonitor.EMPTY, false);
     }
 
     @Deprecated
@@ -317,26 +319,33 @@ public class FileStore implements Segmen
     @Deprecated
     public FileStore(File directory, int maxFileSizeMB, int cacheSizeMB,
             boolean memoryMapping) throws IOException {
-        this(null, directory, EMPTY_NODE, maxFileSizeMB, cacheSizeMB, memoryMapping, GCMonitor.EMPTY);
+        this(null, directory, EMPTY_NODE, maxFileSizeMB, cacheSizeMB, memoryMapping, GCMonitor.EMPTY, false);
     }
 
     @Deprecated
     FileStore(File directory, NodeState initial, int maxFileSize) throws IOException {
-        this(null, directory, initial, maxFileSize, -1, MEMORY_MAPPING_DEFAULT, GCMonitor.EMPTY);
+        this(null, directory, initial, maxFileSize, -1, MEMORY_MAPPING_DEFAULT, GCMonitor.EMPTY, false);
     }
 
     @Deprecated
     public FileStore(
             BlobStore blobStore, final File directory, NodeState initial, int maxFileSizeMB,
             int cacheSizeMB, boolean memoryMapping) throws IOException {
-        this(blobStore, directory, initial, maxFileSizeMB, cacheSizeMB, memoryMapping, GCMonitor.EMPTY);
+        this(blobStore, directory, initial, maxFileSizeMB, cacheSizeMB, memoryMapping, GCMonitor.EMPTY, false);
     }
 
     private FileStore(
             BlobStore blobStore, final File directory, NodeState initial, int maxFileSizeMB,
-            int cacheSizeMB, boolean memoryMapping, GCMonitor gcMonitor)
+            int cacheSizeMB, boolean memoryMapping, GCMonitor gcMonitor, boolean readonly)
             throws IOException {
-        checkNotNull(directory).mkdirs();
+
+        if (readonly) {
+            checkNotNull(directory);
+            checkState(directory.exists() && directory.isDirectory());
+        } else {
+            checkNotNull(directory).mkdirs();
+        }
+
         if (cacheSizeMB < 0) {
             this.tracker = new SegmentTracker(this, 0, getVersion());
         } else if (cacheSizeMB > 0) {
@@ -350,26 +359,40 @@ public class FileStore implements Segmen
         this.memoryMapping = memoryMapping;
         this.gcMonitor = gcMonitor;
 
-        journalFile = new RandomAccessFile(new File(directory, JOURNAL_FILE_NAME), "rw");
-        lockFile = new RandomAccessFile(new File(directory, LOCK_FILE_NAME), "rw");
+        if (readonly) {
+            journalFile = new RandomAccessFile(new File(directory,
+                    JOURNAL_FILE_NAME), "r");
+        } else {
+            journalFile = new RandomAccessFile(new File(directory,
+                    JOURNAL_FILE_NAME), "rw");
+        }
 
         Map<Integer, Map<Character, File>> map = collectFiles(directory);
         this.readers = newArrayListWithCapacity(map.size());
         Integer[] indices = map.keySet().toArray(new Integer[map.size()]);
         Arrays.sort(indices);
         for (int i = indices.length - 1; i >= 0; i--) {
-            readers.add(TarReader.open(map.get(indices[i]), memoryMapping));
+            if (!readonly) {
+                readers.add(TarReader.open(map.get(indices[i]), memoryMapping));
+            } else {
+                // only try to read-only recover the latest file as that might
+                // be the *only* one still being accessed by a writer
+                boolean recover = i == indices.length - 1;
+                readers.add(TarReader.openRO(map.get(indices[i]),
+                        memoryMapping, recover));
+            }
         }
 
-        if (indices.length > 0) {
-            this.writeNumber = indices[indices.length - 1] + 1;
-        } else {
-            this.writeNumber = 0;
+        if (!readonly) {
+            if (indices.length > 0) {
+                this.writeNumber = indices[indices.length - 1] + 1;
+            } else {
+                this.writeNumber = 0;
+            }
+            this.writeFile = new File(directory, String.format(
+                    FILE_NAME_FORMAT, writeNumber, "a"));
+            this.writer = new TarWriter(writeFile);
         }
-        this.writeFile = new File(
-                directory,
-                String.format(FILE_NAME_FORMAT, writeNumber, "a"));
-        this.writer = new TarWriter(writeFile);
 
         RecordId id = null;
         JournalReader journalReader = new JournalReader(new File(directory, JOURNAL_FILE_NAME));
@@ -391,7 +414,15 @@ public class FileStore implements Segmen
         }
 
         journalFile.seek(journalFile.length());
-        lock = lockFile.getChannel().lock();
+
+        if (!readonly) {
+            lockFile = new RandomAccessFile(
+                    new File(directory, LOCK_FILE_NAME), "rw");
+            lock = lockFile.getChannel().lock();
+        } else {
+            lockFile = null;
+            lock = null;
+        }
 
         if (id != null) {
             head = new AtomicReference<RecordId>(id);
@@ -404,29 +435,39 @@ public class FileStore implements Segmen
             persistedHead = new AtomicReference<RecordId>(null);
         }
 
-        this.flushThread = new BackgroundThread(
-                "TarMK flush thread [" + directory + "]", 5000, // 5s interval
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        try {
-                            flush();
-                        } catch (IOException e) {
-                            log.warn("Failed to flush the TarMK at" +
-                                    directory, e);
+        if (!readonly) {
+            this.flushThread = new BackgroundThread(
+                    "TarMK flush thread [" + directory + "]", 5000, // 5s interval
+                    new Runnable() {
+                        @Override
+                        public void run() {
+                            try {
+                                flush();
+                            } catch (IOException e) {
+                                log.warn("Failed to flush the TarMK at" +
+                                        directory, e);
+                            }
                         }
-                    }
-                });
-        this.compactionThread = new BackgroundThread(
-                "TarMK compaction thread [" + directory + "]", -1,
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        maybeCompact(true);
-                    }
-                });
+                    });
+            this.compactionThread = new BackgroundThread(
+                    "TarMK compaction thread [" + directory + "]", -1,
+                    new Runnable() {
+                        @Override
+                        public void run() {
+                            maybeCompact(true);
+                        }
+                    });
+        } else {
+            this.flushThread = null;
+            this.compactionThread = null;
+        }
 
-        log.info("TarMK opened: {} (mmap={})", directory, memoryMapping);
+        if (readonly) {
+            log.info("TarMK ReadOnly opened: {} (mmap={})", directory,
+                    memoryMapping);
+        } else {
+            log.info("TarMK opened: {} (mmap={})", directory, memoryMapping);
+        }
     }
 
     public boolean maybeCompact(boolean cleanup) {
@@ -586,7 +627,10 @@ public class FileStore implements Segmen
      * @return number of segments
      */
     private synchronized int count() {
-        int count = writer.count();
+        int count = 0;
+        if (writer != null) {
+            count += writer.count();
+        }
         for (TarReader reader : readers) {
             count += reader.count();
         }
@@ -690,7 +734,8 @@ public class FileStore implements Segmen
                     if (cleaned != null) {
                         list.add(cleaned);
                     }
-                    File file = reader.close();
+                    closeAndLogOnFail(reader);
+                    File file = reader.getFile();
                     gcMonitor.info("TarMK revision cleanup reclaiming {}", file.getName());
                     toBeRemoved.addLast(file);
                 }
@@ -786,10 +831,12 @@ public class FileStore implements Segmen
 
     public synchronized Iterable<SegmentId> getSegmentIds() {
         List<SegmentId> ids = newArrayList();
-        for (UUID uuid : writer.getUUIDs()) {
-            ids.add(tracker.getSegmentId(
-                    uuid.getMostSignificantBits(),
-                    uuid.getLeastSignificantBits()));
+        if (writer != null) {
+            for (UUID uuid : writer.getUUIDs()) {
+                ids.add(tracker.getSegmentId(
+                        uuid.getMostSignificantBits(),
+                        uuid.getLeastSignificantBits()));
+            }
         }
         for (TarReader reader : readers) {
             for (UUID uuid : reader.getUUIDs()) {
@@ -822,25 +869,26 @@ public class FileStore implements Segmen
     public void close() {
         // avoid deadlocks by closing (and joining) the background
         // threads before acquiring the synchronization lock
-        compactionThread.close();
-        flushThread.close();
-
+        closeAndLogOnFail(compactionThread);
+        closeAndLogOnFail(flushThread);
         synchronized (this) {
             try {
                 flush();
 
-                writer.close();
+                closeAndLogOnFail(writer);
                 tracker.getWriter().dropCache();
 
                 List<TarReader> list = readers;
                 readers = newArrayList();
                 for (TarReader reader : list) {
-                    reader.close();
+                    closeAndLogOnFail(reader);
                 }
 
-                lock.release();
-                lockFile.close();
-                journalFile.close();
+                if (lock != null) {
+                    lock.release();
+                }
+                closeAndLogOnFail(lockFile);
+                closeAndLogOnFail(lockFile);
             } catch (IOException e) {
                 throw new RuntimeException(
                         "Failed to close the TarMK at " + directory, e);
@@ -870,9 +918,11 @@ public class FileStore implements Segmen
             }
         }
 
-        synchronized (this) {
-            if (writer.containsEntry(msb, lsb)) {
-                return true;
+        if (writer != null) {
+            synchronized (this) {
+                if (writer.containsEntry(msb, lsb)) {
+                    return true;
+                }
             }
         }
 
@@ -910,14 +960,16 @@ public class FileStore implements Segmen
             }
         }
 
-        synchronized (this) {
-            try {
-                ByteBuffer buffer = writer.readEntry(msb, lsb);
-                if (buffer != null) {
-                    return new Segment(tracker, id, buffer);
+        if (writer != null) {
+            synchronized (this) {
+                try {
+                    ByteBuffer buffer = writer.readEntry(msb, lsb);
+                    if (buffer != null) {
+                        return new Segment(tracker, id, buffer);
+                    }
+                } catch (IOException e) {
+                    log.warn("Failed to read from tar file " + writer, e);
                 }
-            } catch (IOException e) {
-                log.warn("Failed to read from tar file " + writer, e);
             }
         }
 
@@ -1045,9 +1097,8 @@ public class FileStore implements Segmen
      */
     public static class ReadOnlyStore extends FileStore {
         public ReadOnlyStore(File directory) throws IOException {
-            super(directory, 266);
-            super.flushThread.close();
-            super.compactionThread.close();
+            super(null, directory, EMPTY_NODE, -1, 0, MEMORY_MAPPING_DEFAULT,
+                    GCMonitor.EMPTY, true);
         }
 
         /**
@@ -1059,20 +1110,15 @@ public class FileStore implements Segmen
             super.setRevision(revision);
         }
 
-        /**
-         * @return false
-         */
         @Override
         public boolean setHead(SegmentNodeState base, SegmentNodeState head) {
-            return false;
+            throw new UnsupportedOperationException("Read Only Store");
         }
 
-        /**
-         * no-op
-         */
         @Override
-        public synchronized void writeSegment(SegmentId id, byte[] data, int offset, int length) {
-            // nop
+        public synchronized void writeSegment(SegmentId id, byte[] data,
+                int offset, int length) {
+            throw new UnsupportedOperationException("Read Only Store");
         }
 
         /**
@@ -1081,17 +1127,25 @@ public class FileStore implements Segmen
         @Override
         public void flush() { /* nop */ }
 
-        /**
-         * no-op
-         */
         @Override
-        public synchronized void cleanup() { /* nop */ }
+        public synchronized void cleanup() {
+            throw new UnsupportedOperationException("Read Only Store");
+        }
+
+        @Override
+        public void gc() {
+            throw new UnsupportedOperationException("Read Only Store");
+        }
+
+        @Override
+        public void compact() {
+            throw new UnsupportedOperationException("Read Only Store");
+        }
 
-        /**
-         * no-op
-         */
         @Override
-        public void gc() { /* nop */ }
+        public boolean maybeCompact(boolean cleanup) {
+            throw new UnsupportedOperationException("Read Only Store");
+        }
 
     }
 
@@ -1134,6 +1188,17 @@ public class FileStore implements Segmen
         return version;
     }
 
+    private static void closeAndLogOnFail(Closeable closeable) {
+        if (closeable != null) {
+            try {
+                closeable.close();
+            } catch (IOException ioe) {
+                // ignore and log
+                log.error(ioe.getMessage(), ioe);
+            }
+        }
+    }
+
     private static class LoggingGCMonitor implements GCMonitor {
         public GCMonitor delegatee = GCMonitor.EMPTY;
 

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/TarReader.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/TarReader.java?rev=1698132&r1=1698131&r2=1698132&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/TarReader.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/TarReader.java Thu Aug 27 12:13:28 2015
@@ -29,6 +29,7 @@ import static org.apache.jackrabbit.oak.
 import static org.apache.jackrabbit.oak.plugins.segment.SegmentId.isDataSegmentId;
 import static org.apache.jackrabbit.oak.plugins.segment.file.TarWriter.GRAPH_MAGIC;
 
+import java.io.Closeable;
 import java.io.File;
 import java.io.IOException;
 import java.io.RandomAccessFile;
@@ -50,7 +51,7 @@ import org.apache.jackrabbit.oak.plugins
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-class TarReader {
+class TarReader implements Closeable {
 
     /** Logger instance */
     private static final Logger log = LoggerFactory.getLogger(TarReader.class);
@@ -117,23 +118,90 @@ class TarReader {
         log.warn("Could not find a valid tar index in {}, recovering...", list);
         LinkedHashMap<UUID, byte[]> entries = newLinkedHashMap();
         for (File file : sorted.values()) {
-            log.info("Recovering segments from tar file {}", file);
+            collectFileEntries(file, entries, true);
+        }
+
+        // regenerate the first generation based on the recovered data
+        File file = sorted.values().iterator().next();
+        generateTarFile(entries, file);
+
+        reader = openFirstFileWithValidIndex(singletonList(file), memoryMapping);
+        if (reader != null) {
+            return reader;
+        } else {
+            throw new IOException("Failed to open recovered tar file " + file);
+        }
+    }
+
+    static TarReader openRO(Map<Character, File> files, boolean memoryMapping,
+            boolean recover) throws IOException {
+        // for readonly store only try the latest generation of a given
+        // tar file to prevent any rollback or rewrite
+        File file = files.get(Collections.max(files.keySet()));
+
+        TarReader reader = openFirstFileWithValidIndex(singletonList(file),
+                memoryMapping);
+        if (reader != null) {
+            return reader;
+        }
+        if (recover) {
+            log.warn(
+                    "Could not find a valid tar index in {}, recovering read-only",
+                    file);
+            // collecting the entries (without touching the original file) and
+            // writing them into an artificial tar file '.ro.bak'
+            LinkedHashMap<UUID, byte[]> entries = newLinkedHashMap();
+            collectFileEntries(file, entries, false);
+            file = findAvailGen(file, ".ro.bak");
+            generateTarFile(entries, file);
+            reader = openFirstFileWithValidIndex(singletonList(file),
+                    memoryMapping);
+            if (reader != null) {
+                return reader;
+            }
+        }
+
+        throw new IOException("Failed to open tar file " + file);
+    }
+
+    /**
+     * Collects all entries from the given file and optionally backs-up the
+     * file, by renaming it to a ".bak" extension
+     * 
+     * @param file
+     * @param entries
+     * @param backup
+     * @throws IOException
+     */
+    private static void collectFileEntries(File file,
+            LinkedHashMap<UUID, byte[]> entries, boolean backup)
+            throws IOException {
+        log.info("Recovering segments from tar file {}", file);
+        try {
+            RandomAccessFile access = new RandomAccessFile(file, "r");
             try {
-                RandomAccessFile access = new RandomAccessFile(file, "r");
-                try {
-                    recoverEntries(file, access, entries);
-                } finally {
-                    access.close();
-                }
-            } catch (IOException e) {
-                log.warn("Could not read tar file " + file + ", skipping...", e);
+                recoverEntries(file, access, entries);
+            } finally {
+                access.close();
             }
+        } catch (IOException e) {
+            log.warn("Could not read tar file " + file + ", skipping...", e);
+        }
 
+        if (backup) {
             backupSafely(file);
         }
+    }
 
-        // regenerate the first generation based on the recovered data
-        File file = sorted.values().iterator().next();
+    /**
+     * Regenerates a tar file from a list of entries.
+     * 
+     * @param entries
+     * @param file
+     * @throws IOException
+     */
+    private static void generateTarFile(LinkedHashMap<UUID, byte[]> entries,
+            File file) throws IOException {
         log.info("Regenerating tar file " + file);
         TarWriter writer = new TarWriter(file);
         for (Map.Entry<UUID, byte[]> entry : entries.entrySet()) {
@@ -145,13 +213,6 @@ class TarReader {
                     data, 0, data.length);
         }
         writer.close();
-
-        reader = openFirstFileWithValidIndex(singletonList(file), memoryMapping);
-        if (reader != null) {
-            return reader;
-        } else {
-            throw new IOException("Failed to open recovered tar file " + file);
-        }
     }
 
     /**
@@ -163,14 +224,7 @@ class TarReader {
      * @throws IOException
      */
     private static void backupSafely(File file) throws IOException {
-        File parent = file.getParentFile();
-        String name = file.getName();
-
-        File backup = new File(parent, name + ".bak");
-        for (int i = 2; backup.exists(); i++) {
-            backup = new File(parent, name + "." + i + ".bak");
-        }
-
+        File backup = findAvailGen(file, ".bak");
         log.info("Backing up " + file + " to " + backup.getName());
         if (!file.renameTo(backup)) {
             log.warn("Renaming failed, so using copy to backup {}", file);
@@ -182,6 +236,23 @@ class TarReader {
         }
     }
 
+    /**
+     * Fine next available generation number so that a generated file doesn't
+     * overwrite another existing file.
+     * 
+     * @param file
+     * @throws IOException
+     */
+    private static File findAvailGen(File file, String ext) {
+        File parent = file.getParentFile();
+        String name = file.getName();
+        File backup = new File(parent, name + ext);
+        for (int i = 2; backup.exists(); i++) {
+            backup = new File(parent, name + "." + i + ext);
+        }
+        return backup;
+    }
+
     private static TarReader openFirstFileWithValidIndex(List<File> files, boolean memoryMapping) {
         for (File file : files) {
             String name = file.getName();
@@ -705,10 +776,10 @@ class TarReader {
         return closed;
     }
 
-    File close() throws IOException {
+    @Override
+    public void close() throws IOException {
         closed = true;
         access.close();
-        return file;
     }
 
     //-----------------------------------------------------------< private >--

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/TarWriter.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/TarWriter.java?rev=1698132&r1=1698131&r2=1698132&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/TarWriter.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/TarWriter.java Thu Aug 27 12:13:28 2015
@@ -29,6 +29,7 @@ import static com.google.common.collect.
 import static org.apache.jackrabbit.oak.plugins.segment.Segment.REF_COUNT_OFFSET;
 import static org.apache.jackrabbit.oak.plugins.segment.SegmentId.isDataSegmentId;
 
+import java.io.Closeable;
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.IOException;
@@ -51,7 +52,7 @@ import org.slf4j.LoggerFactory;
  * A writer for tar files. It is also used to read entries while the file is
  * still open.
  */
-class TarWriter {
+class TarWriter implements Closeable {
 
     /** Logger instance */
     private static final Logger log = LoggerFactory.getLogger(TarWriter.class);
@@ -287,7 +288,8 @@ class TarWriter {
      *
      * @throws IOException if the tar file could not be closed
      */
-    void close() throws IOException {
+    @Override
+    public void close() throws IOException {
         // Mark this writer as closed. Note that we only need to synchronize
         // this part, as no other synchronized methods should get invoked
         // once close() has been initiated (see related checkState calls).

Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/segment/file/FileStoreTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/segment/file/FileStoreTest.java?rev=1698132&r1=1698131&r2=1698132&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/segment/file/FileStoreTest.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/segment/file/FileStoreTest.java Thu Aug 27 12:13:28 2015
@@ -43,6 +43,7 @@ import org.apache.jackrabbit.oak.plugins
 import org.apache.jackrabbit.oak.plugins.segment.SegmentNodeBuilder;
 import org.apache.jackrabbit.oak.plugins.segment.SegmentNodeState;
 import org.apache.jackrabbit.oak.plugins.segment.SegmentWriter;
+import org.apache.jackrabbit.oak.plugins.segment.file.FileStore.ReadOnlyStore;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -255,5 +256,27 @@ public class FileStoreTest {
             store.close();
         }
     }
+
+    @Test
+    public void nonBlockingROStore() throws IOException {
+        FileStore store = new FileStore(directory, 1, false);
+        store.flush(); // first 1kB
+        SegmentNodeState base = store.getHead();
+        SegmentNodeBuilder builder = base.builder();
+        builder.setProperty("step", "a");
+        store.setHead(base, builder.getNodeState());
+        store.flush(); // second 1kB
+
+        ReadOnlyStore ro = null;
+        try {
+            ro = new ReadOnlyStore(directory);
+            assertEquals(store.getHead(), ro.getHead());
+        } finally {
+            if (ro != null) {
+                ro.close();
+            }
+            store.close();
+        }
+    }
 
 }