Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Redefine "inUse" definition #79

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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).
* <p>
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/org/cryptomator/frontend/fuse/OpenFile.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,17 @@
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 {

private static final Logger LOG = LoggerFactory.getLogger(OpenFile.class);

private final Path path;
private final FileChannel channel;
private final AtomicReference<Instant> lastUsed = new AtomicReference(Instant.now());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AtomicLong is more lightweight (e.g. using System.nanoTime() instead of Instant)


private OpenFile(Path path, FileChannel channel) {
this.path = path;
Expand All @@ -39,6 +42,7 @@ static OpenFile create(Path path, Set<? extends OpenOption> 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;
Expand Down Expand Up @@ -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");
}
Expand All @@ -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();
Expand Down
15 changes: 13 additions & 2 deletions src/main/java/org/cryptomator/frontend/fuse/OpenFileFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
* <p>
* 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));
}

/**
Expand Down
19 changes: 11 additions & 8 deletions src/main/java/org/cryptomator/frontend/fuse/ReadOnlyAdapter.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,39 +43,42 @@ 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 hasOpenFiles;
private final BooleanSupplier inUseCheck;

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, 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.hasOpenFiles = () -> openFiles.getOpenFileCount() != 0;
this.inUseCheck = inUseCheck;
}

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);
BooleanSupplier inUseCheck = () -> openFiles.hasActiveFiles(activeOpenFileThreshold);
return new ReadOnlyAdapter(errno, root, maxFileNameLength, fileNameTranscoder, fileStore, inUseCheck, 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;
Expand Down Expand Up @@ -305,7 +308,7 @@ public void destroy() {
@Override
public boolean isInUse() {
try (PathLock pLock = lockManager.tryLockForWriting("/")) {
return hasOpenFiles.getAsBoolean();
return inUseCheck.getAsBoolean();
} catch (AlreadyLockedException e) {
return true;
}
Expand Down
14 changes: 10 additions & 4 deletions src/main/java/org/cryptomator/frontend/fuse/ReadWriteAdapter.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import java.nio.file.attribute.PosixFilePermissions;
import java.util.EnumSet;
import java.util.Set;
import java.util.function.BooleanSupplier;

/**
*
Expand All @@ -38,23 +39,28 @@ 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, BooleanSupplier inUseCheck, ReadWriteDirectoryHandler dirHandler, ReadWriteFileHandler fileHandler) {
super(errno, root, maxFileNameLength, fileNameTranscoder, fileStore, inUseCheck, 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);
BooleanSupplier inUseCheck = () -> openFiles.hasActiveFiles(activeOpenFileThreshold);
return new ReadWriteAdapter(errno, root, maxFileNameLength, fileNameTranscoder, fileStore, inUseCheck, 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<Operation> supportedOperations() {
var ops = EnumSet.copyOf(super.supportedOperations());
Expand Down