This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-vfs.git


The following commit(s) were added to refs/heads/master by this push:
     new df56399  Rework SoftRefFilesCache locking (#154)
df56399 is described below

commit df56399d784094a24c0ab784c3eddee0908952eb
Author: Max Kellermann <m...@cm4all.com>
AuthorDate: Fri Mar 5 21:27:10 2021 +0100

    Rework SoftRefFilesCache locking (#154)
    
    * SoftRefFilesCache: no ReferenceQueue timeout, sleep indefinitely
    
    Waking up once a second is just a waste of CPU time.  Let
    ReferenceQueue.remove() only wake up if there's something to do (or if
    the thread got interrupted).
    
    * SoftRefFilesCache: eliminate the requestEnd flag
    
    The Thread class keeps track for us already, and if it's interrupted,
    we'll catch InterruptedException and quit the thread.
    
    * SoftRefFilesCache: fix ReentrantLock usage in {start,end}Thread()
    
    Can't use the "synchronized" keyword on a Lock; this will only lock
    the java.lang.Object, but not the Lock.
    
    This wrong usage was added by commit f09035290b1 (SVN r1704932).
    
    * SoftRefFilesCache: add missing lock to removeFile()
    
    The API documentation of close(FileSystem) requires that the caller
    holds the lock, but removeFile() didn't do that.
    
    * SoftRefFilesCache: require lock while calling 
removeFile(FileSystemAndNameKey)
    
    This allows merging the lock with the caller, fixing a race condition
    in removeFile(FileSystem,FileName).
    
    * SoftRefFilesCache: require lock while calling getOrCreateFilesystemCache()
    
    It is an illusion to believe that not requiring the lock would be
    faster, because all callers will obtain the lock right after
    getOrCreateFilesystemCache() returns.
    
    As a wanted side effect, this also fixes a time-of-check-time-of-use
    bug: if the fileSystemCache becomes non-empty by another thread
    between the isEmpty() and the get() call (with removeFile() calls by
    yet another thread), the SoftRefReleaseThread will never be started.
    
    * SoftRefFilesCache: don't use ConcurrentMap
    
    Now that all accesses to fileSystemCache happen while the lock is
    held, we don't need another layer of synchronization.
    
    * SoftRefFilesCache: eliminate "volatile" on softRefReleaseThread
    
    That "double checked locking" in startThread() is rather pointless,
    because the overhead saved by not locking never materializes.
    startThread() is only ever called when there is no thread, and
    optimizing for a corner case with a data race isn't worth the
    complexity.  So let's just do everything inside the lock and remove
    "volatile".
    
    * SoftRefFilesCache: move endThread() call inside the lock
    
    This avoids locking the Lock twice, because the two lock sections are
    merged.
    
    * SoftRefFilesCache: require lock for startThread(), endThread()
    
    All callers already do that, this just documents the fact and removes
    the explicit lock()/unlock() calls from those methods.
    
    * SoftRefFilesCache: move code to removeFile(Reference)
    
    * SoftRefFilesCache: eliminate ReentrantLock, use "synchronized"
    
    This class uses no special ReentrantLock/Lock feature, it's just basic
    thread synchronization.  Using "synchronized" is easier to use and
    less error prone.
---
 .../commons/vfs2/cache/SoftRefFilesCache.java      | 190 +++++++--------------
 1 file changed, 64 insertions(+), 126 deletions(-)

diff --git 
a/commons-vfs2/src/main/java/org/apache/commons/vfs2/cache/SoftRefFilesCache.java
 
b/commons-vfs2/src/main/java/org/apache/commons/vfs2/cache/SoftRefFilesCache.java
index 1e597d8..83d0ad3 100644
--- 
a/commons-vfs2/src/main/java/org/apache/commons/vfs2/cache/SoftRefFilesCache.java
+++ 
b/commons-vfs2/src/main/java/org/apache/commons/vfs2/cache/SoftRefFilesCache.java
@@ -22,17 +22,12 @@ import java.lang.ref.SoftReference;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.commons.vfs2.FileName;
 import org.apache.commons.vfs2.FileObject;
 import org.apache.commons.vfs2.FileSystem;
-import org.apache.commons.vfs2.VfsLog;
 import org.apache.commons.vfs2.util.Messages;
 
 /**
@@ -43,25 +38,19 @@ import org.apache.commons.vfs2.util.Messages;
  */
 public class SoftRefFilesCache extends AbstractFilesCache {
 
-    private static final int TIMEOUT = 1000;
-
     private static final Log log = LogFactory.getLog(SoftRefFilesCache.class);
 
-    private final ConcurrentMap<FileSystem, Map<FileName, 
Reference<FileObject>>> fileSystemCache = new ConcurrentHashMap<>();
+    private final Map<FileSystem, Map<FileName, Reference<FileObject>>> 
fileSystemCache = new HashMap<>();
     private final Map<Reference<FileObject>, FileSystemAndNameKey> 
refReverseMap = new HashMap<>(100);
     private final ReferenceQueue<FileObject> refQueue = new ReferenceQueue<>();
 
-    private volatile SoftRefReleaseThread softRefReleaseThread; // 
@GuardedBy("lock")
-
-    private final Lock lock = new ReentrantLock();
+    private SoftRefReleaseThread softRefReleaseThread;
 
     /**
      * This thread will listen on the ReferenceQueue and remove the entry in 
the filescache as soon as the vm removes
      * the reference
      */
     private final class SoftRefReleaseThread extends Thread {
-        private volatile boolean requestEnd; // used for inter-thread 
communication
-
         private SoftRefReleaseThread() {
             setName(SoftRefReleaseThread.class.getName());
             setDaemon(true);
@@ -70,28 +59,15 @@ public class SoftRefFilesCache extends AbstractFilesCache {
         @Override
         public void run() {
             try {
-                while (!requestEnd) {
-                    final Reference<?> ref = refQueue.remove(TIMEOUT);
+                while (true) {
+                    final Reference<?> ref = refQueue.remove(0);
                     if (ref == null) {
                         continue;
                     }
 
-                    lock.lock();
-                    try {
-                        final FileSystemAndNameKey key = 
refReverseMap.get(ref);
-
-                        if (key != null && removeFile(key)) {
-                            close(key.getFileSystem());
-                        }
-                    } finally {
-                        lock.unlock();
-                    }
+                    removeFile(ref);
                 }
             } catch (final InterruptedException e) {
-                if (!requestEnd) {
-                    VfsLog.warn(getLogger(), log,
-                                
Messages.getString("vfs.impl/SoftRefReleaseThread-interrupt.info"));
-                }
             }
         }
     }
@@ -99,28 +75,18 @@ public class SoftRefFilesCache extends AbstractFilesCache {
     public SoftRefFilesCache() {
     }
 
-    private void startThread() {
-        // Double Checked Locking is allowed when volatile
-        if (softRefReleaseThread != null) {
-            return;
-        }
-
-        synchronized (lock) {
-            if (softRefReleaseThread == null) {
-                softRefReleaseThread = new SoftRefReleaseThread();
-                softRefReleaseThread.start();
-            }
+    private synchronized void startThread() {
+        if (softRefReleaseThread == null) {
+            softRefReleaseThread = new SoftRefReleaseThread();
+            softRefReleaseThread.start();
         }
     }
 
-    private void endThread() {
-        synchronized (lock) {
-            final SoftRefReleaseThread thread = softRefReleaseThread;
-            softRefReleaseThread = null;
-            if (thread != null) {
-                thread.requestEnd = true;
-                thread.interrupt();
-            }
+    private synchronized void endThread() {
+        final SoftRefReleaseThread thread = softRefReleaseThread;
+        softRefReleaseThread = null;
+        if (thread != null) {
+            thread.interrupt();
         }
     }
 
@@ -130,20 +96,17 @@ public class SoftRefFilesCache extends AbstractFilesCache {
             log.debug("putFile: " + this.getSafeName(fileObject));
         }
 
-        final Map<FileName, Reference<FileObject>> files = 
getOrCreateFilesystemCache(fileObject.getFileSystem());
+        synchronized(this) {
+            final Map<FileName, Reference<FileObject>> files = 
getOrCreateFilesystemCache(fileObject.getFileSystem());
 
-        final Reference<FileObject> ref = createReference(fileObject, 
refQueue);
-        final FileSystemAndNameKey key = new 
FileSystemAndNameKey(fileObject.getFileSystem(), fileObject.getName());
+            final Reference<FileObject> ref = createReference(fileObject, 
refQueue);
+            final FileSystemAndNameKey key = new 
FileSystemAndNameKey(fileObject.getFileSystem(), fileObject.getName());
 
-        lock.lock();
-        try {
             final Reference<FileObject> old = files.put(fileObject.getName(), 
ref);
             if (old != null) {
                 refReverseMap.remove(old);
             }
             refReverseMap.put(ref, key);
-        } finally {
-            lock.unlock();
         }
     }
 
@@ -161,13 +124,12 @@ public class SoftRefFilesCache extends AbstractFilesCache 
{
             log.debug("putFile: " + this.getSafeName(fileObject));
         }
 
-        final Map<FileName, Reference<FileObject>> files = 
getOrCreateFilesystemCache(fileObject.getFileSystem());
+        synchronized(this) {
+            final Map<FileName, Reference<FileObject>> files = 
getOrCreateFilesystemCache(fileObject.getFileSystem());
 
-        final Reference<FileObject> ref = createReference(fileObject, 
refQueue);
-        final FileSystemAndNameKey key = new 
FileSystemAndNameKey(fileObject.getFileSystem(), fileObject.getName());
+            final Reference<FileObject> ref = createReference(fileObject, 
refQueue);
+            final FileSystemAndNameKey key = new 
FileSystemAndNameKey(fileObject.getFileSystem(), fileObject.getName());
 
-        lock.lock();
-        try {
             if (files.containsKey(fileObject.getName()) && 
files.get(fileObject.getName()).get() != null) {
                 return false;
             }
@@ -177,8 +139,6 @@ public class SoftRefFilesCache extends AbstractFilesCache {
             }
             refReverseMap.put(ref, key);
             return true;
-        } finally {
-            lock.unlock();
         }
     }
 
@@ -187,55 +147,43 @@ public class SoftRefFilesCache extends AbstractFilesCache 
{
     }
 
     @Override
-    public FileObject getFile(final FileSystem fileSystem, final FileName 
fileName) {
+    public synchronized FileObject getFile(final FileSystem fileSystem, final 
FileName fileName) {
         final Map<FileName, Reference<FileObject>> files = 
getOrCreateFilesystemCache(fileSystem);
 
-        lock.lock();
-        try {
-            final Reference<FileObject> ref = files.get(fileName);
-            if (ref == null) {
-                return null;
-            }
+        final Reference<FileObject> ref = files.get(fileName);
+        if (ref == null) {
+            return null;
+        }
 
-            final FileObject fo = ref.get();
-            if (fo == null) {
-                removeFile(fileSystem, fileName);
-            }
-            return fo;
-        } finally {
-            lock.unlock();
+        final FileObject fo = ref.get();
+        if (fo == null) {
+            removeFile(fileSystem, fileName);
         }
+        return fo;
     }
 
     @Override
-    public void clear(final FileSystem fileSystem) {
+    public synchronized void clear(final FileSystem fileSystem) {
         final Map<FileName, Reference<FileObject>> files = 
getOrCreateFilesystemCache(fileSystem);
 
-        lock.lock();
-        try {
-            final Iterator<FileSystemAndNameKey> iterKeys = 
refReverseMap.values().iterator();
-            while (iterKeys.hasNext()) {
-                final FileSystemAndNameKey key = iterKeys.next();
-                if (key.getFileSystem() == fileSystem) {
-                    iterKeys.remove();
-                    files.remove(key.getFileName());
-                }
+        final Iterator<FileSystemAndNameKey> iterKeys = 
refReverseMap.values().iterator();
+        while (iterKeys.hasNext()) {
+            final FileSystemAndNameKey key = iterKeys.next();
+            if (key.getFileSystem() == fileSystem) {
+                iterKeys.remove();
+                files.remove(key.getFileName());
             }
+        }
 
-            if (files.isEmpty()) {
-                close(fileSystem);
-            }
-        } finally {
-            lock.unlock();
+        if (files.isEmpty()) {
+            close(fileSystem);
         }
     }
 
     /**
-     * Called while the lock is held
-     *
      * @param fileSystem The file system to close.
      */
-    private void close(final FileSystem fileSystem) {
+    private synchronized void close(final FileSystem fileSystem) {
         if (log.isDebugEnabled()) {
             log.debug("close fs: " + fileSystem.getRootName());
         }
@@ -244,67 +192,57 @@ public class SoftRefFilesCache extends AbstractFilesCache 
{
         if (fileSystemCache.isEmpty()) {
             endThread();
         }
-        /*
-         * This is not thread-safe as another thread might be opening the file 
system ((DefaultFileSystemManager)
-         * getContext().getFileSystemManager()).closeFileSystem(fileSystem);
-         */
     }
 
     @Override
-    public void close() {
+    public synchronized void close() {
         endThread();
 
-        lock.lock();
-        try {
-            fileSystemCache.clear();
+        fileSystemCache.clear();
 
-            refReverseMap.clear();
-        } finally {
-            lock.unlock();
-        }
+        refReverseMap.clear();
     }
 
     @Override
-    public void removeFile(final FileSystem fileSystem, final FileName 
fileName) {
+    public synchronized void removeFile(final FileSystem fileSystem, final 
FileName fileName) {
         if (removeFile(new FileSystemAndNameKey(fileSystem, fileName))) {
             close(fileSystem);
         }
     }
 
-    private boolean removeFile(final FileSystemAndNameKey key) {
+    private synchronized boolean removeFile(final FileSystemAndNameKey key) {
         if (log.isDebugEnabled()) {
             log.debug("removeFile: " + this.getSafeName(key.getFileName()));
         }
 
         final Map<?, ?> files = 
getOrCreateFilesystemCache(key.getFileSystem());
 
-        lock.lock();
-        try {
-            final Object ref = files.remove(key.getFileName());
-            if (ref != null) {
-                refReverseMap.remove(ref);
-            }
+        final Object ref = files.remove(key.getFileName());
+        if (ref != null) {
+            refReverseMap.remove(ref);
+        }
 
-            return files.isEmpty();
-        } finally {
-            lock.unlock();
+        return files.isEmpty();
+    }
+
+    private synchronized void removeFile(final Reference<?> ref) {
+        final FileSystemAndNameKey key = refReverseMap.get(ref);
+
+        if (key != null && removeFile(key)) {
+            close(key.getFileSystem());
         }
     }
 
-    protected Map<FileName, Reference<FileObject>> 
getOrCreateFilesystemCache(final FileSystem fileSystem) {
+    protected synchronized Map<FileName, Reference<FileObject>> 
getOrCreateFilesystemCache(final FileSystem fileSystem) {
         if (fileSystemCache.isEmpty()) {
             startThread();
         }
 
-        Map<FileName, Reference<FileObject>> files;
-
-        do {
-            files = fileSystemCache.get(fileSystem);
-            if (files != null) {
-                break;
-            }
+        Map<FileName, Reference<FileObject>> files = 
fileSystemCache.get(fileSystem);
+        if (files == null) {
             files = new HashMap<>();
-        } while (fileSystemCache.putIfAbsent(fileSystem, files) == null);
+            fileSystemCache.put(fileSystem, files);
+        }
 
         return files;
     }

Reply via email to