From 7cc6ec281e1c226d2ec47b5bc52b83be3972cc1c Mon Sep 17 00:00:00 2001 From: Jussi Koskela Date: Tue, 9 May 2017 16:12:17 +0300 Subject: [PATCH] Improved logic in new head state creation. Earlier any IOException during the reading of head state was interpreted as empty DB. This might cause unwanted DB reset. It's better to identify need for empty head state based on main state head directory. Switched AcornDatabase.start logic back to using RandomAccessFile for touching the db/lock file. Using RandomAccessFile instead of FileSystemProvider.newFileChannel in Windows better prevents any other process from removing the lock file. The newFileChannel version did not prevent the user from initially running 'del lock' to remove the file - although the file will be recreated quickly by the system. Also AcornDatabase.start now re-throws ProCoreException if opening/locking the lock-file fails with IOException to prevent the system from attempting to start up without a proper database to work with. Previously the system just logged the start-up problem and continued. refs #7124 Change-Id: I850b47d8f692e3d1b8ce177b9269540edc4dc272 (cherry picked from commit 25c9cc192b2611646b0a476bf205484500e92997) --- .../org/simantics/acorn/ClusterManager.java | 62 ++++++++++--------- .../src/org/simantics/acorn/HeadState.java | 3 +- .../src/org/simantics/acorn/MainState.java | 4 ++ .../acorn/internal/AcornDatabase.java | 22 +++---- 4 files changed, 47 insertions(+), 44 deletions(-) diff --git a/bundles/org.simantics.acorn/src/org/simantics/acorn/ClusterManager.java b/bundles/org.simantics.acorn/src/org/simantics/acorn/ClusterManager.java index 4c8df9337..9725ffe8d 100644 --- a/bundles/org.simantics.acorn/src/org/simantics/acorn/ClusterManager.java +++ b/bundles/org.simantics.acorn/src/org/simantics/acorn/ClusterManager.java @@ -406,35 +406,39 @@ public class ClusterManager { lastSessionDirectory = dbFolder.resolve(Integer.toString(mainState.headDir - 1)); // Head State - try { - state = HeadState.load(lastSessionDirectory); - } catch (InvalidHeadStateException e) { - // For backwards compatibility only! - Throwable cause = e.getCause(); - if (cause instanceof Throwable) { - try { - org.simantics.db.javacore.HeadState oldState = org.simantics.db.javacore.HeadState.load(lastSessionDirectory); - - HeadState newState = new HeadState(); - newState.clusters = oldState.clusters; - newState.cs = oldState.cs; - newState.files = oldState.files; - newState.stream = oldState.stream; - newState.headChangeSetId = oldState.headChangeSetId; - newState.reservedIds = oldState.reservedIds; - newState.transactionId = oldState.transactionId; - state = newState; - } catch (InvalidHeadStateException e1) { - throw new IOException("Could not load HeadState due to corruption", e1); - } - } else { - // This should never happen as MainState.load() checks the integrity - // of head.state files and rolls back in cases of corruption until a - // consistent state is found (could be case 0 - initial db state) - // IF this does happen something is completely wrong - throw new IOException("Could not load HeadState due to corruption", e); - } - } + if (mainState.isInitial()) { + state = new HeadState(); + } else { + try { + state = HeadState.load(lastSessionDirectory); + } catch (InvalidHeadStateException e) { + // For backwards compatibility only! + Throwable cause = e.getCause(); + if (cause instanceof Throwable) { + try { + org.simantics.db.javacore.HeadState oldState = org.simantics.db.javacore.HeadState.load(lastSessionDirectory); + + HeadState newState = new HeadState(); + newState.clusters = oldState.clusters; + newState.cs = oldState.cs; + newState.files = oldState.files; + newState.stream = oldState.stream; + newState.headChangeSetId = oldState.headChangeSetId; + newState.reservedIds = oldState.reservedIds; + newState.transactionId = oldState.transactionId; + state = newState; + } catch (InvalidHeadStateException e1) { + throw new IOException("Could not load HeadState due to corruption", e1); + } + } else { + // This should never happen as MainState.load() checks the integrity + // of head.state files and rolls back in cases of corruption until a + // consistent state is found (could be case 0 - initial db state) + // IF this does happen something is completely wrong + throw new IOException("Could not load HeadState due to corruption", e); + } + } + } try { workingDirectory = dbFolder.resolve(Integer.toString(mainState.headDir)); Files.createDirectories(workingDirectory); diff --git a/bundles/org.simantics.acorn/src/org/simantics/acorn/HeadState.java b/bundles/org.simantics.acorn/src/org/simantics/acorn/HeadState.java index a6a1622c8..ea54a4185 100644 --- a/bundles/org.simantics.acorn/src/org/simantics/acorn/HeadState.java +++ b/bundles/org.simantics.acorn/src/org/simantics/acorn/HeadState.java @@ -62,8 +62,7 @@ public class HeadState { HeadState1 old = HeadState1.load(directory); return old.migrate(); } - return new HeadState(); -// throw new InvalidHeadStateException(i); + throw new InvalidHeadStateException(i); } catch (NoSuchAlgorithmException e) { throw new Error("SHA-1 Algorithm not found", e); } catch (Throwable t) { diff --git a/bundles/org.simantics.acorn/src/org/simantics/acorn/MainState.java b/bundles/org.simantics.acorn/src/org/simantics/acorn/MainState.java index ebc4079d5..f5644c2ae 100644 --- a/bundles/org.simantics.acorn/src/org/simantics/acorn/MainState.java +++ b/bundles/org.simantics.acorn/src/org/simantics/acorn/MainState.java @@ -38,6 +38,10 @@ public class MainState implements Serializable { private MainState(int headDir) { this.headDir = headDir; } + + public boolean isInitial() { + return this.headDir == 0; + } public static MainState load(Path directory, Runnable rollbackCallback) throws IOException { Files.createDirectories(directory); diff --git a/bundles/org.simantics.acorn/src/org/simantics/acorn/internal/AcornDatabase.java b/bundles/org.simantics.acorn/src/org/simantics/acorn/internal/AcornDatabase.java index db83f1395..a423fbdbe 100644 --- a/bundles/org.simantics.acorn/src/org/simantics/acorn/internal/AcornDatabase.java +++ b/bundles/org.simantics.acorn/src/org/simantics/acorn/internal/AcornDatabase.java @@ -2,16 +2,14 @@ package org.simantics.acorn.internal; import java.io.File; import java.io.IOException; -import java.nio.channels.FileChannel; +import java.io.RandomAccessFile; import java.nio.channels.FileLock; import java.nio.file.DirectoryStream; -import java.nio.file.FileAlreadyExistsException; import java.nio.file.FileVisitOption; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; -import java.nio.file.StandardOpenOption; import java.nio.file.attribute.BasicFileAttributes; import java.util.EnumSet; import java.util.Properties; @@ -41,7 +39,7 @@ public class AcornDatabase implements Database { private DatabaseUserAgent userAgent; - private FileChannel lockFileChannel; + private RandomAccessFile raLockFile; private FileLock lock; @@ -119,19 +117,17 @@ public class AcornDatabase implements Database { @Override public synchronized void start() throws ProCoreException { try { - lockFileChannel = lockFile.getFileSystem().provider().newFileChannel(lockFile, - EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE)); - - lock = lockFileChannel.tryLock(); + raLockFile = new RandomAccessFile(lockFile.toFile(), "rw"); + lock = raLockFile.getChannel().tryLock(); if (lock == null) { - safeLoggingClose(lockFileChannel, lockFile); + safeLoggingClose(raLockFile, lockFile); throw new ProCoreException("The database in folder " + folder.toAbsolutePath() + " is already in use!"); } - isRunning = true; } catch (IOException e) { LOGGER.error("Failed to start database at " + folder.toAbsolutePath(), e); - safeLoggingClose(lockFileChannel, lockFile); + safeLoggingClose(raLockFile, lockFile); + throw new ProCoreException("Failed to start database at " + folder.toAbsolutePath(), e); } } @@ -147,8 +143,8 @@ public class AcornDatabase implements Database { try { safeLoggingClose(lock, lockFile); lock = null; - safeLoggingClose(lockFileChannel, lockFile); - lockFileChannel = null; + safeLoggingClose(raLockFile, lockFile); + raLockFile = null; Files.deleteIfExists(lockFile); isRunning = false; } catch (IOException e) { -- 2.47.1