From 75b6aced4d310792d820bc9c027563d5c687aa46 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 2 Aug 2023 11:29:27 +0200 Subject: [PATCH 1/3] first impl --- .../frontend/fuse/FuseNioAdapter.java | 7 +++++++ .../org/cryptomator/frontend/fuse/OpenFile.java | 9 +++++++++ .../frontend/fuse/OpenFileFactory.java | 15 +++++++++++++-- .../frontend/fuse/ReadOnlyAdapter.java | 16 ++++++++++------ .../frontend/fuse/ReadWriteAdapter.java | 12 ++++++++---- 5 files changed, 47 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/cryptomator/frontend/fuse/FuseNioAdapter.java b/src/main/java/org/cryptomator/frontend/fuse/FuseNioAdapter.java index f651ee8..5d727c1 100644 --- a/src/main/java/org/cryptomator/frontend/fuse/FuseNioAdapter.java +++ b/src/main/java/org/cryptomator/frontend/fuse/FuseNioAdapter.java @@ -11,6 +11,13 @@ public interface FuseNioAdapter extends FuseOperations, AutoCloseable { */ int DEFAULT_MAX_FILENAMELENGTH = 254; // 255 is preferred, but nautilus checks for this value + 1 + /** + * The default value until an {@link OpenFile} is considered active. + * + * @see OpenFileFactory#hasActiveFiles(long) + */ + long DEFAULT_ACTIVE_THRESHOLD = Long.MAX_VALUE; + /** * Checks if the filesystem is in use (and therefore an unmount attempt should be avoided). *

diff --git a/src/main/java/org/cryptomator/frontend/fuse/OpenFile.java b/src/main/java/org/cryptomator/frontend/fuse/OpenFile.java index 5fc4dd9..25cf6dc 100644 --- a/src/main/java/org/cryptomator/frontend/fuse/OpenFile.java +++ b/src/main/java/org/cryptomator/frontend/fuse/OpenFile.java @@ -10,7 +10,9 @@ import java.nio.file.OpenOption; import java.nio.file.Path; import java.nio.file.attribute.FileAttribute; +import java.time.Instant; import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; public class OpenFile implements Closeable { @@ -18,6 +20,7 @@ public class OpenFile implements Closeable { private final Path path; private final FileChannel channel; + private final AtomicReference lastUsed = new AtomicReference(Instant.now()); private OpenFile(Path path, FileChannel channel) { this.path = path; @@ -39,6 +42,7 @@ static OpenFile create(Path path, Set options, FileAttribu * @throws IOException If an exception occurs during read. */ public int read(ByteBuffer buf, long num, long offset) throws IOException { + lastUsed.set(Instant.now()); long size = channel.size(); if (offset >= size) { return 0; @@ -69,6 +73,7 @@ public int read(ByteBuffer buf, long num, long offset) throws IOException { * @throws IOException If an exception occurs during write. */ public int write(ByteBuffer buf, long num, long offset) throws IOException { + lastUsed.set(Instant.now()); if (num > Integer.MAX_VALUE) { throw new IOException("Requested too many bytes"); } @@ -80,6 +85,10 @@ public int write(ByteBuffer buf, long num, long offset) throws IOException { return written; } + public Instant lastUsed() { + return lastUsed.get(); + } + @Override public void close() throws IOException { channel.close(); diff --git a/src/main/java/org/cryptomator/frontend/fuse/OpenFileFactory.java b/src/main/java/org/cryptomator/frontend/fuse/OpenFileFactory.java index f1471cb..8b0fee6 100644 --- a/src/main/java/org/cryptomator/frontend/fuse/OpenFileFactory.java +++ b/src/main/java/org/cryptomator/frontend/fuse/OpenFileFactory.java @@ -8,6 +8,7 @@ import java.nio.file.OpenOption; import java.nio.file.Path; import java.nio.file.attribute.FileAttribute; +import java.time.Instant; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -67,8 +68,18 @@ public void close(long fileHandle) throws ClosedChannelException, IOException { } } - public int getOpenFileCount(){ - return openFiles.size(); + /** + * Checks, if any active {@link OpenFile} exists. + *

+ * An OpenFile is active, if its read or write methods were called in the last threshold given seconds. + * To get atomicity guarantees this method must be externally synchronized. + * + * @param activeThreshold number of seconds defining the threshold an OpenFile is considered active + * @return {@code true} if at least one active OpenFile exists, otherwise {@code false} + */ + public boolean hasActiveFiles(long activeThreshold) { + var comparsion = Instant.now().minusSeconds(activeThreshold); + return openFiles.values().stream().anyMatch(openFile -> openFile.lastUsed().isAfter(comparsion)); } /** diff --git a/src/main/java/org/cryptomator/frontend/fuse/ReadOnlyAdapter.java b/src/main/java/org/cryptomator/frontend/fuse/ReadOnlyAdapter.java index a4c8064..f622dd6 100644 --- a/src/main/java/org/cryptomator/frontend/fuse/ReadOnlyAdapter.java +++ b/src/main/java/org/cryptomator/frontend/fuse/ReadOnlyAdapter.java @@ -48,9 +48,9 @@ public sealed class ReadOnlyAdapter implements FuseNioAdapter permits ReadWriteA private final ReadOnlyDirectoryHandler dirHandler; private final ReadOnlyFileHandler fileHandler; private final ReadOnlyLinkHandler linkHandler; - private final BooleanSupplier hasOpenFiles; + private final BooleanSupplier hasActiveOpenFiles; - protected ReadOnlyAdapter(Errno errno, Path root, int maxFileNameLength, FileNameTranscoder fileNameTranscoder, FileStore fileStore, OpenFileFactory openFiles, ReadOnlyDirectoryHandler dirHandler, ReadOnlyFileHandler fileHandler) { + protected ReadOnlyAdapter(Errno errno, Path root, int maxFileNameLength, FileNameTranscoder fileNameTranscoder, FileStore fileStore, OpenFileFactory openFiles, long activeOpenFileThreshold, ReadOnlyDirectoryHandler dirHandler, ReadOnlyFileHandler fileHandler) { this.errno = errno; this.root = root; this.maxFileNameLength = maxFileNameLength; @@ -61,21 +61,25 @@ protected ReadOnlyAdapter(Errno errno, Path root, int maxFileNameLength, FileNam this.dirHandler = dirHandler; this.fileHandler = fileHandler; this.linkHandler = new ReadOnlyLinkHandler(fileNameTranscoder); - this.hasOpenFiles = () -> openFiles.getOpenFileCount() != 0; + this.hasActiveOpenFiles = () -> openFiles.hasActiveFiles(30); } - public static ReadOnlyAdapter create(Errno errno, Path root, int maxFileNameLength, FileNameTranscoder fileNameTranscoder) { + public static ReadOnlyAdapter create(Errno errno, Path root, int maxFileNameLength, FileNameTranscoder fileNameTranscoder, long activeOpenFileThreshold) { try { var fileStore = Files.getFileStore(root); var openFiles = new OpenFileFactory(); var dirHandler = new ReadOnlyDirectoryHandler(fileNameTranscoder); var fileHandler = new ReadOnlyFileHandler(openFiles); - return new ReadOnlyAdapter(errno, root, maxFileNameLength, fileNameTranscoder, fileStore, openFiles, dirHandler, fileHandler); + return new ReadOnlyAdapter(errno, root, maxFileNameLength, fileNameTranscoder, fileStore, openFiles, activeOpenFileThreshold, dirHandler, fileHandler); } catch (IOException e) { throw new UncheckedIOException(e); } } + public static ReadOnlyAdapter create(Errno errno, Path root, int maxFileNameLength, FileNameTranscoder fileNameTranscoder) { + return create(errno, root, maxFileNameLength, fileNameTranscoder, DEFAULT_ACTIVE_THRESHOLD); + } + @Override public Errno errno() { return errno; @@ -305,7 +309,7 @@ public void destroy() { @Override public boolean isInUse() { try (PathLock pLock = lockManager.tryLockForWriting("/")) { - return hasOpenFiles.getAsBoolean(); + return hasActiveOpenFiles.getAsBoolean(); } catch (AlreadyLockedException e) { return true; } diff --git a/src/main/java/org/cryptomator/frontend/fuse/ReadWriteAdapter.java b/src/main/java/org/cryptomator/frontend/fuse/ReadWriteAdapter.java index 810c312..e153c9d 100644 --- a/src/main/java/org/cryptomator/frontend/fuse/ReadWriteAdapter.java +++ b/src/main/java/org/cryptomator/frontend/fuse/ReadWriteAdapter.java @@ -38,23 +38,27 @@ public final class ReadWriteAdapter extends ReadOnlyAdapter { private static final Logger LOG = LoggerFactory.getLogger(ReadWriteAdapter.class); private final ReadWriteFileHandler fileHandler; - private ReadWriteAdapter(Errno errno, Path root, int maxFileNameLength, FileNameTranscoder fileNameTranscoder, FileStore fileStore, OpenFileFactory openFiles, ReadWriteDirectoryHandler dirHandler, ReadWriteFileHandler fileHandler) { - super(errno, root, maxFileNameLength, fileNameTranscoder, fileStore, openFiles, dirHandler, fileHandler); + private ReadWriteAdapter(Errno errno, Path root, int maxFileNameLength, FileNameTranscoder fileNameTranscoder, FileStore fileStore, OpenFileFactory openFiles, long activeOpenFileThreshold, ReadWriteDirectoryHandler dirHandler, ReadWriteFileHandler fileHandler) { + super(errno, root, maxFileNameLength, fileNameTranscoder, fileStore, openFiles, activeOpenFileThreshold, dirHandler, fileHandler); this.fileHandler = fileHandler; } - public static ReadWriteAdapter create(Errno errno, Path root, int maxFileNameLength, FileNameTranscoder fileNameTranscoder) { + public static ReadWriteAdapter create(Errno errno, Path root, int maxFileNameLength, FileNameTranscoder fileNameTranscoder, long activeOpenFileThreshold) { try { var fileStore = Files.getFileStore(root); var openFiles = new OpenFileFactory(); var dirHandler = new ReadWriteDirectoryHandler(fileNameTranscoder); var fileHandler = new ReadWriteFileHandler(openFiles); - return new ReadWriteAdapter(errno, root, maxFileNameLength, fileNameTranscoder, fileStore, openFiles, dirHandler, fileHandler); + return new ReadWriteAdapter(errno, root, maxFileNameLength, fileNameTranscoder, fileStore, openFiles, activeOpenFileThreshold, dirHandler, fileHandler); } catch (IOException e) { throw new UncheckedIOException(e); } } + public static ReadWriteAdapter create(Errno errno, Path root, int maxFileNameLength, FileNameTranscoder fileNameTranscoder) { + return create(errno, root, maxFileNameLength, fileNameTranscoder, DEFAULT_ACTIVE_THRESHOLD); + } + @Override public Set supportedOperations() { var ops = EnumSet.copyOf(super.supportedOperations()); From 7d1daf46cfba6a0df2a005d707fad7b3342c183d Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 2 Aug 2023 11:29:59 +0200 Subject: [PATCH 2/3] reduce unnecessary object tunneling --- .../cryptomator/frontend/fuse/ReadOnlyAdapter.java | 13 ++++++------- .../cryptomator/frontend/fuse/ReadWriteAdapter.java | 8 +++++--- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/cryptomator/frontend/fuse/ReadOnlyAdapter.java b/src/main/java/org/cryptomator/frontend/fuse/ReadOnlyAdapter.java index f622dd6..5a6ffe9 100644 --- a/src/main/java/org/cryptomator/frontend/fuse/ReadOnlyAdapter.java +++ b/src/main/java/org/cryptomator/frontend/fuse/ReadOnlyAdapter.java @@ -43,25 +43,23 @@ public sealed class ReadOnlyAdapter implements FuseNioAdapter permits ReadWriteA private final int maxFileNameLength; protected final FileStore fileStore; protected final LockManager lockManager; - protected final OpenFileFactory openFiles; protected final FileNameTranscoder fileNameTranscoder; private final ReadOnlyDirectoryHandler dirHandler; private final ReadOnlyFileHandler fileHandler; private final ReadOnlyLinkHandler linkHandler; - private final BooleanSupplier hasActiveOpenFiles; + private final BooleanSupplier inUseCheck; - protected ReadOnlyAdapter(Errno errno, Path root, int maxFileNameLength, FileNameTranscoder fileNameTranscoder, FileStore fileStore, OpenFileFactory openFiles, long activeOpenFileThreshold, ReadOnlyDirectoryHandler dirHandler, ReadOnlyFileHandler fileHandler) { + protected ReadOnlyAdapter(Errno errno, Path root, int maxFileNameLength, FileNameTranscoder fileNameTranscoder, FileStore fileStore, BooleanSupplier inUseCheck, ReadOnlyDirectoryHandler dirHandler, ReadOnlyFileHandler fileHandler) { this.errno = errno; this.root = root; this.maxFileNameLength = maxFileNameLength; this.fileNameTranscoder = fileNameTranscoder; this.fileStore = fileStore; this.lockManager = new LockManager(); - this.openFiles = openFiles; this.dirHandler = dirHandler; this.fileHandler = fileHandler; this.linkHandler = new ReadOnlyLinkHandler(fileNameTranscoder); - this.hasActiveOpenFiles = () -> openFiles.hasActiveFiles(30); + this.inUseCheck = inUseCheck; } public static ReadOnlyAdapter create(Errno errno, Path root, int maxFileNameLength, FileNameTranscoder fileNameTranscoder, long activeOpenFileThreshold) { @@ -70,7 +68,8 @@ public static ReadOnlyAdapter create(Errno errno, Path root, int maxFileNameLeng var openFiles = new OpenFileFactory(); var dirHandler = new ReadOnlyDirectoryHandler(fileNameTranscoder); var fileHandler = new ReadOnlyFileHandler(openFiles); - return new ReadOnlyAdapter(errno, root, maxFileNameLength, fileNameTranscoder, fileStore, openFiles, activeOpenFileThreshold, dirHandler, fileHandler); + BooleanSupplier inUseCheck = () -> openFiles.hasActiveFiles(activeOpenFileThreshold); + return new ReadOnlyAdapter(errno, root, maxFileNameLength, fileNameTranscoder, fileStore, inUseCheck, dirHandler, fileHandler); } catch (IOException e) { throw new UncheckedIOException(e); } @@ -309,7 +308,7 @@ public void destroy() { @Override public boolean isInUse() { try (PathLock pLock = lockManager.tryLockForWriting("/")) { - return hasActiveOpenFiles.getAsBoolean(); + return inUseCheck.getAsBoolean(); } catch (AlreadyLockedException e) { return true; } diff --git a/src/main/java/org/cryptomator/frontend/fuse/ReadWriteAdapter.java b/src/main/java/org/cryptomator/frontend/fuse/ReadWriteAdapter.java index e153c9d..c555908 100644 --- a/src/main/java/org/cryptomator/frontend/fuse/ReadWriteAdapter.java +++ b/src/main/java/org/cryptomator/frontend/fuse/ReadWriteAdapter.java @@ -29,6 +29,7 @@ import java.nio.file.attribute.PosixFilePermissions; import java.util.EnumSet; import java.util.Set; +import java.util.function.BooleanSupplier; /** * @@ -38,8 +39,8 @@ public final class ReadWriteAdapter extends ReadOnlyAdapter { private static final Logger LOG = LoggerFactory.getLogger(ReadWriteAdapter.class); private final ReadWriteFileHandler fileHandler; - private ReadWriteAdapter(Errno errno, Path root, int maxFileNameLength, FileNameTranscoder fileNameTranscoder, FileStore fileStore, OpenFileFactory openFiles, long activeOpenFileThreshold, ReadWriteDirectoryHandler dirHandler, ReadWriteFileHandler fileHandler) { - super(errno, root, maxFileNameLength, fileNameTranscoder, fileStore, openFiles, activeOpenFileThreshold, dirHandler, fileHandler); + private ReadWriteAdapter(Errno errno, Path root, int maxFileNameLength, FileNameTranscoder fileNameTranscoder, FileStore fileStore, BooleanSupplier inUseCheck, ReadWriteDirectoryHandler dirHandler, ReadWriteFileHandler fileHandler) { + super(errno, root, maxFileNameLength, fileNameTranscoder, fileStore, inUseCheck, dirHandler, fileHandler); this.fileHandler = fileHandler; } @@ -49,7 +50,8 @@ public static ReadWriteAdapter create(Errno errno, Path root, int maxFileNameLen var openFiles = new OpenFileFactory(); var dirHandler = new ReadWriteDirectoryHandler(fileNameTranscoder); var fileHandler = new ReadWriteFileHandler(openFiles); - return new ReadWriteAdapter(errno, root, maxFileNameLength, fileNameTranscoder, fileStore, openFiles, activeOpenFileThreshold, dirHandler, fileHandler); + BooleanSupplier inUseCheck = () -> openFiles.hasActiveFiles(activeOpenFileThreshold); + return new ReadWriteAdapter(errno, root, maxFileNameLength, fileNameTranscoder, fileStore, inUseCheck, dirHandler, fileHandler); } catch (IOException e) { throw new UncheckedIOException(e); } From 4cde4aa658ef3b802561520872f3201207f999cf Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 20 Sep 2023 11:30:26 +0200 Subject: [PATCH 3/3] use AtomicLong to store last used time --- .../java/org/cryptomator/frontend/fuse/OpenFile.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/cryptomator/frontend/fuse/OpenFile.java b/src/main/java/org/cryptomator/frontend/fuse/OpenFile.java index 25cf6dc..26b0875 100644 --- a/src/main/java/org/cryptomator/frontend/fuse/OpenFile.java +++ b/src/main/java/org/cryptomator/frontend/fuse/OpenFile.java @@ -12,7 +12,7 @@ import java.nio.file.attribute.FileAttribute; import java.time.Instant; import java.util.Set; -import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.AtomicLong; public class OpenFile implements Closeable { @@ -20,13 +20,13 @@ public class OpenFile implements Closeable { private final Path path; private final FileChannel channel; - private final AtomicReference lastUsed = new AtomicReference(Instant.now()); + private final AtomicLong lastUsed = new AtomicLong(Instant.now().toEpochMilli()); private OpenFile(Path path, FileChannel channel) { this.path = path; this.channel = channel; } - + static OpenFile create(Path path, Set options, FileAttribute... attrs) throws IOException { FileChannel ch = FileChannel.open(path, options, attrs); return new OpenFile(path, ch); @@ -42,7 +42,7 @@ static OpenFile create(Path path, Set options, FileAttribu * @throws IOException If an exception occurs during read. */ public int read(ByteBuffer buf, long num, long offset) throws IOException { - lastUsed.set(Instant.now()); + lastUsed.set(Instant.now().toEpochMilli()); long size = channel.size(); if (offset >= size) { return 0; @@ -73,7 +73,7 @@ public int read(ByteBuffer buf, long num, long offset) throws IOException { * @throws IOException If an exception occurs during write. */ public int write(ByteBuffer buf, long num, long offset) throws IOException { - lastUsed.set(Instant.now()); + lastUsed.set(Instant.now().toEpochMilli()); if (num > Integer.MAX_VALUE) { throw new IOException("Requested too many bytes"); } @@ -86,7 +86,7 @@ public int write(ByteBuffer buf, long num, long offset) throws IOException { } public Instant lastUsed() { - return lastUsed.get(); + return Instant.ofEpochMilli(lastUsed.get()); } @Override