Skip to content

Commit a6643d8

Browse files
authored
Merge pull request #2302 from adamretter/hotfix/journal-must-write-ahead
Journal must write-ahead
2 parents 96346b5 + 85a0527 commit a6643d8

File tree

3 files changed

+50
-199
lines changed

3 files changed

+50
-199
lines changed

src/org/exist/storage/BrokerPool.java

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import com.evolvedbinary.j8fu.fsm.AtomicFSM;
2323
import com.evolvedbinary.j8fu.fsm.FSM;
2424
import net.jcip.annotations.GuardedBy;
25+
import net.jcip.annotations.NotThreadSafe;
2526
import net.jcip.annotations.ThreadSafe;
2627
import org.apache.logging.log4j.LogManager;
2728
import org.apache.logging.log4j.Logger;
@@ -1409,16 +1410,25 @@ public long getLastMajorSync() {
14091410
/**
14101411
* Executes a waiting cache synchronization for the database instance.
14111412
*
1413+
* NOTE: This method should not be called concurrently from multiple threads.
1414+
*
14121415
* @param broker A broker responsible for executing the job
14131416
* @param syncEvent One of {@link org.exist.storage.sync.Sync}
14141417
*/
1415-
//TODO : rename as runSync ? executeSync ?
1416-
//TOUNDERSTAND (pb) : *not* synchronized, so... "executes" or, rather, "schedules" ? "executes" (WM)
1417-
//TOUNDERSTAND (pb) : why do we need a broker here ? Why not get and release one when we're done ?
1418-
// WM: the method will always be under control of the BrokerPool. It is guaranteed that no
1419-
// other brokers are active when it is called. That's why we don't need to synchronize here.
1420-
//TODO : make it protected ?
14211418
public void sync(final DBBroker broker, final Sync syncEvent) {
1419+
1420+
/**
1421+
* Database Systems - The Complete Book (Second edition)
1422+
* § 17.4.1 The Undo/Redo Rules
1423+
*
1424+
* The constraints that an undo/redo logging system must follow are summarized by the following rule:
1425+
* * UR1 Before modifying any database element X on disk because of changes
1426+
* made by some transaction T, it is necessary that the update record
1427+
* <T,X,v,w> appear on disk.
1428+
*/
1429+
journalManager.get().flush(true, true);
1430+
1431+
// sync various DBX files
14221432
broker.sync(syncEvent);
14231433

14241434
//TODO : strange that it is set *after* the sunc method has been called.

src/org/exist/storage/journal/FileSyncRunnable.java

Lines changed: 0 additions & 164 deletions
This file was deleted.

src/org/exist/storage/journal/Journal.java

Lines changed: 34 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@
9898
*/
9999
@ConfigurationClass("journal")
100100
//TODO: conf.xml refactoring <recovery> => <recovery><journal/></recovery>
101-
public final class Journal {
101+
public final class Journal implements Closeable {
102102
/**
103103
* Logger for this class
104104
*/
@@ -187,13 +187,7 @@ public final class Journal {
187187
* the current output channel
188188
* Only valid after switchFiles() was called at least once!
189189
*/
190-
private SeekableByteChannel channel;
191-
192-
/**
193-
* Syncing the journal is done by a background thread
194-
*/
195-
private final FileSyncRunnable fileSyncRunnable;
196-
private final Thread fileSyncThread;
190+
private FileChannel channel;
197191

198192
/**
199193
* latch used to synchronize writes to the channel
@@ -263,10 +257,6 @@ public Journal(final BrokerPool pool, final Path directory) throws EXistExceptio
263257
this.fsJournalDir = directory.resolve("fs.journal");
264258
this.currentBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
265259

266-
this.fileSyncRunnable = new FileSyncRunnable(latch);
267-
this.fileSyncThread = newInstanceThread(pool, "file-sync-thread", fileSyncRunnable);
268-
fileSyncThread.start(); //this makes us to use class as a final only - no inheritance allowed
269-
270260
this.syncOnCommit = pool.getConfiguration().getProperty(PROPERTY_RECOVERY_SYNC_ON_COMMIT, DEFAULT_SYNC_ON_COMMIT);
271261
if (LOG.isDebugEnabled()) {
272262
LOG.debug("SyncOnCommit = " + syncOnCommit);
@@ -409,11 +399,18 @@ public synchronized void flushToLog(final boolean fsync, final boolean forceSync
409399
if (inRecovery) {
410400
return;
411401
}
402+
412403
flushBuffer();
413-
if (forceSync || (fsync && syncOnCommit && currentLsn.compareTo(lastSyncLsn) > 0)) {
414-
fileSyncRunnable.triggerSync();
415-
lastSyncLsn = currentLsn;
404+
405+
try {
406+
if (forceSync || (fsync && syncOnCommit && currentLsn.compareTo(lastSyncLsn) > 0)) {
407+
sync();
408+
lastSyncLsn = currentLsn;
409+
}
410+
} catch (final IOException e) {
411+
LOG.error("Could not sync Journal to disk: " + e.getMessage(), e);
416412
}
413+
417414
try {
418415
if (channel != null && channel.size() >= journalSizeLimit) {
419416
pool.triggerCheckpoint();
@@ -423,6 +420,10 @@ public synchronized void flushToLog(final boolean fsync, final boolean forceSync
423420
}
424421
}
425422

423+
private void sync() throws IOException {
424+
channel.force(true);
425+
}
426+
426427
/**
427428
* Flush the buffer to disk.
428429
*/
@@ -541,11 +542,13 @@ public void switchFiles() throws LogException {
541542
}
542543

543544
synchronized (latch) {
544-
close();
545545
try {
546-
channel = Files.newByteChannel(file, CREATE_NEW, WRITE);
546+
// close current file
547+
close();
548+
549+
// open new file
550+
channel = (FileChannel) Files.newByteChannel(file, CREATE_NEW, WRITE);
547551
writeJournalHeader(channel);
548-
fileSyncRunnable.setChannel((FileChannel) channel);
549552
initialised = true;
550553
} catch (final IOException e) {
551554
throw new LogException("Failed to open new journal: " + file.toAbsolutePath().toString(), e);
@@ -571,13 +574,15 @@ private void writeJournalHeader(final SeekableByteChannel channel) throws IOExce
571574
/**
572575
* Close the journal.
573576
*/
574-
public void close() {
577+
@Override
578+
public void close() throws IOException {
575579
if (channel != null) {
576580
try {
577-
channel.close();
581+
sync();
578582
} catch (final IOException e) {
579-
LOG.warn("Failed to close journal channel", e);
583+
LOG.error(e.getMessage(), e);
580584
}
585+
channel.close();
581586
}
582587
}
583588

@@ -647,23 +652,23 @@ public void shutdown(final long txnId, final boolean checkpoint) {
647652

648653
if (!BrokerPool.FORCE_CORRUPTION) {
649654
if (checkpoint) {
650-
LOG.info("Transaction journal cleanly shutting down with checkpoint...");
655+
LOG.info("Shutting down Journal with checkpoint...");
651656
try {
652657
writeToLog(new Checkpoint(txnId));
653658
} catch (final JournalException e) {
654-
LOG.error("An error occurred while closing the journal file: " + e.getMessage(), e);
659+
LOG.error("An error occurred whilst writing a checkpoint to the Journal: " + e.getMessage(), e);
655660
}
656661
}
657662
flushBuffer();
658663
}
659-
fileLock.release();
660-
fileSyncRunnable.shutdown();
661-
fileSyncThread.interrupt();
664+
662665
try {
663-
fileSyncThread.join();
664-
} catch (final InterruptedException e) {
665-
//Nothing to do
666+
channel.close();
667+
} catch (final IOException e) {
668+
LOG.error("Unable to close Journal file: " + e.getMessage(), e);
666669
}
670+
channel = null;
671+
fileLock.release();
667672
currentBuffer = null;
668673
}
669674

0 commit comments

Comments
 (0)