Skip to content

Conversation

@infeo
Copy link
Member

@infeo infeo commented Oct 10, 2025

This PR implements the Files-in-use feature.

Problem

Cryptofs encrypts each file by its own, in order to be easily used for often synced files (i.e. synced with a cloud storage provider). A common scenario nowadays is, that multiple people work on the same synced files at the same time but at different locations. This leads to conflicts, which are normally resolved by the sync algorithms (and Cryptomator. But some applications simply overwrite newer versions without asking. Also, if the files reside on a network shared location, you have direct access and hence conflicts will not be detected.

Solution

Every time a file channel is opened or an existing file is overwritten/deleted, we first check if the file is currently used by another party. "Used" means, that an encrypted helper file with the same cipher file name and file extension .c9u exists, containing an owner string and a lastUpdated timestamp. If

  • the owner is not the same as this filesystem and
  • last updated time is recent enough (see below)

the file is considered as in use. If the file is used, io operations throw a special exception (FileAlreadyInUseException). Additionally, an event is triggered.

An in-use file is created, if a file channel with WRITE option is successfully opened to a file. The owner is set to the string handed over in the CryptoFileSystemProperties. The inUseFile has the same lifetime as the OpenCryptoFile and if the latter is closed, also the inUseFile is closed (and deleted). To mitigate performance drops, the first 5 seconds the inUseFile only exists in memory and afterwards it is persisted on disk. If during the "virtual" time the file channel is closed again , no file is created.

Every 5min, all open inUseFiles are refreshed, i.e. rewritten with an updated "lastUpdated" timestamp. If the lastUpdated time is 10min ago, it is considered stale and the file can be stolen by other users.

If a file is in-use , the consumer can "unblock" this state for the next accesses over a short amount of time. If during this time a file channel is openened, any existing inUseFile will be stolen, i.e. after successful opening the file is overwritten with this cryptofilesystem owner.

The whole inUse system is deactivated if no filesystem owner is specified in the CryptoFileSystemProperties. This is done by using a StubInUseManager.

Todos

  • Health check removing all in-use files
  • Conflict resolution of in-use-files

infeo added 30 commits July 30, 2025 13:41
* first read in-use-file
* on failure create/own it
* more static methods
* renaming methods
* rename FileIsInUseException to FileAlreadyInUseException
* rename InUse:tryMarkInUse to acquire()
* add ignore flag for inUse check
* close token on failed file creation
* add logging
* only move file if file is already created
* delete file on close
* add factory method to create InvalidToken
* add instance variable owner
* define in-use-file-creation threshold as a constant
* create interface
* sublass interface by "fake" impl and real impl
* decide on cryptofilesystem proeprties what impl to use
* create interface UseToken
* subclass it with real and stub impl
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/test/java/org/cryptomator/cryptofs/inuse/RealInUseManagerTest.java (1)

358-358: Add direct unit tests for the validate() method.

The TODO indicates that the validate() method in RealInUseManager lacks dedicated unit tests. While the IsInUseUseInfo nested class tests indirectly verify that valid UseInfo objects are produced, there are no tests that directly exercise validate() to ensure it properly handles edge cases such as:

  • Missing OWNER_KEY or LASTUPDATED_KEY
  • Null or blank owner values
  • Invalid timestamp formats
  • Properties with unexpected keys or malformed content

Do you want me to generate unit tests for the validate() method to ensure comprehensive coverage of these edge cases?

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7dd7540 and fc70e09.

📒 Files selected for processing (7)
  • src/main/java/org/cryptomator/cryptofs/DirectoryIdBackup.java (3 hunks)
  • src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java (1 hunks)
  • src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (1 hunks)
  • src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (1 hunks)
  • src/main/java/org/cryptomator/cryptofs/inuse/package-info.java (1 hunks)
  • src/test/java/org/cryptomator/cryptofs/inuse/RealInUseManagerTest.java (1 hunks)
  • src/test/java/org/cryptomator/cryptofs/inuse/RealUseTokenTest.java (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/main/java/org/cryptomator/cryptofs/inuse/package-info.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java
  • src/test/java/org/cryptomator/cryptofs/inuse/RealUseTokenTest.java
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: infeo
PR: cryptomator/cryptofs#312
File: src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java:44-46
Timestamp: 2025-10-14T16:55:45.287Z
Learning: In `src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java`, the interface includes default implementations (including `use()` returning `INIT_TOKEN`) because there are only two implementations—StubInUseManager (stub) and RealInUseManager (real)—with no plans for additional implementations. This design is intentional and acceptable.
📚 Learning: 2025-10-14T16:55:45.287Z
Learnt from: infeo
PR: cryptomator/cryptofs#312
File: src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java:44-46
Timestamp: 2025-10-14T16:55:45.287Z
Learning: In `src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java`, the interface includes default implementations (including `use()` returning `INIT_TOKEN`) because there are only two implementations—StubInUseManager (stub) and RealInUseManager (real)—with no plans for additional implementations. This design is intentional and acceptable.

Applied to files:

  • src/test/java/org/cryptomator/cryptofs/inuse/RealInUseManagerTest.java
  • src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java
  • src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java
🧬 Code graph analysis (4)
src/main/java/org/cryptomator/cryptofs/DirectoryIdBackup.java (1)
src/main/java/org/cryptomator/cryptofs/common/EncryptedChannels.java (1)
  • EncryptedChannels (9-22)
src/test/java/org/cryptomator/cryptofs/inuse/RealInUseManagerTest.java (3)
src/main/java/org/cryptomator/cryptofs/common/EncryptedChannels.java (1)
  • EncryptedChannels (9-22)
src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (1)
  • RealInUseManager (38-229)
src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (1)
  • RealUseToken (35-206)
src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (4)
src/main/java/org/cryptomator/cryptofs/common/CacheUtils.java (1)
  • CacheUtils (8-57)
src/main/java/org/cryptomator/cryptofs/common/Constants.java (1)
  • Constants (11-41)
src/main/java/org/cryptomator/cryptofs/common/EncryptedChannels.java (1)
  • EncryptedChannels (9-22)
src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (1)
  • RealUseToken (35-206)
src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (3)
src/main/java/org/cryptomator/cryptofs/common/Constants.java (1)
  • Constants (11-41)
src/main/java/org/cryptomator/cryptofs/common/EncryptedChannels.java (1)
  • EncryptedChannels (9-22)
src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (1)
  • RealInUseManager (38-229)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyse
  • GitHub Check: Build and Test
🔇 Additional comments (10)
src/main/java/org/cryptomator/cryptofs/DirectoryIdBackup.java (3)

5-5: LGTM! Clean refactoring to centralized utility.

The import addition supports the refactoring that eliminates local wrapper methods in favor of the centralized EncryptedChannels utility class. This reduces code duplication and improves maintainability.


37-42: LGTM! Correct delegation to centralized utility.

The write method now correctly delegates encryption channel creation to EncryptedChannels.wrapEncryptionAround, eliminating the local wrapper method. The method signature and behavior are preserved.


65-83: LGTM! Correct delegation to centralized utility.

The read method now correctly delegates decryption channel creation to EncryptedChannels.wrapDecryptionAround, eliminating the local wrapper method. The method signature and behavior are preserved, and the refactoring aligns with the broader PR objective to centralize channel provisioning.

src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (4)

129-129: Address the TODO for validate() testing.

This TODO corresponds to line 358 in RealInUseManagerTest.java where direct unit tests for the validate() method are missing. Consider adding comprehensive unit tests to ensure all edge cases (missing keys, null/blank values, invalid timestamps) are properly covered.

Based on the TODO comment in the test file.


50-67: LGTM!

The constructor properly initializes caches with reasonable TTLs (2 minutes for ignored files, 5 seconds for use info) and sets up scheduled refresh of tokens every 5 minutes. The cache sizes (100 for ignored, 1000 for useInfo) are appropriate for the use case.


110-127: LGTM!

The readInUseFile() method correctly:

  • Allocates a fixed-size buffer (INUSE_CLEARTEXT_SIZE)
  • Opens and wraps the channel with decryption
  • Checks for empty files (readBytes < 0)
  • Loads Properties from the decrypted content

186-202: LGTM!

The createInternal() method properly handles all file state scenarios:

  • Existing valid in-use file → throws FileAlreadyInUseException
  • Non-existent file → creates new token with CREATE_NEW
  • Invalid file → steals ownership with TRUNCATE_EXISTING
  • IO errors → wraps as UncheckedIOException

Cache invalidation on successful creation is correct.

src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (3)

62-86: LGTM!

The constructor correctly:

  • Maps ActivationType to appropriate OpenOption sets (CREATECREATE_NEW, STEALTRUNCATE_EXISTING)
  • Handles NONE activation by marking as closed immediately
  • Schedules delayed creation after INUSE_DELAY_MILLIS using a virtual thread executor

135-161: LGTM!

The moveToInternal() method correctly:

  • Synchronizes with the fileCreationSync lock
  • Only moves the file if the channel exists (file was persisted)
  • Updates both the map and the filePath field atomically
  • Handles errors by logging and closing to prevent invalid states

168-194: LGTM!

The close() method properly:

  • Prevents double-close with the closed flag
  • Cancels the delayed creation task
  • Closes the channel and deletes the file atomically within the map compute
  • Logs failures without throwing (appropriate for cleanup code)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (3)
src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (2)

168-171: Include exception in warn log for deletion failure

Current log drops the exception; add it for diagnostics.

-						LOG.warn("Failed to delete inUse File {}. Must be deleted manually.", path);
+						LOG.warn("Failed to delete in-use file {}. Must be deleted manually.", path, e);

52-53: Use ReentrantLock instead of WriteLock (simpler and clearer)

A plain mutual-exclusion is sufficient; ReentrantLock reduces complexity and intent is clearer.

If acceptable, I can provide a small patch to switch to ReentrantLock.

src/test/java/org/cryptomator/cryptofs/inuse/RealUseTokenTest.java (1)

30-41: Add tests for refresh() null-safety (before creation and after close)

Current tests don’t cover calling refresh when channel is null. This would have caught the NPE in RealUseToken.refresh().

Add:

@Test
@DisplayName("refresh before creation does not throw")
public void testRefreshBeforeCreation() {
	var filePath = tmpDir.resolve("inUse.file");
	try (var token = new RealUseToken(filePath, "test3000", cryptor, useTokens, StandardOpenOption.CREATE_NEW, encWrapper)) {
		// Immediately refresh before delayed creation -> no NPE
		Assertions.assertDoesNotThrow(token::refresh);
	}
}

@Test
@DisplayName("refresh after close does not throw")
public void testRefreshAfterClose() {
	var filePath = tmpDir.resolve("inUse.file");
	var token = new RealUseToken(filePath, "test3000", cryptor, useTokens, StandardOpenOption.CREATE_NEW, encWrapper);
	token.close();
	Assertions.assertDoesNotThrow(token::refresh);
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fc70e09 and e5fb49e.

📒 Files selected for processing (2)
  • src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (1 hunks)
  • src/test/java/org/cryptomator/cryptofs/inuse/RealUseTokenTest.java (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: infeo
PR: cryptomator/cryptofs#312
File: src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java:44-46
Timestamp: 2025-10-14T16:55:45.287Z
Learning: In `src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java`, the interface includes default implementations (including `use()` returning `INIT_TOKEN`) because there are only two implementations—StubInUseManager (stub) and RealInUseManager (real)—with no plans for additional implementations. This design is intentional and acceptable.
📚 Learning: 2025-10-14T16:55:45.287Z
Learnt from: infeo
PR: cryptomator/cryptofs#312
File: src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java:44-46
Timestamp: 2025-10-14T16:55:45.287Z
Learning: In `src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java`, the interface includes default implementations (including `use()` returning `INIT_TOKEN`) because there are only two implementations—StubInUseManager (stub) and RealInUseManager (real)—with no plans for additional implementations. This design is intentional and acceptable.

Applied to files:

  • src/test/java/org/cryptomator/cryptofs/inuse/RealUseTokenTest.java
  • src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java
🧬 Code graph analysis (2)
src/test/java/org/cryptomator/cryptofs/inuse/RealUseTokenTest.java (1)
src/main/java/org/cryptomator/cryptofs/common/Constants.java (1)
  • Constants (11-41)
src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (3)
src/main/java/org/cryptomator/cryptofs/common/Constants.java (1)
  • Constants (11-41)
src/main/java/org/cryptomator/cryptofs/common/EncryptedChannels.java (1)
  • EncryptedChannels (9-22)
src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (1)
  • RealInUseManager (38-229)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyse
  • GitHub Check: Build and Test

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java (1)

6-13: Clarify "in use" definition in interface Javadoc.

The bullets describe what InUseManager does but don't define when a file is considered "in use by others." Per the implementation, a file is in use when: (1) an in-use file exists, (2) it's owned by a different owner than this filesystem, and (3) lastUpdated is within the staleness threshold. Rephrase the first bullet to make this explicit.

Based on past review comments.

src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (2)

108-118: Add defensive size check for Properties payload.

If the serialized Properties exceeds INUSE_CLEARTEXT_SIZE (e.g., long owner string), readers will silently truncate, causing validation failures. Add a check after line 115 to log a warning or throw if rawInfo.size() > Constants.INUSE_CLEARTEXT_SIZE.

Apply this diff:

 int writeInUseFile() throws IOException {
     try (var nonClosingWrapper = new NonClosingByteChannel(channel); //
          var encChannel = encWrapper.wrapWithEncryption(nonClosingWrapper, cryptor)) {
         var rawInfo = new ByteArrayOutputStream(Constants.INUSE_CLEARTEXT_SIZE);
         var prop = new Properties();
         prop.put(UseToken.OWNER_KEY, owner);
         prop.put(UseToken.LASTUPDATED_KEY, Instant.now().toString());
         prop.store(rawInfo, null);
+        var data = rawInfo.toByteArray();
+        if (data.length > Constants.INUSE_CLEARTEXT_SIZE) {
+            throw new IOException("In-use file payload (%d bytes) exceeds buffer size (%d bytes). Shorten owner string.".formatted(data.length, Constants.INUSE_CLEARTEXT_SIZE));
+        }
-        return encChannel.write(ByteBuffer.wrap(rawInfo.toByteArray()));
+        return encChannel.write(ByteBuffer.wrap(data));
     }
 }

Based on past review comments.


93-106: Critical: refresh() positions channel AFTER write, leaving trailing bytes.

Line 100 calls channel.position(0) AFTER writeInUseFile() completes, which doesn't prevent stale data. If the new Properties payload is shorter than the previous write, trailing bytes will remain and corrupt the file. Position and truncate the channel BEFORE writing.

Apply this diff:

 void refresh() {
     fileCreationSync.lock();
     try {
         if (closed || channel == null) {
             return;
         }
+        channel.position(0);
+        channel.truncate(0);
         writeInUseFile();
-        channel.position(0);
     } catch (IOException e) {
         LOG.debug("Failed to refresh in-use file {}.", filePath, e);
     } finally {
         fileCreationSync.unlock();
     }
 }
🧹 Nitpick comments (1)
src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (1)

132-132: Remove stale TODO comment.

The validate() method is already tested via RealInUseManagerTest (nested class IsInUseUseInfo and other test methods that call readInUseFile() or isInUse() which invoke validate()). Remove the TODO.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8fb283b and b65c0ea.

📒 Files selected for processing (7)
  • src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFile.java (9 hunks)
  • src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java (1 hunks)
  • src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (1 hunks)
  • src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (1 hunks)
  • src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFileTest.java (7 hunks)
  • src/test/java/org/cryptomator/cryptofs/inuse/RealInUseManagerTest.java (1 hunks)
  • src/test/java/org/cryptomator/cryptofs/inuse/RealUseTokenTest.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFile.java
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: infeo
PR: cryptomator/cryptofs#312
File: src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java:44-46
Timestamp: 2025-10-14T16:55:45.287Z
Learning: In `src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java`, the interface includes default implementations (including `use()` returning `INIT_TOKEN`) because there are only two implementations—StubInUseManager (stub) and RealInUseManager (real)—with no plans for additional implementations. This design is intentional and acceptable.
📚 Learning: 2025-10-14T16:55:45.287Z
Learnt from: infeo
PR: cryptomator/cryptofs#312
File: src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java:44-46
Timestamp: 2025-10-14T16:55:45.287Z
Learning: In `src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java`, the interface includes default implementations (including `use()` returning `INIT_TOKEN`) because there are only two implementations—StubInUseManager (stub) and RealInUseManager (real)—with no plans for additional implementations. This design is intentional and acceptable.

Applied to files:

  • src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java
  • src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java
  • src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java
  • src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFileTest.java
  • src/test/java/org/cryptomator/cryptofs/inuse/RealInUseManagerTest.java
🧬 Code graph analysis (4)
src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (3)
src/main/java/org/cryptomator/cryptofs/common/Constants.java (1)
  • Constants (11-41)
src/main/java/org/cryptomator/cryptofs/common/EncryptedChannels.java (1)
  • EncryptedChannels (9-22)
src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (1)
  • RealInUseManager (39-233)
src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (4)
src/main/java/org/cryptomator/cryptofs/common/CacheUtils.java (1)
  • CacheUtils (8-57)
src/main/java/org/cryptomator/cryptofs/common/Constants.java (1)
  • Constants (11-41)
src/main/java/org/cryptomator/cryptofs/common/EncryptedChannels.java (1)
  • EncryptedChannels (9-22)
src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (1)
  • RealUseToken (36-211)
src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFileTest.java (2)
src/main/java/org/cryptomator/cryptofs/inuse/FileAlreadyInUseException.java (1)
  • FileAlreadyInUseException (6-11)
src/main/java/org/cryptomator/cryptofs/EffectiveOpenOptions.java (1)
  • EffectiveOpenOptions (29-189)
src/test/java/org/cryptomator/cryptofs/inuse/RealInUseManagerTest.java (3)
src/main/java/org/cryptomator/cryptofs/common/EncryptedChannels.java (1)
  • EncryptedChannels (9-22)
src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (1)
  • RealInUseManager (39-233)
src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (1)
  • RealUseToken (36-211)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyse
  • GitHub Check: Build and Test
🔇 Additional comments (3)
src/test/java/org/cryptomator/cryptofs/inuse/RealUseTokenTest.java (1)

1-257: LGTM! Comprehensive test coverage for RealUseToken.

The test suite thoroughly exercises token lifecycle, including delayed persistence, file operations, move semantics, refresh behavior, and error handling. The use of Awaitility for timing assertions and extensive mocking is appropriate for this component.

src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFileTest.java (1)

68-271: LGTM! Proper integration testing for in-use feature.

The updated tests correctly verify InUseManager integration with OpenCryptoFile, covering token lifecycle scenarios, read-only bypass, token acquisition on closed state, exception handling, and path update operations.

src/test/java/org/cryptomator/cryptofs/inuse/RealInUseManagerTest.java (1)

1-367: LGTM! Thorough test coverage for RealInUseManager.

The test suite comprehensively exercises all RealInUseManager paths, including cache interactions, validation logic, token creation strategies, error handling, and edge cases. The nested test structure and extensive mocking appropriately test the component in isolation.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
pom.xml (1)

162-167: Dependency addition is well‑justified for async testing.

Awaitility is an appropriate choice for testing the asynchronous state transitions in the in‑use feature (delayed persistence, refresh intervals, token lifecycle). The dependency is properly scoped and pinned.

For consistency with other test dependencies (junit, mockito, hamcrest, jimfs), consider defining the version as a property variable:

<awaitility.version>4.3.0</awaitility.version>

Then reference it in the dependency. This is a minor style preference and can be addressed now or deferred.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b65c0ea and 5eb9191.

📒 Files selected for processing (1)
  • pom.xml (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: infeo
PR: cryptomator/cryptofs#312
File: src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java:44-46
Timestamp: 2025-10-14T16:55:45.287Z
Learning: In `src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java`, the interface includes default implementations (including `use()` returning `INIT_TOKEN`) because there are only two implementations—StubInUseManager (stub) and RealInUseManager (real)—with no plans for additional implementations. This design is intentional and acceptable.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyse
  • GitHub Check: Build and Test

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java (2)

654-668: Remove unused mock variable.

The inUsePath mock declared on line 656 is never wired into the test setup (no when() stubs) and the verification on line 667 doesn't add meaningful coverage since it's not connected to any code path.

Apply this diff:

 	@Test
 	public void testDeleteShortenedExistingFile() throws IOException {
-		var inUsePath = mock(Path.class, "in use file");
 		when(cryptoPathMapper.getCiphertextFileType(cleartextPath)).thenReturn(CiphertextFileType.FILE);
 		when(physicalFsProv.deleteIfExists(ciphertextRawPath)).thenReturn(true);
 		doNothing().when(openCryptoFiles).delete(Mockito.any());
 		when(ciphertextPath.isShortened()).thenReturn(true);
 
 		inTest.delete(cleartextPath);
 
 		verify(readonlyFlag).assertWritable();
 		verify(openCryptoFiles).delete(ciphertextFilePath);
 		verify(physicalFsProv).deleteIfExists(ciphertextRawPath);
-		verify(physicalFsProv, never()).deleteIfExists(inUsePath);
 	}

670-685: Remove unused mock variable.

The inUsePath mock declared on line 672 is never wired into the test setup and the verification on line 684 doesn't add meaningful coverage.

Apply this diff:

 	@Test
 	public void testDeleteInUseFileThrows() throws IOException {
-		var inUsePath = mock(Path.class, "in use file");
 		when(cryptoPathMapper.getCiphertextFileType(cleartextPath)).thenReturn(CiphertextFileType.FILE);
 		when(physicalFsProv.deleteIfExists(ciphertextRawPath)).thenReturn(true);
 		doNothing().when(openCryptoFiles).delete(Mockito.any());
 		when(ciphertextPath.isShortened()).thenReturn(false);
 		var inTestSpy = spy(inTest);
 		doThrow(FileAlreadyInUseException.class).when(inTestSpy).checkUsage(cleartextPath, ciphertextPath);
 
 		Assertions.assertThrows(FileAlreadyInUseException.class, () -> inTestSpy.delete(cleartextPath));
 
 		verify(openCryptoFiles, never()).delete(ciphertextFilePath);
 		verify(physicalFsProv, never()).deleteIfExists(ciphertextRawPath);
-		verify(physicalFsProv, never()).deleteIfExists(inUsePath);
 	}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5eb9191 and 7a2b548.

📒 Files selected for processing (2)
  • src/main/java/org/cryptomator/cryptofs/event/FileIsInUseEvent.java (1 hunks)
  • src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java (9 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: infeo
PR: cryptomator/cryptofs#312
File: src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java:44-46
Timestamp: 2025-10-14T16:55:45.287Z
Learning: In `src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java`, the interface includes default implementations (including `use()` returning `INIT_TOKEN`) because there are only two implementations—StubInUseManager (stub) and RealInUseManager (real)—with no plans for additional implementations. This design is intentional and acceptable.
📚 Learning: 2025-10-17T13:54:22.498Z
Learnt from: infeo
PR: cryptomator/cryptofs#319
File: src/main/java/org/cryptomator/cryptofs/event/BrokenFileNodeEvent.java:14-18
Timestamp: 2025-10-17T13:54:22.498Z
Learning: In the cryptofs event package (e.g., BrokenFileNodeEvent, ConflictResolutionFailedEvent, ConflictResolvedEvent), the event constructors are intended to be called only from within the filesystem implementation, so null-safety guards like Objects.requireNonNull() are not needed because the call sites are controlled and paths are guaranteed to be non-null.

Applied to files:

  • src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java
  • src/main/java/org/cryptomator/cryptofs/event/FileIsInUseEvent.java
🧬 Code graph analysis (1)
src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java (2)
src/main/java/org/cryptomator/cryptofs/inuse/FileAlreadyInUseException.java (1)
  • FileAlreadyInUseException (6-11)
src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFiles.java (1)
  • TwoPhaseMove (124-174)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyse
  • GitHub Check: Build and Test
🔇 Additional comments (3)
src/main/java/org/cryptomator/cryptofs/event/FileIsInUseEvent.java (1)

1-25: LGTM!

The event record is well-structured. The secondary constructor conveniently accepts a Path for cleartextPath and converts it to String for the canonical constructor, which is a good ergonomic choice for call sites.

src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java (2)

589-597: LGTM!

The test correctly verifies that when a file channel cannot be opened due to in-use status, both the exception is propagated and the corresponding event is emitted.


850-902: LGTM!

The move tests properly verify in-use checking for both source and destination paths. The refactoring to use a helper method moveFileWithXInUse eliminates duplication between the two in-use test cases.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java (1)

475-477: Fix the incorrect @DisplayName annotation.

The @DisplayName says "throws exception when file is in-use" but the test name testCheckUsageForNotInUseFiles and body (using assertDoesNotThrow) clearly test the opposite case where the file is NOT in use.

Apply this diff:

 @Test
-@DisplayName("checkUsage throws exception when file is in-use")
+@DisplayName("checkUsage succeeds when file is not in-use")
 public void testCheckUsageForNotInUseFiles() throws FileAlreadyInUseException {
🧹 Nitpick comments (3)
src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java (1)

638-640: Consider addressing the TODO optimization.

The TODO suggests skipping usage checks when no owner is configured. Since StubInUseManager provides no-op behavior, the current implementation is safe but could be optimized by checking if the owner is set before calling checkUsage.

Consider:

// In CryptoFileSystemModule or properties check:
if (fileSystemProperties.owner().isPresent()) {
    checkUsage(cleartextSource, ciphertextSource);
    checkUsage(cleartextTarget, ciphertextTarget);
}
src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (2)

67-67: Remove stale TODO comment.

The TODO "never closed -> resource leak" is no longer accurate since the close() method at lines 216-233 properly shuts down both executors with appropriate timeout and interrupt handling.

Apply this diff:

-		this.tokenRefresher = Executors.newSingleThreadScheduledExecutor(); //TODO: never closed -> resource leak
+		this.tokenRefresher = Executors.newSingleThreadScheduledExecutor();

134-134: Remove outdated TODO comment.

The TODO "test" is outdated since RealInUseManagerTest already includes comprehensive tests for the validate() method through the isInUse tests and ReadInUseFile nested class.

Apply this diff:

-	//TODO: test
 	UseInfo validate(Properties content) throws IllegalArgumentException {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7a2b548 and cd7bf09.

📒 Files selected for processing (6)
  • src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java (12 hunks)
  • src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java (1 hunks)
  • src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (1 hunks)
  • src/main/java/org/cryptomator/cryptofs/inuse/StubInUseManager.java (1 hunks)
  • src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java (12 hunks)
  • src/test/java/org/cryptomator/cryptofs/inuse/RealInUseManagerTest.java (1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: infeo
PR: cryptomator/cryptofs#312
File: src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java:44-46
Timestamp: 2025-10-14T16:55:45.287Z
Learning: In `src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java`, the interface includes default implementations (including `use()` returning `INIT_TOKEN`) because there are only two implementations—StubInUseManager (stub) and RealInUseManager (real)—with no plans for additional implementations. This design is intentional and acceptable.
📚 Learning: 2025-10-17T13:54:22.510Z
Learnt from: infeo
PR: cryptomator/cryptofs#319
File: src/main/java/org/cryptomator/cryptofs/event/BrokenFileNodeEvent.java:14-18
Timestamp: 2025-10-17T13:54:22.510Z
Learning: In the cryptofs event package (e.g., BrokenFileNodeEvent, ConflictResolutionFailedEvent, ConflictResolvedEvent), the event constructors are intended to be called only from within the filesystem implementation, so null-safety guards like Objects.requireNonNull() are not needed because the call sites are controlled and paths are guaranteed to be non-null.

Applied to files:

  • src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java
📚 Learning: 2025-10-14T16:55:45.287Z
Learnt from: infeo
PR: cryptomator/cryptofs#312
File: src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java:44-46
Timestamp: 2025-10-14T16:55:45.287Z
Learning: In `src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java`, the interface includes default implementations (including `use()` returning `INIT_TOKEN`) because there are only two implementations—StubInUseManager (stub) and RealInUseManager (real)—with no plans for additional implementations. This design is intentional and acceptable.

Applied to files:

  • src/main/java/org/cryptomator/cryptofs/inuse/StubInUseManager.java
  • src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java
  • src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java
  • src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java
  • src/test/java/org/cryptomator/cryptofs/inuse/RealInUseManagerTest.java
📚 Learning: 2025-10-17T13:49:30.221Z
Learnt from: infeo
PR: cryptomator/cryptofs#312
File: src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java:108-118
Timestamp: 2025-10-17T13:49:30.221Z
Learning: In `src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java`, the owner field is validated in `CryptoFileSystemProperties.withOwner(String)` to enforce a maximum length of 100 characters. This validation ensures that the serialized Properties payload (owner + lastUpdated timestamp + Properties overhead) never exceeds the 1000-byte INUSE_CLEARTEXT_SIZE buffer, making additional size checks in writeInUseFile() unnecessary.

Applied to files:

  • src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java
🧬 Code graph analysis (4)
src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java (2)
src/main/java/org/cryptomator/cryptofs/inuse/FileAlreadyInUseException.java (1)
  • FileAlreadyInUseException (6-11)
src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFiles.java (1)
  • TwoPhaseMove (124-174)
src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (4)
src/main/java/org/cryptomator/cryptofs/common/CacheUtils.java (1)
  • CacheUtils (8-57)
src/main/java/org/cryptomator/cryptofs/common/Constants.java (1)
  • Constants (11-41)
src/main/java/org/cryptomator/cryptofs/common/EncryptedChannels.java (1)
  • EncryptedChannels (9-22)
src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (1)
  • RealUseToken (36-211)
src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java (2)
src/main/java/org/cryptomator/cryptofs/inuse/FileAlreadyInUseException.java (1)
  • FileAlreadyInUseException (6-11)
src/main/java/org/cryptomator/cryptofs/common/DeletingFileVisitor.java (1)
  • DeletingFileVisitor (31-91)
src/test/java/org/cryptomator/cryptofs/inuse/RealInUseManagerTest.java (3)
src/main/java/org/cryptomator/cryptofs/common/EncryptedChannels.java (1)
  • EncryptedChannels (9-22)
src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (1)
  • RealInUseManager (40-255)
src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (1)
  • RealUseToken (36-211)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyse
🔇 Additional comments (13)
src/main/java/org/cryptomator/cryptofs/inuse/StubInUseManager.java (1)

5-11: LGTM! Stub implementation is appropriate.

The minimal stub implementation correctly relies on the InUseManager interface defaults and provides an empty close() method, which is appropriate for scenarios where in-use tracking is disabled (readonly or no owner configured). Based on learnings.

src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java (5)

102-144: LGTM! Proper dependency injection for in-use workflow.

The InUseManager and event consumer are correctly wired through the constructor and stored as final fields, enabling the in-use tracking feature.


214-221: LGTM! InUseManager properly closed in shutdown sequence.

The close() method correctly includes inUseManager::close in the finallyUtil guarantee chain, ensuring clean resource cleanup.


429-442: LGTM! Proper error handling with event emission.

FileAlreadyInUseException is caught, an event is emitted with UseInfo, and the channel is closed safely with proper exception suppression. The error handling correctly addresses past review concerns.


459-468: LGTM! Proper usage check before file deletion.

The deleteFile method correctly checks for in-use status before deletion, preventing accidental deletion of files currently used by other filesystem instances.


736-744: LGTM! Centralized usage check with proper event signaling.

The checkUsage method correctly checks in-use status, emits FileIsInUseEvent with UseInfo data, and throws FileAlreadyInUseException. The fallback to "UNKNOWN" owner handles edge cases gracefully.

src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java (4)

108-133: LGTM! Test setup properly includes in-use mocks.

The test setup correctly mocks InUseManager and event consumer, wiring them into the CryptoFileSystemImpl constructor for comprehensive testing.


305-310: LGTM! Test verifies InUseManager cleanup.

The test correctly verifies that inUseManager.close() is invoked during filesystem shutdown.


653-700: LGTM! Comprehensive tests for delete with in-use checks.

The tests properly verify that deleteFile calls checkUsage and that FileAlreadyInUseException prevents deletion, ensuring in-use files are protected.


874-917: LGTM! Thorough tests for move with in-use scenarios.

The tests cover both source and target in-use scenarios, verifying that usage checks prevent moves when files are in use and that the move operation is blocked appropriately.

src/test/java/org/cryptomator/cryptofs/inuse/RealInUseManagerTest.java (1)

36-371: LGTM! Comprehensive test suite for RealInUseManager.

The test suite provides excellent coverage of RealInUseManager functionality, including:

  • isInUseByOthers behavior with existing tokens and cache interactions
  • use() flow with token creation and exception handling
  • createInternal scenarios for existing, invalid, and missing in-use files
  • readInUseFile with encryption/decryption mocking
  • isInUse logic for owner matching and timestamp thresholds
  • Cache interaction verification

The tests are well-structured with proper mocking, clear display names, and good isolation.

src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (2)

76-161: LGTM! Well-structured in-use detection with proper caching.

The three-layered isInUse implementation is well-designed:

  1. isInUseByOthers checks own tokens first, then delegates
  2. isInUse(Path) uses ignored cache and useInfoCache with proper IO wrapping
  3. isInUse(UseInfo) validates owner and staleness threshold

The 10-minute staleness threshold (2×REFRESH_DELAY_MINUTES) correctly prevents false positives while allowing detection of abandoned in-use files.


177-208: LGTM! Robust token creation with comprehensive error handling.

The use() method properly:

  • Computes and caches tokens atomically
  • Invalidates ignored cache on successful use
  • Unwraps FileAlreadyInUseException from UncheckedIOException
  • Returns CLOSED_TOKEN for other IO errors (graceful degradation)

The createInternal logic correctly handles all scenarios:

  • Existing valid in-use file → throw FileAlreadyInUseException
  • Non-existing file → create new token
  • Existing invalid file → steal ownership
  • Other IO errors → wrapped as UncheckedIOException

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (1)

107-121: Consider truncating after repositioning to guarantee no trailing bytes.

While channel.position(0) resets the write position, the channel is not truncated. If a subsequent write is shorter than the previous content, trailing bytes will remain. Although the encryption layer may add padding that makes this unlikely, explicitly truncating provides stronger guarantees.

Consider adding channel.truncate(0) after channel.position(0):

 int writeInUseFile() throws IOException {
 	channel.position(0);
+	channel.truncate(0);
 	final int bytesWritten;
 	try (var nonClosingWrapper = new NonClosingByteChannel(channel); //

Alternatively, if the encryption always produces fixed-size output, document that assumption.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cd7bf09 and 3448469.

📒 Files selected for processing (4)
  • pom.xml (2 hunks)
  • src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (1 hunks)
  • src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (1 hunks)
  • src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java (12 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: infeo
PR: cryptomator/cryptofs#312
File: src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java:44-46
Timestamp: 2025-10-14T16:55:45.287Z
Learning: In `src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java`, the interface includes default implementations (including `use()` returning `INIT_TOKEN`) because there are only two implementations—StubInUseManager (stub) and RealInUseManager (real)—with no plans for additional implementations. This design is intentional and acceptable.
📚 Learning: 2025-10-17T13:54:22.510Z
Learnt from: infeo
PR: cryptomator/cryptofs#319
File: src/main/java/org/cryptomator/cryptofs/event/BrokenFileNodeEvent.java:14-18
Timestamp: 2025-10-17T13:54:22.510Z
Learning: In the cryptofs event package (e.g., BrokenFileNodeEvent, ConflictResolutionFailedEvent, ConflictResolvedEvent), the event constructors are intended to be called only from within the filesystem implementation, so null-safety guards like Objects.requireNonNull() are not needed because the call sites are controlled and paths are guaranteed to be non-null.

Applied to files:

  • src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java
📚 Learning: 2025-10-14T16:55:45.287Z
Learnt from: infeo
PR: cryptomator/cryptofs#312
File: src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java:44-46
Timestamp: 2025-10-14T16:55:45.287Z
Learning: In `src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java`, the interface includes default implementations (including `use()` returning `INIT_TOKEN`) because there are only two implementations—StubInUseManager (stub) and RealInUseManager (real)—with no plans for additional implementations. This design is intentional and acceptable.

Applied to files:

  • src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java
  • src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java
📚 Learning: 2025-10-17T13:49:30.221Z
Learnt from: infeo
PR: cryptomator/cryptofs#312
File: src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java:108-118
Timestamp: 2025-10-17T13:49:30.221Z
Learning: In `src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java`, the owner field is validated in `CryptoFileSystemProperties.withOwner(String)` to enforce a maximum length of 100 characters. This validation ensures that the serialized Properties payload (owner + lastUpdated timestamp + Properties overhead) never exceeds the 1000-byte INUSE_CLEARTEXT_SIZE buffer, making additional size checks in writeInUseFile() unnecessary.

Applied to files:

  • src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java
  • src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java
🧬 Code graph analysis (3)
src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java (2)
src/main/java/org/cryptomator/cryptofs/inuse/FileAlreadyInUseException.java (1)
  • FileAlreadyInUseException (6-11)
src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFiles.java (1)
  • TwoPhaseMove (124-174)
src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (3)
src/main/java/org/cryptomator/cryptofs/common/Constants.java (1)
  • Constants (11-41)
src/main/java/org/cryptomator/cryptofs/common/EncryptedChannels.java (1)
  • EncryptedChannels (9-22)
src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (1)
  • RealInUseManager (40-255)
src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (4)
src/main/java/org/cryptomator/cryptofs/common/CacheUtils.java (1)
  • CacheUtils (8-57)
src/main/java/org/cryptomator/cryptofs/common/Constants.java (1)
  • Constants (11-41)
src/main/java/org/cryptomator/cryptofs/common/EncryptedChannels.java (1)
  • EncryptedChannels (9-22)
src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (1)
  • RealUseToken (36-214)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyse
  • GitHub Check: Build and Test
🔇 Additional comments (18)
pom.xml (1)

28-28: Properly integrated Awaitility test dependency.

The addition of Awaitility follows project conventions: the version is properly managed via Maven properties, the dependency is correctly scoped to test, and the placement is consistent with other test dependencies. Verification confirms that version 4.3.0 is the current release with no known vulnerabilities. The configuration aligns well with the async/timing assertions needed for the in-use token lifecycle tests.

src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java (7)

291-295: LGTM: finallyUtil parameter count updated correctly.

The addition of a fifth RunnableThrowingException parameter aligns with the new inUseManager.close() invocation in the filesystem shutdown sequence (verified by the test at lines 305-310).

Also applies to: 367-371


305-310: LGTM: Good test coverage for InUseManager cleanup.

The test correctly verifies that inUseManager.close() is invoked as part of the filesystem shutdown sequence.


457-473: LGTM: Test correctly verifies FileAlreadyInUseException and event emission.

The test properly mocks the in-use condition, verifies the exception is thrown, and confirms that a FileIsInUseEvent is emitted via the event consumer.


475-488: LGTM: Test correctly verifies no-op behavior when file is not in-use.

The test properly verifies that checkUsage does not throw and does not emit events when the file is not in use. The @DisplayName now correctly describes the test behavior.


604-612: LGTM: Test correctly verifies newFileChannel failure when file is in-use.

The test properly verifies that when openCryptoFile.newFileChannel() throws FileAlreadyInUseException, the exception is propagated and a FileIsInUseEvent is emitted.


652-700: LGTM: Tests correctly verify delete operation with in-use checks.

The tests properly cover:

  • Regular file deletion with successful checkUsage
  • Shortened file deletion (skips checkUsage per design)
  • In-use file deletion throwing exception and preventing the delete

The spy pattern correctly isolates the checkUsage behavior for testing.


874-918: LGTM: Tests correctly verify move operation with in-use checks.

The tests properly cover move operations with in-use detection for both source and target files. The helper method moveFileWithXInUse avoids duplication while testing both scenarios.

src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (4)

59-74: LGTM: Constructor correctly implements delayed persistence.

The delayed executor pattern using the injected tokenPersistor correctly schedules file creation after INUSE_DELAY_MILLIS, allowing the token to be canceled before persistence if the channel closes early.


93-105: LGTM: Refresh correctly updates the in-use file timestamp.

The method properly synchronizes with the write lock, guards against closed/not-yet-created states, and handles errors gracefully. The channel.position(0) in writeInUseFile() ensures no trailing bytes remain from previous writes.


130-154: LGTM: Move operation correctly maintains consistency.

The atomic update of the useTokens map combined with the file move under a write lock ensures consistency. The defensive close() on error prevents invalid states where the map and filesystem are out of sync.


162-185: LGTM: Close correctly cleans up all resources.

The method properly:

  • Cancels the delayed creation task to prevent late file creation
  • Atomically removes the token from the map
  • Closes the channel and deletes the file if it was created
  • Handles cleanup errors gracefully with logging
src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (6)

53-72: LGTM: Constructor correctly initializes caches and executors.

The cache configurations are appropriate:

  • ignoredInUseFiles: 2-minute TTL prevents lingering ignores
  • useInfoCache: 5-second TTL reduces disk reads while staying fresh
  • Periodic refresh every 5 minutes with matching initial delay maintains active ownership

75-87: LGTM: Fast path optimization for self-owned files.

The method correctly short-circuits when we own the token and gracefully handles errors by assuming the file is not in use by others, allowing operations to proceed.


134-151: Address the TODO comment for test coverage.

The validate() method correctly parses the owner from the file (not the local owner field) and validates both required keys. However, the TODO comment indicates test coverage is needed.

Consider adding unit tests to cover:

  • Valid Properties with owner and lastUpdated
  • Missing owner key
  • Blank owner value
  • Missing lastUpdated key
  • Invalid lastUpdated format (DateTimeParseException)

Based on learnings


115-132: LGTM: File reading and decryption correctly implemented.

The method safely reads up to INUSE_CLEARTEXT_SIZE bytes, decrypts the content, validates non-empty, and loads Properties. The fixed buffer size prevents overflow while accommodating the validated owner length.

Based on learnings


177-191: LGTM: Token acquisition correctly handles concurrency and errors.

The atomic computeIfAbsent ensures only one token is created per path, the ignore cache is properly invalidated, and errors are handled defensively by returning CLOSED_TOKEN (except for FileAlreadyInUseException which is correctly propagated).


217-233: LGTM: Executor cleanup correctly prevents resource leaks.

The close method properly shuts down both executors with appropriate timeouts and forced termination, and correctly handles InterruptedException by re-interrupting and forcing shutdown.

Based on learnings

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (3)
src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (3)

28-29: Use ReentrantLock instead of WriteLock (read lock unused).

A plain ReentrantLock is simpler and clearer since only exclusive access is used.

-import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.concurrent.locks.ReentrantLock;
@@
-	private final ReentrantReadWriteLock.WriteLock fileCreationSync = new ReentrantReadWriteLock().writeLock();
+	private final ReentrantLock fileCreationSync = new ReentrantLock();

Also applies to: 53-54


64-74: Rename ctor parameter for clarity (it’s a delayed executor).

The second constructor’s parameter named tokenPersistor is actually the executor used by runAsync (already delayed by the first ctor). Rename to avoid confusion.

-	RealUseToken(Path filePath, String owner, Cryptor cryptor, ConcurrentMap<Path, RealUseToken> useTokens, Executor tokenPersistor, OpenOption openMode, EncryptionDecorator encWrapper) {
+	RealUseToken(Path filePath, String owner, Cryptor cryptor, ConcurrentMap<Path, RealUseToken> useTokens, Executor executor, OpenOption openMode, EncryptionDecorator encWrapper) {
@@
-		this.creationTask = CompletableFuture.runAsync(() -> createInUseFile(openOptions), tokenPersistor);
+		this.creationTask = CompletableFuture.runAsync(() -> createInUseFile(openOptions), executor);

175-178: Log the exception cause on delete failure.

Currently the warn drops the IOException; include it for diagnostics.

-						//ignore
-						LOG.warn("Failed to delete inUse File {}. Must be deleted manually.", path);
+						LOG.warn("Failed to delete inUse File {}. Must be deleted manually.", path, e);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3448469 and ebe8c6e.

📒 Files selected for processing (1)
  • src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: infeo
PR: cryptomator/cryptofs#312
File: src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java:44-46
Timestamp: 2025-10-14T16:55:45.287Z
Learning: In `src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java`, the interface includes default implementations (including `use()` returning `INIT_TOKEN`) because there are only two implementations—StubInUseManager (stub) and RealInUseManager (real)—with no plans for additional implementations. This design is intentional and acceptable.
📚 Learning: 2025-10-14T16:55:45.287Z
Learnt from: infeo
PR: cryptomator/cryptofs#312
File: src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java:44-46
Timestamp: 2025-10-14T16:55:45.287Z
Learning: In `src/main/java/org/cryptomator/cryptofs/inuse/InUseManager.java`, the interface includes default implementations (including `use()` returning `INIT_TOKEN`) because there are only two implementations—StubInUseManager (stub) and RealInUseManager (real)—with no plans for additional implementations. This design is intentional and acceptable.

Applied to files:

  • src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java
📚 Learning: 2025-10-17T13:49:30.221Z
Learnt from: infeo
PR: cryptomator/cryptofs#312
File: src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java:108-118
Timestamp: 2025-10-17T13:49:30.221Z
Learning: In `src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java`, the owner field is validated in `CryptoFileSystemProperties.withOwner(String)` to enforce a maximum length of 100 characters. This validation ensures that the serialized Properties payload (owner + lastUpdated timestamp + Properties overhead) never exceeds the 1000-byte INUSE_CLEARTEXT_SIZE buffer, making additional size checks in writeInUseFile() unnecessary.

Applied to files:

  • src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java
🧬 Code graph analysis (1)
src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java (3)
src/main/java/org/cryptomator/cryptofs/common/Constants.java (1)
  • Constants (11-41)
src/main/java/org/cryptomator/cryptofs/common/EncryptedChannels.java (1)
  • EncryptedChannels (9-22)
src/main/java/org/cryptomator/cryptofs/inuse/RealInUseManager.java (1)
  • RealInUseManager (40-255)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyse
  • GitHub Check: Build and Test

Comment on lines +136 to +151
useTokens.compute(newFilePath, (_, _) -> {
try {
if (channel != null) {
Files.move(filePath, newFilePath, StandardCopyOption.REPLACE_EXISTING);
}
return this;
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
useTokens.remove(filePath);
this.filePath = newFilePath;
} catch (UncheckedIOException e) {
LOG.debug("Failed to move in-use file {} to {}.", filePath, newFilePath, e.getCause());
close(); //To prevent invalid states
} finally {
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Moving an open file is fragile on Windows; add a fallback (close/recreate) path.

Files.move on an open FileChannel often fails on Windows (sharing/locking). Today you close the token on failure. Prefer keeping the token by falling back to recreating the in-use file at the new path.

-			useTokens.compute(newFilePath, (k, v) -> {
-				try {
-					if (channel != null) {
-						Files.move(filePath, newFilePath, StandardCopyOption.REPLACE_EXISTING);
-					}
-					return this;
-				} catch (IOException e) {
-					throw new UncheckedIOException(e);
-				}
-			});
+			useTokens.compute(newFilePath, (k, v) -> {
+				try {
+					if (channel != null) {
+						try {
+							Files.move(filePath, newFilePath, StandardCopyOption.REPLACE_EXISTING);
+							return this;
+						} catch (java.nio.file.AccessDeniedException winMoveDenied) {
+							// Windows fallback: close old channel, recreate at new path, then delete old file.
+							var oldPath = filePath;
+							var oldCh = channel;
+							channel = null; // prevent refresh from writing to stale handle
+							this.filePath = newFilePath;
+							this.channel = FileChannel.open(newFilePath, java.util.Set.of(
+									StandardOpenOption.WRITE,
+									StandardOpenOption.CREATE,
+									StandardOpenOption.TRUNCATE_EXISTING));
+							writeInUseFile();
+							try {
+								if (oldCh != null && oldCh.isOpen()) oldCh.close();
+							} catch (IOException ignore) { /* best-effort */ }
+							try {
+								Files.deleteIfExists(oldPath);
+							} catch (IOException ignore) { /* best-effort */ }
+							return this;
+						}
+					}
+					return this;
+				} catch (IOException e) {
+					throw new UncheckedIOException(e);
+				}
+			});

If you prefer not to special-case Windows, consider always closing/recreating instead of moving when channel != null. I can provide that variant too.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/main/java/org/cryptomator/cryptofs/inuse/RealUseToken.java around lines
136-151, Files.move of an open file can fail on Windows; instead of closing the
token on UncheckedIOException, implement a fallback that preserves the token by
(1) closing the open channel/stream safely, (2) creating or opening a new in-use
file at newFilePath (with the same semantics as the original, e.g.,
createNewFile or replace existing), (3) updating the useTokens map atomically to
point to the new path and removing the old key, and (4) reinitializing
this.filePath to newFilePath and reopening the channel if needed — ensure all
IOExceptions are handled/wrapped consistently, log the fallback action, and keep
the finally cleanup unchanged so no invalid state is left if the fallback also
fails.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant