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 <[email protected]>
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;
}