Author: mreutegg
Date: Mon Nov 27 12:42:56 2017
New Revision: 1816446

URL: http://svn.apache.org/viewvc?rev=1816446&view=rev
Log:
Fix end of line

Modified:
    
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/directory/ActiveDeletedBlobCollectorFactory.java

Modified: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/directory/ActiveDeletedBlobCollectorFactory.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/directory/ActiveDeletedBlobCollectorFactory.java?rev=1816446&r1=1816445&r2=1816446&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/directory/ActiveDeletedBlobCollectorFactory.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/directory/ActiveDeletedBlobCollectorFactory.java
 Mon Nov 27 12:42:56 2017
@@ -1,499 +1,499 @@
-/*
- * 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.index.lucene.directory;
-
-import com.google.common.base.Charsets;
-import com.google.common.base.Joiner;
-import com.google.common.collect.Lists;
-import com.google.common.io.Closeables;
-import com.google.common.io.Files;
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.io.filefilter.IOFileFilter;
-import org.apache.commons.io.filefilter.RegexFileFilter;
-import org.apache.jackrabbit.core.data.DataStoreException;
-import org.apache.jackrabbit.oak.commons.FileIOUtils;
-import org.apache.jackrabbit.oak.commons.benchmark.PerfLogger;
-import org.apache.jackrabbit.oak.plugins.blob.BlobTrackingStore;
-import org.apache.jackrabbit.oak.plugins.blob.datastore.BlobTracker;
-import org.apache.jackrabbit.oak.plugins.blob.datastore.BlobTracker.Options;
-import org.apache.jackrabbit.oak.plugins.index.IndexCommitCallback;
-import org.apache.jackrabbit.oak.spi.blob.GarbageCollectableBlobStore;
-import org.apache.jackrabbit.oak.stats.Clock;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.annotation.Nonnull;
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Properties;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import static com.google.common.base.Preconditions.checkArgument;
-
-public class ActiveDeletedBlobCollectorFactory {
-    public interface ActiveDeletedBlobCollector {
-        /**
-         * @return an instance of {@link BlobDeletionCallback} that can be 
used to track deleted blobs
-         */
-        BlobDeletionCallback getBlobDeletionCallback();
-
-        void purgeBlobsDeleted(long before, GarbageCollectableBlobStore 
blobStore);
-
-        void cancelBlobCollection();
-    }
-
-    public static ActiveDeletedBlobCollector NOOP = new 
ActiveDeletedBlobCollector() {
-        @Override
-        public BlobDeletionCallback getBlobDeletionCallback() {
-            return BlobDeletionCallback.NOOP;
-        }
-
-        @Override
-        public void purgeBlobsDeleted(long before, GarbageCollectableBlobStore 
blobStore) {
-
-        }
-
-        @Override
-        public void cancelBlobCollection() {
-
-        }
-    };
-
-    public interface BlobDeletionCallback extends IndexCommitCallback {
-        /**
-         * Tracks deleted blobs. From the pov of this interface, blobId is an 
opaque string
-         * that needs to be tracked.
-         * @param blobId blobId representing deleted blob. In theory, it has 
nothing to do with
-         *               blobs though.
-         * @param ids Information that can be useful for debugging - this is 
not used for purging
-         *            blobs.
-         */
-        void deleted(String blobId, Iterable<String> ids);
-        BlobDeletionCallback NOOP = new BlobDeletionCallback() {
-            @Override
-            public void deleted(String blobId, Iterable<String> ids) {
-            }
-
-            @Override
-            public void commitProgress(IndexProgress indexProgress) {
-            }
-        };
-    }
-
-    public static ActiveDeletedBlobCollector newInstance(@Nonnull File 
rootDirectory,
-                                                         ExecutorService 
executorService) {
-        try {
-            FileUtils.forceMkdir(rootDirectory);
-        } catch (IOException ioe) {
-            ActiveDeletedBlobCollectorImpl.LOG.warn("Disabling active blob 
collector as we couldn't not create folder: "
-                    + rootDirectory, ioe);
-            return NOOP;
-        }
-        if(!rootDirectory.canRead() || !rootDirectory.canWrite() || 
!rootDirectory.canExecute()) {
-            ActiveDeletedBlobCollectorImpl.LOG.warn("Insufficient access in 
directory - {}. Disabling active blob collector",
-                    rootDirectory);
-            return NOOP;
-        }
-        return new ActiveDeletedBlobCollectorImpl(rootDirectory, 
executorService);
-    }
-
-    /**
-     * Blob collector which takes *no* guarantees about checking whether the
-     * blob might be referred by paths other than one for which it is notified
-     * due deleted blob
-     */
-    static class ActiveDeletedBlobCollectorImpl implements 
ActiveDeletedBlobCollector {
-        private static PerfLogger PERF_LOG = new PerfLogger(
-                
LoggerFactory.getLogger(ActiveDeletedBlobCollectorImpl.class.getName() + 
".perf"));
-        private static Logger LOG = 
LoggerFactory.getLogger(ActiveDeletedBlobCollectorImpl.class.getName());
-
-        private final Clock clock;
-
-        private final File rootDirectory;
-
-        private final ExecutorService executorService;
-
-        private volatile boolean cancelled;
-
-        private static final String BLOB_FILE_PATTERN_PREFIX = "blobs-";
-        private static final String BLOB_FILE_PATTERN_SUFFIX = ".txt";
-        private static final String BLOB_FILE_PATTERN = 
BLOB_FILE_PATTERN_PREFIX + "%s" + BLOB_FILE_PATTERN_SUFFIX;
-        private static final IOFileFilter blobFileNameFilter = new 
RegexFileFilter("blobs-.*\\.txt");
-
-        private final BlockingQueue<BlobIdInfoStruct> deletedBlobs;
-        private final DeletedBlobsFileWriter deletedBlobsFileWriter;
-
-        /**
-         * @param rootDirectory directory that may be used by this instance to
-         *                      keep temporary data (e.g. reported deleted 
blob-ids).
-         * @param executorService executor service to asynchronously flush 
deleted blobs
-         *                        to a file.
-         */
-        ActiveDeletedBlobCollectorImpl(@Nonnull File rootDirectory, @Nonnull 
ExecutorService executorService) {
-            this(Clock.SIMPLE, rootDirectory, executorService);
-        }
-
-        ActiveDeletedBlobCollectorImpl(Clock clock, @Nonnull File 
rootDirectory,
-                                       @Nonnull ExecutorService 
executorService) {
-            this.clock = clock;
-            this.rootDirectory = rootDirectory;
-            this.executorService = executorService;
-            this.deletedBlobs = new LinkedBlockingQueue<>(100000);
-            this.deletedBlobsFileWriter = new DeletedBlobsFileWriter();
-        }
-
-        /**
-         * Purges blobs form blob-store which were tracked earlier to deleted.
-         * @param before only purge blobs which were deleted before this 
timestamps
-         * @param blobStore used to purge blobs/chunks
-         */
-        public void purgeBlobsDeleted(long before, @Nonnull 
GarbageCollectableBlobStore blobStore) {
-            cancelled = false;
-            long start = clock.getTime();
-            LOG.info("Starting purge of blobs deleted before {}", before);
-            long numBlobsDeleted = 0;
-            long numChunksDeleted = 0;
-
-            File idTempDeleteFile = null;
-            BufferedWriter idTempDeleteWriter = null;
-            // If blob store support blob tracking
-            boolean blobIdsTracked = blobStore instanceof BlobTrackingStore;
-
-            if (blobIdsTracked) {
-                try {
-                    idTempDeleteFile = File.createTempFile("idTempDelete", 
null, rootDirectory);
-                    idTempDeleteWriter = Files.newWriter(idTempDeleteFile, 
Charsets.UTF_8);
-                } catch (Exception e) {
-                    LOG.warn("Unable to open a writer to a temp file, will 
ignore tracker sync");
-                    blobIdsTracked = false;
-                }
-            }
-
-            long lastCheckedBlobTimestamp = readLastCheckedBlobTimestamp();
-            long lastDeletedBlobTimestamp = lastCheckedBlobTimestamp;
-            String currInUseFileName = deletedBlobsFileWriter.inUseFileName;
-            deletedBlobsFileWriter.releaseInUseFile();
-            for (File deletedBlobListFile : FileUtils.listFiles(rootDirectory, 
blobFileNameFilter, null)) {
-                if (cancelled) {
-                    break;
-                }
-                if 
(deletedBlobListFile.getName().equals(deletedBlobsFileWriter.inUseFileName)) {
-                    continue;
-                }
-                LOG.debug("Purging blobs from {}", deletedBlobListFile);
-                long timestamp;
-                try {
-                    timestamp = 
getTimestampFromBlobFileName(deletedBlobListFile.getName());
-                } catch (IllegalArgumentException iae) {
-                    LOG.warn("Couldn't extract timestamp from filename - " + 
deletedBlobListFile, iae);
-                    continue;
-                }
-                if (timestamp < before) {
-                    try {
-                        for (String deletedBlobLine : 
FileUtils.readLines(deletedBlobListFile, (String) null)) {
-                            if (cancelled) {
-                                break;
-                            }
-
-                            String[] parsedDeletedBlobIdLine = 
deletedBlobLine.split("\\|", 3);
-                            if (parsedDeletedBlobIdLine.length != 3) {
-                                LOG.warn("Unparseable line ({}) in file {}. It 
won't be retried.",
-                                        parsedDeletedBlobIdLine, 
deletedBlobListFile);
-                            } else {
-                                String deletedBlobId = 
parsedDeletedBlobIdLine[0];
-                                try {
-                                    long blobDeletionTimestamp = 
Long.valueOf(parsedDeletedBlobIdLine[1]);
-
-                                    if (blobDeletionTimestamp < 
lastCheckedBlobTimestamp) {
-                                        continue;
-                                    }
-
-                                    if (blobDeletionTimestamp >= before) {
-                                        break;
-                                    }
-
-                                    lastDeletedBlobTimestamp = 
Math.max(lastDeletedBlobTimestamp, blobDeletionTimestamp);
-
-                                    List<String> chunkIds = 
Lists.newArrayList(blobStore.resolveChunks(deletedBlobId));
-                                    if (chunkIds.size() > 0) {
-                                        long deleted = 
blobStore.countDeleteChunks(chunkIds, 0);
-                                        if (deleted < 1) {
-                                            LOG.warn("Blob {} in file {} not 
deleted", deletedBlobId, deletedBlobListFile);
-                                        } else {
-                                            numBlobsDeleted++;
-                                            numChunksDeleted += deleted;
-
-                                            if (blobIdsTracked) {
-                                                // Save deleted chunkIds to a 
temporary file
-                                                for (String id : chunkIds) {
-                                                    
FileIOUtils.writeAsLine(idTempDeleteWriter, id, true);
-                                                }
-                                            }
-                                        }
-                                    }
-                                } catch (NumberFormatException nfe) {
-                                    LOG.warn("Couldn't parse blobTimestamp(" + 
parsedDeletedBlobIdLine[1] +
-                                            "). deletedBlobLine - " + 
deletedBlobLine +
-                                            "; file - " + 
deletedBlobListFile.getName(), nfe);
-                                } catch (DataStoreException dse) {
-                                    LOG.debug("Exception occurred while 
attempting to delete blob " + deletedBlobId, dse);
-                                } catch (Exception e) {
-                                    LOG.warn("Exception occurred while 
attempting to delete blob " + deletedBlobId, e);
-                                }
-                            }
-                        }
-                    } catch (IOException ioe) {
-                        //log error and continue
-                        LOG.warn("Couldn't read deleted blob list file - " + 
deletedBlobListFile, ioe);
-                    }
-
-                    // OAK-6314 revealed that blobs appended might not be 
immediately available. So, we'd skip
-                    // the file that was being processed when purge started - 
next cycle would re-process and
-                    // delete
-                    if 
(!deletedBlobListFile.getName().equals(currInUseFileName)) {
-                        if (!deletedBlobListFile.delete()) {
-                            LOG.warn("File {} couldn't be deleted while all 
blobs listed in it have been purged", deletedBlobListFile);
-                        } else {
-                            LOG.debug("File {} deleted", deletedBlobListFile);
-                        }
-                    }
-                } else {
-                    LOG.debug("Skipping {} as its timestamp is newer than {}", 
deletedBlobListFile.getName(), before);
-                }
-            }
-
-            long startBlobTrackerSyncTime = clock.getTime();
-            // Synchronize deleted blob ids with the blob id tracker
-            try {
-                Closeables.close(idTempDeleteWriter, true);
-
-                if (blobIdsTracked && numBlobsDeleted > 0) {
-                    BlobTracker tracker = ((BlobTrackingStore) 
blobStore).getTracker();
-                    if (tracker != null) {
-                        tracker.remove(idTempDeleteFile, 
Options.ACTIVE_DELETION);
-                    }
-                }
-            } catch(Exception e) {
-                LOG.warn("Error refreshing tracked blob ids", e);
-            }
-            long endBlobTrackerSyncTime = clock.getTime();
-            LOG.info("Synchronizing changes with blob tracker took {} ms", 
endBlobTrackerSyncTime - startBlobTrackerSyncTime);
-
-            if (cancelled) {
-                LOG.info("Deletion run cancelled by user");
-            }
-            long end = clock.getTime();
-            LOG.info("Deleted {} blobs contained in {} chunks in {} ms", 
numBlobsDeleted, numChunksDeleted, end - start);
-            writeOutLastCheckedBlobTimestamp(lastDeletedBlobTimestamp);
-        }
-
-        @Override
-        public void cancelBlobCollection() {
-            cancelled = true;
-        }
-
-        private long readLastCheckedBlobTimestamp() {
-            File blobCollectorInfoFile = new File(rootDirectory, 
"collection-info.txt");
-            if (!blobCollectorInfoFile.exists()) {
-                LOG.debug("Couldn't read last checked blob timestamp (file not 
found). Would do a bit more scan");
-                return -1;
-            }
-            InputStream is = null;
-            Properties p;
-            try {
-                is = new BufferedInputStream(new 
FileInputStream(blobCollectorInfoFile));
-                p = new Properties();
-                p.load(is);
-            } catch (IOException e) {
-                LOG.warn("Couldn't read last checked blob timestamp from {} 
... would do a bit more scan",
-                        blobCollectorInfoFile, e);
-                return -1;
-            } finally {
-                org.apache.commons.io.IOUtils.closeQuietly(is);
-            }
-
-            String resString = p.getProperty("last-checked-blob-timestamp");
-            if (resString == null) {
-                LOG.warn("Couldn't fine last checked blob timestamp property 
in collection-info.txt");
-                return -1;
-            }
-
-            try {
-                return Long.valueOf(resString);
-            } catch (NumberFormatException nfe) {
-                LOG.warn("Couldn't read last checked blob timestamp '" + 
resString + "' as long", nfe);
-                return -1;
-            }
-        }
-
-        private void writeOutLastCheckedBlobTimestamp(long timestamp) {
-            Properties p = new Properties();
-            p.setProperty("last-checked-blob-timestamp", 
String.valueOf(timestamp));
-            File blobCollectorInfoFile = new File(rootDirectory, 
"collection-info.txt");
-            OutputStream os = null;
-            try {
-                os = new BufferedOutputStream(new 
FileOutputStream(blobCollectorInfoFile));
-                p.store(os, "Last checked blob timestamp");
-            } catch (IOException e) {
-                LOG.warn("Couldn't write out last checked blob timestamp(" + 
timestamp + ")", e);
-            } finally {
-                org.apache.commons.io.IOUtils.closeQuietly(os);
-            }
-
-        }
-
-        public BlobDeletionCallback getBlobDeletionCallback() throws 
IllegalStateException {
-            return new DeletedBlobCollector();
-        }
-
-        static long getTimestampFromBlobFileName(String filename) throws 
IllegalArgumentException {
-            checkArgument(filename.startsWith(BLOB_FILE_PATTERN_PREFIX),
-                    "Filename(%s) must start with %s", filename, 
BLOB_FILE_PATTERN_PREFIX);
-            checkArgument(filename.endsWith(BLOB_FILE_PATTERN_SUFFIX),
-                    "Filename(%s) must end with %s", filename, 
BLOB_FILE_PATTERN_SUFFIX);
-            String timestampStr = filename.substring(
-                    BLOB_FILE_PATTERN_PREFIX.length(),
-                    filename.length() - BLOB_FILE_PATTERN_SUFFIX.length());
-
-            return Long.parseLong(timestampStr);
-        }
-
-        private void addDeletedBlobs(Collection<BlobIdInfoStruct> 
deletedBlobs) {
-            int addedForFlush = 0;
-            for (BlobIdInfoStruct info : deletedBlobs) {
-                try {
-                    if (!this.deletedBlobs.offer(info, 1, TimeUnit.SECONDS)) {
-                        LOG.warn("Timed out while offer-ing {} into queue.", 
info);
-                    }
-                    if (LOG.isDebugEnabled()) {
-                        addedForFlush++;
-                    }
-                } catch (InterruptedException e) {
-                    LOG.warn("Interrupted while adding " + info, e);
-                }
-            }
-            if (LOG.isDebugEnabled()) {
-                LOG.debug("Added {} (out of {} tried) to be flushed. QSize: 
{}",
-                        addedForFlush, deletedBlobs.size(), 
this.deletedBlobs.size());
-            }
-            deletedBlobsFileWriter.scheduleFileFlushIfNeeded();
-        }
-
-        private class DeletedBlobsFileWriter implements Runnable {
-            private final AtomicBoolean fileFlushScheduled = new 
AtomicBoolean(false);
-
-            private volatile String inUseFileName = null;
-
-            private synchronized void flushDeletedBlobs() {
-                List<BlobIdInfoStruct> localDeletedBlobs = new LinkedList<>();
-                deletedBlobs.drainTo(localDeletedBlobs);
-                if (localDeletedBlobs.size() > 0) {
-                    File outFile = new File(rootDirectory, getBlobFileName());
-                    try {
-                        long start = PERF_LOG.start();
-                        FileUtils.writeLines(outFile, localDeletedBlobs, true);
-                        PERF_LOG.end(start, 1, "Flushing deleted blobs");
-                    } catch (IOException e) {
-                        LOG.error("Couldn't write out to " + outFile, e);
-                    }
-                    if (LOG.isDebugEnabled()) {
-                        LOG.debug("Flushed {} blobs to {}", 
localDeletedBlobs.size(), outFile.getName());
-                    }
-                }
-            }
-
-            private void scheduleFileFlushIfNeeded() {
-                if (fileFlushScheduled.compareAndSet(false, true)) {
-                    executorService.submit(this);
-                }
-            }
-
-            private synchronized void releaseInUseFile() {
-                inUseFileName = null;
-            }
-
-            @Override
-            public void run() {
-                flushDeletedBlobs();
-                fileFlushScheduled.set(false);
-            }
-
-            private String getBlobFileName() {
-                if (inUseFileName == null) {
-                    inUseFileName = String.format(BLOB_FILE_PATTERN, 
clock.getTime());
-                }
-                return inUseFileName;
-            }
-        }
-
-        /**
-         * This implementation would track deleted blobs and then pass them 
onto
-         * {@link ActiveDeletedBlobCollectorImpl} on a successful commit
-         */
-        private class DeletedBlobCollector implements BlobDeletionCallback {
-            List<BlobIdInfoStruct> deletedBlobs = new ArrayList<>();
-
-            @Override
-            public void deleted(String blobId, Iterable<String> ids) {
-                deletedBlobs.add(new BlobIdInfoStruct(blobId, ids));
-            }
-
-            @Override
-            public void commitProgress(IndexProgress indexProgress) {
-                if (indexProgress != IndexProgress.COMMIT_SUCCEDED && 
indexProgress != IndexProgress.COMMIT_FAILED) {
-                    LOG.debug("We only care for commit success/failure");
-                    return;
-                }
-                if (indexProgress == IndexProgress.COMMIT_SUCCEDED) {
-                    addDeletedBlobs(deletedBlobs);
-                }
-
-                deletedBlobs.clear();
-            }
-        }
-
-        private class BlobIdInfoStruct {
-            final String blobId;
-            final Iterable<String> ids;
-
-            BlobIdInfoStruct(String blobId, Iterable<String> ids) {
-                this.blobId = blobId;
-                this.ids = ids;
-            }
-
-            @Override
-            public String toString() {
-                return String.format("%s|%s|%s", blobId, clock.getTime(), 
Joiner.on("|").join(ids));
-            }
-        }
-    }
-}
+/*
+ * 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.index.lucene.directory;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Joiner;
+import com.google.common.collect.Lists;
+import com.google.common.io.Closeables;
+import com.google.common.io.Files;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.filefilter.IOFileFilter;
+import org.apache.commons.io.filefilter.RegexFileFilter;
+import org.apache.jackrabbit.core.data.DataStoreException;
+import org.apache.jackrabbit.oak.commons.FileIOUtils;
+import org.apache.jackrabbit.oak.commons.benchmark.PerfLogger;
+import org.apache.jackrabbit.oak.plugins.blob.BlobTrackingStore;
+import org.apache.jackrabbit.oak.plugins.blob.datastore.BlobTracker;
+import org.apache.jackrabbit.oak.plugins.blob.datastore.BlobTracker.Options;
+import org.apache.jackrabbit.oak.plugins.index.IndexCommitCallback;
+import org.apache.jackrabbit.oak.spi.blob.GarbageCollectableBlobStore;
+import org.apache.jackrabbit.oak.stats.Clock;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.Nonnull;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Properties;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+public class ActiveDeletedBlobCollectorFactory {
+    public interface ActiveDeletedBlobCollector {
+        /**
+         * @return an instance of {@link BlobDeletionCallback} that can be 
used to track deleted blobs
+         */
+        BlobDeletionCallback getBlobDeletionCallback();
+
+        void purgeBlobsDeleted(long before, GarbageCollectableBlobStore 
blobStore);
+
+        void cancelBlobCollection();
+    }
+
+    public static ActiveDeletedBlobCollector NOOP = new 
ActiveDeletedBlobCollector() {
+        @Override
+        public BlobDeletionCallback getBlobDeletionCallback() {
+            return BlobDeletionCallback.NOOP;
+        }
+
+        @Override
+        public void purgeBlobsDeleted(long before, GarbageCollectableBlobStore 
blobStore) {
+
+        }
+
+        @Override
+        public void cancelBlobCollection() {
+
+        }
+    };
+
+    public interface BlobDeletionCallback extends IndexCommitCallback {
+        /**
+         * Tracks deleted blobs. From the pov of this interface, blobId is an 
opaque string
+         * that needs to be tracked.
+         * @param blobId blobId representing deleted blob. In theory, it has 
nothing to do with
+         *               blobs though.
+         * @param ids Information that can be useful for debugging - this is 
not used for purging
+         *            blobs.
+         */
+        void deleted(String blobId, Iterable<String> ids);
+        BlobDeletionCallback NOOP = new BlobDeletionCallback() {
+            @Override
+            public void deleted(String blobId, Iterable<String> ids) {
+            }
+
+            @Override
+            public void commitProgress(IndexProgress indexProgress) {
+            }
+        };
+    }
+
+    public static ActiveDeletedBlobCollector newInstance(@Nonnull File 
rootDirectory,
+                                                         ExecutorService 
executorService) {
+        try {
+            FileUtils.forceMkdir(rootDirectory);
+        } catch (IOException ioe) {
+            ActiveDeletedBlobCollectorImpl.LOG.warn("Disabling active blob 
collector as we couldn't not create folder: "
+                    + rootDirectory, ioe);
+            return NOOP;
+        }
+        if(!rootDirectory.canRead() || !rootDirectory.canWrite() || 
!rootDirectory.canExecute()) {
+            ActiveDeletedBlobCollectorImpl.LOG.warn("Insufficient access in 
directory - {}. Disabling active blob collector",
+                    rootDirectory);
+            return NOOP;
+        }
+        return new ActiveDeletedBlobCollectorImpl(rootDirectory, 
executorService);
+    }
+
+    /**
+     * Blob collector which takes *no* guarantees about checking whether the
+     * blob might be referred by paths other than one for which it is notified
+     * due deleted blob
+     */
+    static class ActiveDeletedBlobCollectorImpl implements 
ActiveDeletedBlobCollector {
+        private static PerfLogger PERF_LOG = new PerfLogger(
+                
LoggerFactory.getLogger(ActiveDeletedBlobCollectorImpl.class.getName() + 
".perf"));
+        private static Logger LOG = 
LoggerFactory.getLogger(ActiveDeletedBlobCollectorImpl.class.getName());
+
+        private final Clock clock;
+
+        private final File rootDirectory;
+
+        private final ExecutorService executorService;
+
+        private volatile boolean cancelled;
+
+        private static final String BLOB_FILE_PATTERN_PREFIX = "blobs-";
+        private static final String BLOB_FILE_PATTERN_SUFFIX = ".txt";
+        private static final String BLOB_FILE_PATTERN = 
BLOB_FILE_PATTERN_PREFIX + "%s" + BLOB_FILE_PATTERN_SUFFIX;
+        private static final IOFileFilter blobFileNameFilter = new 
RegexFileFilter("blobs-.*\\.txt");
+
+        private final BlockingQueue<BlobIdInfoStruct> deletedBlobs;
+        private final DeletedBlobsFileWriter deletedBlobsFileWriter;
+
+        /**
+         * @param rootDirectory directory that may be used by this instance to
+         *                      keep temporary data (e.g. reported deleted 
blob-ids).
+         * @param executorService executor service to asynchronously flush 
deleted blobs
+         *                        to a file.
+         */
+        ActiveDeletedBlobCollectorImpl(@Nonnull File rootDirectory, @Nonnull 
ExecutorService executorService) {
+            this(Clock.SIMPLE, rootDirectory, executorService);
+        }
+
+        ActiveDeletedBlobCollectorImpl(Clock clock, @Nonnull File 
rootDirectory,
+                                       @Nonnull ExecutorService 
executorService) {
+            this.clock = clock;
+            this.rootDirectory = rootDirectory;
+            this.executorService = executorService;
+            this.deletedBlobs = new LinkedBlockingQueue<>(100000);
+            this.deletedBlobsFileWriter = new DeletedBlobsFileWriter();
+        }
+
+        /**
+         * Purges blobs form blob-store which were tracked earlier to deleted.
+         * @param before only purge blobs which were deleted before this 
timestamps
+         * @param blobStore used to purge blobs/chunks
+         */
+        public void purgeBlobsDeleted(long before, @Nonnull 
GarbageCollectableBlobStore blobStore) {
+            cancelled = false;
+            long start = clock.getTime();
+            LOG.info("Starting purge of blobs deleted before {}", before);
+            long numBlobsDeleted = 0;
+            long numChunksDeleted = 0;
+
+            File idTempDeleteFile = null;
+            BufferedWriter idTempDeleteWriter = null;
+            // If blob store support blob tracking
+            boolean blobIdsTracked = blobStore instanceof BlobTrackingStore;
+
+            if (blobIdsTracked) {
+                try {
+                    idTempDeleteFile = File.createTempFile("idTempDelete", 
null, rootDirectory);
+                    idTempDeleteWriter = Files.newWriter(idTempDeleteFile, 
Charsets.UTF_8);
+                } catch (Exception e) {
+                    LOG.warn("Unable to open a writer to a temp file, will 
ignore tracker sync");
+                    blobIdsTracked = false;
+                }
+            }
+
+            long lastCheckedBlobTimestamp = readLastCheckedBlobTimestamp();
+            long lastDeletedBlobTimestamp = lastCheckedBlobTimestamp;
+            String currInUseFileName = deletedBlobsFileWriter.inUseFileName;
+            deletedBlobsFileWriter.releaseInUseFile();
+            for (File deletedBlobListFile : FileUtils.listFiles(rootDirectory, 
blobFileNameFilter, null)) {
+                if (cancelled) {
+                    break;
+                }
+                if 
(deletedBlobListFile.getName().equals(deletedBlobsFileWriter.inUseFileName)) {
+                    continue;
+                }
+                LOG.debug("Purging blobs from {}", deletedBlobListFile);
+                long timestamp;
+                try {
+                    timestamp = 
getTimestampFromBlobFileName(deletedBlobListFile.getName());
+                } catch (IllegalArgumentException iae) {
+                    LOG.warn("Couldn't extract timestamp from filename - " + 
deletedBlobListFile, iae);
+                    continue;
+                }
+                if (timestamp < before) {
+                    try {
+                        for (String deletedBlobLine : 
FileUtils.readLines(deletedBlobListFile, (String) null)) {
+                            if (cancelled) {
+                                break;
+                            }
+
+                            String[] parsedDeletedBlobIdLine = 
deletedBlobLine.split("\\|", 3);
+                            if (parsedDeletedBlobIdLine.length != 3) {
+                                LOG.warn("Unparseable line ({}) in file {}. It 
won't be retried.",
+                                        parsedDeletedBlobIdLine, 
deletedBlobListFile);
+                            } else {
+                                String deletedBlobId = 
parsedDeletedBlobIdLine[0];
+                                try {
+                                    long blobDeletionTimestamp = 
Long.valueOf(parsedDeletedBlobIdLine[1]);
+
+                                    if (blobDeletionTimestamp < 
lastCheckedBlobTimestamp) {
+                                        continue;
+                                    }
+
+                                    if (blobDeletionTimestamp >= before) {
+                                        break;
+                                    }
+
+                                    lastDeletedBlobTimestamp = 
Math.max(lastDeletedBlobTimestamp, blobDeletionTimestamp);
+
+                                    List<String> chunkIds = 
Lists.newArrayList(blobStore.resolveChunks(deletedBlobId));
+                                    if (chunkIds.size() > 0) {
+                                        long deleted = 
blobStore.countDeleteChunks(chunkIds, 0);
+                                        if (deleted < 1) {
+                                            LOG.warn("Blob {} in file {} not 
deleted", deletedBlobId, deletedBlobListFile);
+                                        } else {
+                                            numBlobsDeleted++;
+                                            numChunksDeleted += deleted;
+
+                                            if (blobIdsTracked) {
+                                                // Save deleted chunkIds to a 
temporary file
+                                                for (String id : chunkIds) {
+                                                    
FileIOUtils.writeAsLine(idTempDeleteWriter, id, true);
+                                                }
+                                            }
+                                        }
+                                    }
+                                } catch (NumberFormatException nfe) {
+                                    LOG.warn("Couldn't parse blobTimestamp(" + 
parsedDeletedBlobIdLine[1] +
+                                            "). deletedBlobLine - " + 
deletedBlobLine +
+                                            "; file - " + 
deletedBlobListFile.getName(), nfe);
+                                } catch (DataStoreException dse) {
+                                    LOG.debug("Exception occurred while 
attempting to delete blob " + deletedBlobId, dse);
+                                } catch (Exception e) {
+                                    LOG.warn("Exception occurred while 
attempting to delete blob " + deletedBlobId, e);
+                                }
+                            }
+                        }
+                    } catch (IOException ioe) {
+                        //log error and continue
+                        LOG.warn("Couldn't read deleted blob list file - " + 
deletedBlobListFile, ioe);
+                    }
+
+                    // OAK-6314 revealed that blobs appended might not be 
immediately available. So, we'd skip
+                    // the file that was being processed when purge started - 
next cycle would re-process and
+                    // delete
+                    if 
(!deletedBlobListFile.getName().equals(currInUseFileName)) {
+                        if (!deletedBlobListFile.delete()) {
+                            LOG.warn("File {} couldn't be deleted while all 
blobs listed in it have been purged", deletedBlobListFile);
+                        } else {
+                            LOG.debug("File {} deleted", deletedBlobListFile);
+                        }
+                    }
+                } else {
+                    LOG.debug("Skipping {} as its timestamp is newer than {}", 
deletedBlobListFile.getName(), before);
+                }
+            }
+
+            long startBlobTrackerSyncTime = clock.getTime();
+            // Synchronize deleted blob ids with the blob id tracker
+            try {
+                Closeables.close(idTempDeleteWriter, true);
+
+                if (blobIdsTracked && numBlobsDeleted > 0) {
+                    BlobTracker tracker = ((BlobTrackingStore) 
blobStore).getTracker();
+                    if (tracker != null) {
+                        tracker.remove(idTempDeleteFile, 
Options.ACTIVE_DELETION);
+                    }
+                }
+            } catch(Exception e) {
+                LOG.warn("Error refreshing tracked blob ids", e);
+            }
+            long endBlobTrackerSyncTime = clock.getTime();
+            LOG.info("Synchronizing changes with blob tracker took {} ms", 
endBlobTrackerSyncTime - startBlobTrackerSyncTime);
+
+            if (cancelled) {
+                LOG.info("Deletion run cancelled by user");
+            }
+            long end = clock.getTime();
+            LOG.info("Deleted {} blobs contained in {} chunks in {} ms", 
numBlobsDeleted, numChunksDeleted, end - start);
+            writeOutLastCheckedBlobTimestamp(lastDeletedBlobTimestamp);
+        }
+
+        @Override
+        public void cancelBlobCollection() {
+            cancelled = true;
+        }
+
+        private long readLastCheckedBlobTimestamp() {
+            File blobCollectorInfoFile = new File(rootDirectory, 
"collection-info.txt");
+            if (!blobCollectorInfoFile.exists()) {
+                LOG.debug("Couldn't read last checked blob timestamp (file not 
found). Would do a bit more scan");
+                return -1;
+            }
+            InputStream is = null;
+            Properties p;
+            try {
+                is = new BufferedInputStream(new 
FileInputStream(blobCollectorInfoFile));
+                p = new Properties();
+                p.load(is);
+            } catch (IOException e) {
+                LOG.warn("Couldn't read last checked blob timestamp from {} 
... would do a bit more scan",
+                        blobCollectorInfoFile, e);
+                return -1;
+            } finally {
+                org.apache.commons.io.IOUtils.closeQuietly(is);
+            }
+
+            String resString = p.getProperty("last-checked-blob-timestamp");
+            if (resString == null) {
+                LOG.warn("Couldn't fine last checked blob timestamp property 
in collection-info.txt");
+                return -1;
+            }
+
+            try {
+                return Long.valueOf(resString);
+            } catch (NumberFormatException nfe) {
+                LOG.warn("Couldn't read last checked blob timestamp '" + 
resString + "' as long", nfe);
+                return -1;
+            }
+        }
+
+        private void writeOutLastCheckedBlobTimestamp(long timestamp) {
+            Properties p = new Properties();
+            p.setProperty("last-checked-blob-timestamp", 
String.valueOf(timestamp));
+            File blobCollectorInfoFile = new File(rootDirectory, 
"collection-info.txt");
+            OutputStream os = null;
+            try {
+                os = new BufferedOutputStream(new 
FileOutputStream(blobCollectorInfoFile));
+                p.store(os, "Last checked blob timestamp");
+            } catch (IOException e) {
+                LOG.warn("Couldn't write out last checked blob timestamp(" + 
timestamp + ")", e);
+            } finally {
+                org.apache.commons.io.IOUtils.closeQuietly(os);
+            }
+
+        }
+
+        public BlobDeletionCallback getBlobDeletionCallback() throws 
IllegalStateException {
+            return new DeletedBlobCollector();
+        }
+
+        static long getTimestampFromBlobFileName(String filename) throws 
IllegalArgumentException {
+            checkArgument(filename.startsWith(BLOB_FILE_PATTERN_PREFIX),
+                    "Filename(%s) must start with %s", filename, 
BLOB_FILE_PATTERN_PREFIX);
+            checkArgument(filename.endsWith(BLOB_FILE_PATTERN_SUFFIX),
+                    "Filename(%s) must end with %s", filename, 
BLOB_FILE_PATTERN_SUFFIX);
+            String timestampStr = filename.substring(
+                    BLOB_FILE_PATTERN_PREFIX.length(),
+                    filename.length() - BLOB_FILE_PATTERN_SUFFIX.length());
+
+            return Long.parseLong(timestampStr);
+        }
+
+        private void addDeletedBlobs(Collection<BlobIdInfoStruct> 
deletedBlobs) {
+            int addedForFlush = 0;
+            for (BlobIdInfoStruct info : deletedBlobs) {
+                try {
+                    if (!this.deletedBlobs.offer(info, 1, TimeUnit.SECONDS)) {
+                        LOG.warn("Timed out while offer-ing {} into queue.", 
info);
+                    }
+                    if (LOG.isDebugEnabled()) {
+                        addedForFlush++;
+                    }
+                } catch (InterruptedException e) {
+                    LOG.warn("Interrupted while adding " + info, e);
+                }
+            }
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("Added {} (out of {} tried) to be flushed. QSize: 
{}",
+                        addedForFlush, deletedBlobs.size(), 
this.deletedBlobs.size());
+            }
+            deletedBlobsFileWriter.scheduleFileFlushIfNeeded();
+        }
+
+        private class DeletedBlobsFileWriter implements Runnable {
+            private final AtomicBoolean fileFlushScheduled = new 
AtomicBoolean(false);
+
+            private volatile String inUseFileName = null;
+
+            private synchronized void flushDeletedBlobs() {
+                List<BlobIdInfoStruct> localDeletedBlobs = new LinkedList<>();
+                deletedBlobs.drainTo(localDeletedBlobs);
+                if (localDeletedBlobs.size() > 0) {
+                    File outFile = new File(rootDirectory, getBlobFileName());
+                    try {
+                        long start = PERF_LOG.start();
+                        FileUtils.writeLines(outFile, localDeletedBlobs, true);
+                        PERF_LOG.end(start, 1, "Flushing deleted blobs");
+                    } catch (IOException e) {
+                        LOG.error("Couldn't write out to " + outFile, e);
+                    }
+                    if (LOG.isDebugEnabled()) {
+                        LOG.debug("Flushed {} blobs to {}", 
localDeletedBlobs.size(), outFile.getName());
+                    }
+                }
+            }
+
+            private void scheduleFileFlushIfNeeded() {
+                if (fileFlushScheduled.compareAndSet(false, true)) {
+                    executorService.submit(this);
+                }
+            }
+
+            private synchronized void releaseInUseFile() {
+                inUseFileName = null;
+            }
+
+            @Override
+            public void run() {
+                flushDeletedBlobs();
+                fileFlushScheduled.set(false);
+            }
+
+            private String getBlobFileName() {
+                if (inUseFileName == null) {
+                    inUseFileName = String.format(BLOB_FILE_PATTERN, 
clock.getTime());
+                }
+                return inUseFileName;
+            }
+        }
+
+        /**
+         * This implementation would track deleted blobs and then pass them 
onto
+         * {@link ActiveDeletedBlobCollectorImpl} on a successful commit
+         */
+        private class DeletedBlobCollector implements BlobDeletionCallback {
+            List<BlobIdInfoStruct> deletedBlobs = new ArrayList<>();
+
+            @Override
+            public void deleted(String blobId, Iterable<String> ids) {
+                deletedBlobs.add(new BlobIdInfoStruct(blobId, ids));
+            }
+
+            @Override
+            public void commitProgress(IndexProgress indexProgress) {
+                if (indexProgress != IndexProgress.COMMIT_SUCCEDED && 
indexProgress != IndexProgress.COMMIT_FAILED) {
+                    LOG.debug("We only care for commit success/failure");
+                    return;
+                }
+                if (indexProgress == IndexProgress.COMMIT_SUCCEDED) {
+                    addDeletedBlobs(deletedBlobs);
+                }
+
+                deletedBlobs.clear();
+            }
+        }
+
+        private class BlobIdInfoStruct {
+            final String blobId;
+            final Iterable<String> ids;
+
+            BlobIdInfoStruct(String blobId, Iterable<String> ids) {
+                this.blobId = blobId;
+                this.ids = ids;
+            }
+
+            @Override
+            public String toString() {
+                return String.format("%s|%s|%s", blobId, clock.getTime(), 
Joiner.on("|").join(ids));
+            }
+        }
+    }
+}


Reply via email to