Explorar o código

Add locking mechanism to change the vault state t

Armin Schrenk %!s(int64=4) %!d(string=hai) anos
pai
achega
beba6490c3

+ 30 - 4
main/commons/src/main/java/org/cryptomator/common/vaults/Vault.java

@@ -27,6 +27,7 @@ import org.slf4j.LoggerFactory;
 import javax.inject.Inject;
 import javax.inject.Named;
 import javax.inject.Provider;
+import javafx.application.Platform;
 import javafx.beans.Observable;
 import javafx.beans.binding.Bindings;
 import javafx.beans.binding.BooleanBinding;
@@ -43,6 +44,7 @@ import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.StampedLock;
 
 import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
 
@@ -52,6 +54,8 @@ public class Vault {
 	private static final Logger LOG = LoggerFactory.getLogger(Vault.class);
 	private static final Path HOME_DIR = Paths.get(SystemUtils.USER_HOME);
 
+	private final StampedLock stateLock;
+
 	private final VaultSettings vaultSettings;
 	private final Provider<Volume> volumeProvider;
 	private final StringBinding defaultMountFlags;
@@ -93,6 +97,8 @@ public class Vault {
 		this.accessPoint = Bindings.createStringBinding(this::getAccessPoint, state);
 		this.accessPointPresent = this.accessPoint.isNotEmpty();
 		this.showingStats = new SimpleBooleanProperty(false);
+
+		this.stateLock = new StampedLock();
 	}
 
 	// ******************************************************************************
@@ -141,9 +147,13 @@ public class Vault {
 				volume = volumeProvider.get();
 				volume.mount(fs, getEffectiveMountFlags(), throwable -> {
 					destroyCryptoFileSystem();
-					setState(VaultState.LOCKED); //TODO: possible race conditions of the vault state. Use Platform.runLater()?
+					new Thread(() -> { //TODO: maybe use the executor service
+						long stamp = stateLock.writeLock();
+						setState(VaultState.LOCKED, stamp);
+						stateLock.unlock(stamp);
+					}).start();
 					if (throwable != null) {
-						LOG.warn("Unexpected unmount and lock of vault" + getDisplayName(), throwable);
+						LOG.warn("Unexpected unmount and lock of vault " + getDisplayName(), throwable);
 					}
 				});
 			} catch (Exception e) {
@@ -180,8 +190,24 @@ public class Vault {
 		return state.get();
 	}
 
-	public void setState(VaultState value) {
-		state.setValue(value);
+	public long lockVaultState() {
+		return stateLock.writeLock();
+	}
+
+	public void unlockVaultState(long stamp) {
+		stateLock.unlock(stamp);
+	}
+
+	public void setState(VaultState value, long stamp) {
+		if (stateLock.isWriteLockStamp(stamp)) {
+			if (Platform.isFxApplicationThread()) {
+				state.setValue(value);
+			} else {
+				Platform.runLater(() -> state.setValue(value));
+			}
+		} else {
+			throw new IllegalCallerException("Stamp is not a valid write lock.");
+		}
 	}
 
 	public ObjectProperty<Exception> lastKnownExceptionProperty() {

+ 5 - 3
main/commons/src/main/java/org/cryptomator/common/vaults/VaultListManager.java

@@ -25,7 +25,6 @@ import java.nio.file.Path;
 import java.util.Collection;
 import java.util.Optional;
 import java.util.ResourceBundle;
-import java.util.stream.Collectors;
 
 import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
 
@@ -108,15 +107,18 @@ public class VaultListManager {
 		VaultState previousState = vault.getState();
 		return switch (previousState) {
 			case LOCKED, NEEDS_MIGRATION, MISSING -> {
+				long stamp = vault.lockVaultState();
 				try {
 					VaultState determinedState = determineVaultState(vault.getPath());
-					vault.setState(determinedState);
+					vault.setState(determinedState, stamp);
 					yield determinedState;
 				} catch (IOException e) {
 					LOG.warn("Failed to determine vault state for " + vault.getPath(), e);
-					vault.setState(VaultState.ERROR);
+					vault.setState(VaultState.ERROR, stamp);
 					vault.setLastKnownException(e);
 					yield VaultState.ERROR;
+				} finally {
+					vault.unlockVaultState(stamp);
 				}
 			}
 			case ERROR, UNLOCKED, PROCESSING -> previousState;

+ 14 - 3
main/ui/src/main/java/org/cryptomator/ui/common/VaultService.java

@@ -163,6 +163,8 @@ public class VaultService {
 		private final Vault vault;
 		private final boolean forced;
 
+		private volatile long stamp;
+
 		/**
 		 * @param vault The vault to lock
 		 * @param forced Whether to attempt a forced lock
@@ -176,23 +178,32 @@ public class VaultService {
 
 		@Override
 		protected Vault call() throws Volume.VolumeException {
+			this.stamp = vault.lockVaultState();
 			vault.lock(forced);
 			return vault;
 		}
 
 		@Override
 		protected void scheduled() {
-			vault.setState(VaultState.PROCESSING);
+			vault.setState(VaultState.PROCESSING, stamp);
 		}
 
 		@Override
 		protected void succeeded() {
-			vault.setState(VaultState.LOCKED);
+			vault.setState(VaultState.LOCKED, stamp);
+			vault.unlockVaultState(stamp);
 		}
 
 		@Override
 		protected void failed() {
-			vault.setState(VaultState.UNLOCKED);
+			vault.setState(VaultState.UNLOCKED, stamp);
+			vault.unlockVaultState(stamp);
+		}
+
+		@Override
+		protected void cancelled() {
+			vault.setState(VaultState.UNLOCKED, stamp);
+			vault.unlockVaultState(stamp);
 		}
 
 	}

+ 10 - 4
main/ui/src/main/java/org/cryptomator/ui/lock/LockWorkflow.java

@@ -36,6 +36,8 @@ public class LockWorkflow extends Task<Void> {
 	private final Lazy<Scene> lockForcedScene;
 	private final Lazy<Scene> lockFailedScene;
 
+	private volatile long stamp;
+
 	@Inject
 	public LockWorkflow(@LockWindow Stage lockWindow, @LockWindow Vault vault, UserInteractionLock<LockModule.ForceLockDecision> forceLockDecisionLock, @FxmlScene(FxmlFile.LOCK_FORCED) Lazy<Scene> lockForcedScene, @FxmlScene(FxmlFile.LOCK_FAILED) Lazy<Scene> lockFailedScene) {
 		this.lockWindow = lockWindow;
@@ -47,6 +49,7 @@ public class LockWorkflow extends Task<Void> {
 
 	@Override
 	protected Void call() throws Volume.VolumeException, InterruptedException {
+		this.stamp = vault.lockVaultState();
 		try {
 			vault.lock(false);
 		} catch (Volume.VolumeException e) {
@@ -79,19 +82,21 @@ public class LockWorkflow extends Task<Void> {
 
 	@Override
 	protected void scheduled() {
-		vault.setState(VaultState.PROCESSING);
+		vault.setState(VaultState.PROCESSING, stamp);
 	}
 
 	@Override
 	protected void succeeded() {
 		LOG.info("Lock of {} succeeded.", vault.getDisplayName());
-		vault.setState(VaultState.LOCKED);
+		vault.setState(VaultState.LOCKED, stamp);
+		vault.unlockVaultState(stamp);
 	}
 
 	@Override
 	protected void failed() {
 		LOG.warn("Failed to lock {}.", vault.getDisplayName());
-		vault.setState(VaultState.UNLOCKED);
+		vault.setState(VaultState.UNLOCKED, stamp);
+		vault.unlockVaultState(stamp);
 		lockWindow.setScene(lockFailedScene.get());
 		lockWindow.show();
 	}
@@ -99,7 +104,8 @@ public class LockWorkflow extends Task<Void> {
 	@Override
 	protected void cancelled() {
 		LOG.debug("Lock of {} canceled.", vault.getDisplayName());
-		vault.setState(VaultState.UNLOCKED);
+		vault.setState(VaultState.UNLOCKED, stamp);
+		vault.unlockVaultState(stamp);
 	}
 
 }

+ 9 - 7
main/ui/src/main/java/org/cryptomator/ui/migration/MigrationRunController.java

@@ -101,7 +101,8 @@ public class MigrationRunController implements FxController {
 	public void migrate() {
 		LOG.info("Migrating vault {}", vault.getPath());
 		CharSequence password = passwordField.getCharacters();
-		vault.setState(VaultState.PROCESSING);
+		long stamp = vault.lockVaultState();
+		vault.setState(VaultState.PROCESSING, stamp);
 		passwordField.setDisable(true);
 		ScheduledFuture<?> progressSyncTask = scheduler.scheduleAtFixedRate(() -> {
 			Platform.runLater(() -> {
@@ -115,10 +116,10 @@ public class MigrationRunController implements FxController {
 		}).onSuccess(needsAnotherMigration -> {
 			if (needsAnotherMigration) {
 				LOG.info("Migration of '{}' succeeded, but another migration is required.", vault.getDisplayName());
-				vault.setState(VaultState.NEEDS_MIGRATION);
+				vault.setState(VaultState.NEEDS_MIGRATION, stamp);
 			} else {
 				LOG.info("Migration of '{}' succeeded.", vault.getDisplayName());
-				vault.setState(VaultState.LOCKED);
+				vault.setState(VaultState.LOCKED, stamp);
 				passwordField.wipe();
 				window.setScene(successScene.get());
 			}
@@ -127,22 +128,23 @@ public class MigrationRunController implements FxController {
 			passwordField.setDisable(false);
 			passwordField.selectAll();
 			passwordField.requestFocus();
-			vault.setState(VaultState.NEEDS_MIGRATION);
+			vault.setState(VaultState.NEEDS_MIGRATION, stamp);
 		}).onError(FileSystemCapabilityChecker.MissingCapabilityException.class, e -> {
 			LOG.error("Underlying file system not supported.", e);
-			vault.setState(VaultState.NEEDS_MIGRATION);
+			vault.setState(VaultState.NEEDS_MIGRATION, stamp);
 			missingCapability.set(e.getMissingCapability());
 			window.setScene(capabilityErrorScene.get());
 		}).onError(FileNameTooLongException.class, e -> {
 			LOG.error("Migration failed because the underlying file system does not support long filenames.", e);
-			vault.setState(VaultState.NEEDS_MIGRATION);
+			vault.setState(VaultState.NEEDS_MIGRATION, stamp);
 			errorComponent.cause(e).window(window).returnToScene(startScene.get()).build().showErrorScene();
 			window.setScene(impossibleScene.get());
 		}).onError(Exception.class, e -> { // including RuntimeExceptions
 			LOG.error("Migration failed for technical reasons.", e);
-			vault.setState(VaultState.NEEDS_MIGRATION);
+			vault.setState(VaultState.NEEDS_MIGRATION, stamp);
 			errorComponent.cause(e).window(window).returnToScene(startScene.get()).build().showErrorScene();
 		}).andFinally(() -> {
+			vault.unlockVaultState(stamp);
 			passwordField.setDisable(false);
 			progressSyncTask.cancel(true);
 		}).runOnce(executor);

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 10 - 4
main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java