Skip to content

Commit

Permalink
Bump default read ahead size to 4MiB and make it configurable
Browse files Browse the repository at this point in the history
  • Loading branch information
SailReal committed Dec 21, 2021
1 parent da44e8b commit a5f7253
Show file tree
Hide file tree
Showing 4 changed files with 22 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class CloudAccessFSConfig {
private static final long DEFAULT_TOTAL_QUOTA = 1_000_000_000; // 1 GB
private static final long DEFAULT_AVAILABLE_QUOTA = 500_000_000; // 500 MB
private static final int DEFAULT_IDLE_FILE_TIMEOUT = 20;
public static final int DEFAULT_READAHEAD_SIZE = 1024 * 1024 * 4; // 4 MiB
private static final String DEFAULT_CACHE_DIR = System.getProperty("java.io.tmpdir") + "/fcaCache";
private static final String DEFAULT_LOST_AND_FOUND_DIR = "lostAndFound";
private static final String DEFAULT_UPLOAD_DIR = "/58a230a40ae05cee64dfc0680d920e1e";
Expand All @@ -20,6 +21,7 @@ public class CloudAccessFSConfig {
private final long totalQuota;
private final long availableQuota;
private final int idleFileTimeoutSeconds;
private final int readAheadBytes;
private final String cacheDir;
private final String lostAndFoundDir;
private final String uploadDir;
Expand All @@ -28,6 +30,7 @@ public class CloudAccessFSConfig {
CloudAccessFSConfig() {
this.pendingUploadTimeoutSeconds = Integer.getInteger("org.cryptomator.fusecloudaccess.pendingUploadsTimeoutSeconds", DEFAULT_PENDING_UPLOAD_TIMEOUT);
this.idleFileTimeoutSeconds = Integer.getInteger("org.cryptomator.fusecloudaccess.idleFileTimeoutSeconds", DEFAULT_IDLE_FILE_TIMEOUT);
this.readAheadBytes = Integer.getInteger("org.cryptomator.fusecloudaccess.readAheadBytes", DEFAULT_READAHEAD_SIZE);
this.totalQuota = Long.getLong("org.cryptomator.fusecloudaccess.totalQuota", DEFAULT_TOTAL_QUOTA);
this.availableQuota = Long.getLong("org.cryptomator.fusecloudaccess.availableQuota", DEFAULT_AVAILABLE_QUOTA);
this.cacheDir = System.getProperty("org.cryptomator.fusecloudaccess.cacheDir", DEFAULT_CACHE_DIR);
Expand All @@ -43,6 +46,10 @@ public int getIdleFileTimeoutSeconds() {
return idleFileTimeoutSeconds;
}

public int getReadAheadBytes() {
return readAheadBytes;
}

public long getTotalQuota() {
return totalQuota;
}
Expand Down
11 changes: 6 additions & 5 deletions src/main/java/org/cryptomator/fusecloudaccess/OpenFile.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,21 +41,21 @@
class OpenFile implements Closeable {

private static final Logger LOG = LoggerFactory.getLogger(OpenFile.class);
private static final int READAHEAD_SIZE = 1024 * 1024; // 1 MiB TODO: should this be configurable?

private final CompletableAsynchronousFileChannel fc;
private final CloudProvider provider;
private final RangeSet<Long> populatedRanges;
private final RangeMap<Long, CompletionStage<Void>> activeRequests;
private final AtomicInteger openFileHandleCount;
private final AtomicReference<OpenFile.State> state;
private final int readAheadBytes;
private volatile CloudPath path;
private Instant lastModified;

public enum State {UNMODIFIED, NEEDS_UPLOAD, UPLOADING, NEEDS_REUPLOAD}

// visible for testing
OpenFile(CloudPath path, CompletableAsynchronousFileChannel fc, CloudProvider provider, RangeSet<Long> populatedRanges, RangeMap<Long, CompletionStage<Void>> activeRequests, Instant initialLastModified) {
OpenFile(CloudPath path, CompletableAsynchronousFileChannel fc, CloudProvider provider, RangeSet<Long> populatedRanges, RangeMap<Long, CompletionStage<Void>> activeRequests, Instant initialLastModified, int readAheadBytes) {
this.path = path;
this.fc = fc;
this.provider = provider;
Expand All @@ -64,6 +64,7 @@ public enum State {UNMODIFIED, NEEDS_UPLOAD, UPLOADING, NEEDS_REUPLOAD}
this.openFileHandleCount = new AtomicInteger();
this.state = new AtomicReference<>(State.UNMODIFIED);
this.lastModified = initialLastModified;
this.readAheadBytes = readAheadBytes;
}

/**
Expand All @@ -76,7 +77,7 @@ public enum State {UNMODIFIED, NEEDS_UPLOAD, UPLOADING, NEEDS_REUPLOAD}
* @return The created file
* @throws IOException I/O errors during creation of the cache file located at <code>tmpFilePath</code>
*/
public static OpenFile create(CloudPath path, Path tmpFilePath, CloudProvider provider, long initialSize) throws IOException {
public static OpenFile create(CloudPath path, Path tmpFilePath, CloudProvider provider, long initialSize, int readAheadBytes) throws IOException {
var fc = AsynchronousFileChannel.open(tmpFilePath, READ, WRITE, CREATE_NEW, SPARSE, DELETE_ON_CLOSE);
if (initialSize > 0) {
try {
Expand All @@ -88,7 +89,7 @@ public static OpenFile create(CloudPath path, Path tmpFilePath, CloudProvider pr
throw new IOException("Failed to create file", e);
}
}
return new OpenFile(path, new CompletableAsynchronousFileChannel(fc), provider, TreeRangeSet.create(), TreeRangeMap.create(), Instant.now());
return new OpenFile(path, new CompletableAsynchronousFileChannel(fc), provider, TreeRangeSet.create(), TreeRangeMap.create(), Instant.now(), readAheadBytes);
}

public AtomicInteger getOpenFileHandleCount() {
Expand Down Expand Up @@ -233,7 +234,7 @@ CompletionStage<Void> load(long offset, long count) {
if (requiredRange.isEmpty() || populatedRanges.encloses(requiredRange)) {
return CompletableFuture.completedFuture(null);
} else {
var desiredCount = Math.max(count, READAHEAD_SIZE); // reads at least the readahead
var desiredCount = Math.max(count, readAheadBytes); // reads at least the readahead
var desiredLastByte = Math.min(size, offset + desiredCount); // reads not behind eof (lastByte is exclusive!)
var desiredRange = Range.closedOpen(offset, desiredLastByte);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class OpenFileFactory {
private final Path cacheDir;
private final ScheduledExecutorService scheduler;
private final int keepIdleFileSeconds;
private final int readAheadBytes;

@Inject
OpenFileFactory(@Named("openFiles") ConcurrentMap<CloudPath, OpenFile> openFiles, CloudProvider provider, CloudAccessFSConfig config, OpenFileUploader uploader, ScheduledExecutorService scheduler) {
Expand All @@ -52,6 +53,7 @@ class OpenFileFactory {
this.provider = provider;
this.uploader = uploader;
this.cacheDir = config.getCacheDir();
this.readAheadBytes = config.getReadAheadBytes();
this.scheduler = scheduler;
this.keepIdleFileSeconds = config.getIdleFileTimeoutSeconds();
}
Expand Down Expand Up @@ -86,7 +88,7 @@ public long open(CloudPath path, Set<OpenFlags> flags, long initialSize, Instant
OpenFile createOpenFile(CloudPath path, long initialSize) {
try {
var tmpFile = cacheDir.resolve(UUID.randomUUID().toString());
return OpenFile.create(path, tmpFile, provider, initialSize);
return OpenFile.create(path, tmpFile, provider, initialSize, readAheadBytes);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
Expand Down
10 changes: 6 additions & 4 deletions src/test/java/org/cryptomator/fusecloudaccess/OpenFileTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,16 @@ public class OpenFileTest {
private RangeSet<Long> populatedRanges;
private RangeMap<Long, CompletionStage<Void>> activeRequests;

public static final int DEFAULT_READAHEAD_SIZE = 1024 * 1024 * 4; // 4 MiB

@BeforeEach
public void setup() throws IOException {
this.file = Mockito.mock(CloudPath.class, "/path/to/file");
this.provider = Mockito.mock(CloudProvider.class);
this.fileChannel = Mockito.mock(CompletableAsynchronousFileChannel.class);
this.populatedRanges = Mockito.spy(TreeRangeSet.create());
this.activeRequests = Mockito.spy(TreeRangeMap.create());
this.openFile = new OpenFile(file, fileChannel, provider, populatedRanges, activeRequests, Instant.EPOCH);
this.openFile = new OpenFile(file, fileChannel, provider, populatedRanges, activeRequests, Instant.EPOCH, DEFAULT_READAHEAD_SIZE);
Mockito.when(fileChannel.size()).thenReturn(100l);
Mockito.when(fileChannel.isOpen()).thenReturn(true);
}
Expand All @@ -60,7 +62,7 @@ public void setup() throws IOException {
@ValueSource(longs = {0l, 1l, 42l})
public void testCreate(long size, @TempDir Path tmpDir) throws IOException {
Path tmpFile = tmpDir.resolve("cache.file");
try (var cachedFile = OpenFile.create(file, tmpFile, provider, size)) {
try (var cachedFile = OpenFile.create(file, tmpFile, provider, size, DEFAULT_READAHEAD_SIZE)) {
Assertions.assertNotNull(cachedFile);
Assertions.assertEquals(size, cachedFile.getSize());
}
Expand All @@ -73,7 +75,7 @@ public void testPersist(@TempDir Path tmpDir) throws IOException {
Path tmpFile = tmpDir.resolve("cache.file");
Path persistentFile = tmpDir.resolve("persistent.file");

try (var cachedFile = OpenFile.create(file, tmpFile, provider, 0)) {
try (var cachedFile = OpenFile.create(file, tmpFile, provider, 0, DEFAULT_READAHEAD_SIZE)) {
cachedFile.truncate(100l);
Assertions.assertTimeoutPreemptively(Duration.ofMillis(100), () -> cachedFile.persistTo(persistentFile).toCompletableFuture().get());
}
Expand Down Expand Up @@ -453,7 +455,7 @@ public class Merge {
public void setup() {
var prePopulatedRanges = ImmutableRangeSet.of(Range.closedOpen(0l, 50l));
populatedRanges = Mockito.spy(TreeRangeSet.create(prePopulatedRanges));
openFile = new OpenFile(file, fileChannel, provider, populatedRanges, activeRequests, Instant.EPOCH);
openFile = new OpenFile(file, fileChannel, provider, populatedRanges, activeRequests, Instant.EPOCH, DEFAULT_READAHEAD_SIZE);
this.fileSpy = Mockito.spy(openFile);
}

Expand Down

0 comments on commit a5f7253

Please sign in to comment.