From c274af47a0d932b86f9afb86338ebff811edb4ee Mon Sep 17 00:00:00 2001
From: Nicolas Capponi <nicolas.capponi@forgerock.com>
Date: Fri, 05 Jun 2015 13:55:40 +0000
Subject: [PATCH] OPENDJ-1811 CR-7142 Disk space issues can corrupt file based changelog
---
opendj-server-legacy/src/main/java/org/opends/server/replication/server/changelog/file/ReplicationEnvironment.java | 90 +++++++++++++++++++++++++++++++++------------
1 files changed, 66 insertions(+), 24 deletions(-)
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/replication/server/changelog/file/ReplicationEnvironment.java b/opendj-server-legacy/src/main/java/org/opends/server/replication/server/changelog/file/ReplicationEnvironment.java
index 5a6c2aa..bc947e7 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/replication/server/changelog/file/ReplicationEnvironment.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/replication/server/changelog/file/ReplicationEnvironment.java
@@ -21,7 +21,7 @@
* CDDL HEADER END
*
*
- * Copyright 2014 ForgeRock AS
+ * Copyright 2014-2015 ForgeRock AS
*/
package org.opends.server.replication.server.changelog.file;
@@ -37,6 +37,10 @@
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
+import java.nio.file.AtomicMoveNotSupportedException;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -104,7 +108,7 @@
* | \---changenumberindex
* | \--- head.log [contains last records written]
* | \--- 1_50.log [contains records with keys in interval [1, 50]]
- * | \--- rtime198745512.last
+ * | \--- rotationtime198745512.last
* | \---1.domain
* | \---generation1.id
* | \---22.server
@@ -122,6 +126,7 @@
*/
class ReplicationEnvironment
{
+
private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
private static final long CN_INDEX_DB_MAX_LOG_FILE_SIZE_IN_BYTES = 1024 * 1024;
@@ -130,6 +135,9 @@
private static final int NO_GENERATION_ID = -1;
+ /** Extension for the temporary file used when modifying an environment file. */
+ private static final String FILE_EXTENSION_TEMP = ".tmp";
+
private static final String CN_INDEX_DB_DIRNAME = "changenumberindex";
private static final String DOMAINS_STATE_FILENAME = "domains.state";
@@ -204,15 +212,14 @@
private final ChangelogState changelogState;
/** The list of logs that are in use for Replica DBs. */
- private final List<Log<CSN, UpdateMsg>> logsReplicaDB = new CopyOnWriteArrayList<Log<CSN, UpdateMsg>>();
+ private final List<Log<CSN, UpdateMsg>> logsReplicaDB = new CopyOnWriteArrayList<>();
/**
* The list of logs that are in use for the CN Index DB.
* There is a single CN Index DB for a ReplicationServer, but there can be multiple references opened on it.
- * This is the responsability of Log class to handle properly these multiple references.
+ * This is the responsibility of Log class to handle properly these multiple references.
*/
- private List<Log<Long, ChangeNumberIndexRecord>> logsCNIndexDB =
- new CopyOnWriteArrayList<Log<Long, ChangeNumberIndexRecord>>();;
+ private List<Log<Long, ChangeNumberIndexRecord>> logsCNIndexDB = new CopyOnWriteArrayList<>();
/**
* Maps each domain DN to a domain id that is used to name directory in file system.
@@ -532,23 +539,19 @@
return; // no serverId anymore => no-op
}
final File offlineFile = new File(serverIdPath, REPLICA_OFFLINE_STATE_FILENAME);
- Writer writer = null;
- try
+ try (Writer writer = newTempFileWriter(offlineFile))
{
- // Overwrite file, only the last sent offline CSN is kept
- writer = newFileWriter(offlineFile);
+ // Only the last sent offline CSN is kept
writer.write(offlineCSN.toString());
+ StaticUtils.close(writer);
changelogState.addOfflineReplica(domainDN, offlineCSN);
+ commitFile(offlineFile);
}
catch (IOException e)
{
throw new ChangelogException(ERR_CHANGELOG_UNABLE_TO_WRITE_REPLICA_OFFLINE_STATE_FILE.get(
domainDN.toString(), offlineCSN.getServerId(), offlineFile.getPath(), offlineCSN.toString()), e);
}
- finally
- {
- StaticUtils.close(writer);
- }
}
}
@@ -715,24 +718,20 @@
final String nextDomainId = findNextDomainId();
domains.put(domainDN, nextDomainId);
final File domainsStateFile = new File(replicationRootPath, DOMAINS_STATE_FILENAME);
- Writer writer = null;
- try
+ try (Writer writer = newTempFileWriter(domainsStateFile))
{
- writer = newFileWriter(domainsStateFile);
for (final Entry<DN, String> entry : domains.entrySet())
{
writer.write(String.format("%s%s%s%n", entry.getValue(), DOMAIN_STATE_SEPARATOR, entry.getKey()));
}
+ StaticUtils.close(writer);
+ commitFile(domainsStateFile);
}
catch (IOException e)
{
throw new ChangelogException(ERR_CHANGELOG_UNABLE_TO_UPDATE_DOMAIN_STATE_FILE.get(nextDomainId,
domainDN.toString(), domainsStateFile.getPath()), e);
}
- finally
- {
- StaticUtils.close(writer);
- }
return nextDomainId;
}
@@ -1016,10 +1015,53 @@
}
}
- /** Returns a buffered writer on the provided file. */
- private BufferedWriter newFileWriter(final File file) throws UnsupportedEncodingException, FileNotFoundException
+ /**
+ * Returns a buffered writer on the temp file (".tmp") corresponding to the provided file.
+ * <p>
+ * Once writes are finished, the {@code commitFile()} method should be called to finish the update
+ * of the provided file.
+ */
+ private BufferedWriter newTempFileWriter(final File file) throws UnsupportedEncodingException, FileNotFoundException
{
- return new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), UTF8_ENCODING));
+ File tempFile = getTempFileFor(file);
+ return new BufferedWriter(new OutputStreamWriter(new FileOutputStream(tempFile), UTF8_ENCODING));
+ }
+
+ /**
+ * "Commit" the provided file by moving the ".tmp" file to its final location.
+ * <p>
+ * In order to prevent partially written environment files, update of files is always
+ * performed by writing first a ".tmp" version and then switching the ".tmp" version to
+ * the final version once update is finished.
+ * <p>
+ * This method effectively moves the ".tmp" version to the final version.
+ *
+ * @param file
+ * the final file location.
+ */
+ private void commitFile(final File file) throws IOException
+ {
+ File tempFile = getTempFileFor(file);
+ try
+ {
+ Files.move(tempFile.toPath(), file.toPath(), StandardCopyOption.ATOMIC_MOVE);
+ }
+ catch (FileAlreadyExistsException | AtomicMoveNotSupportedException e)
+ {
+ // The atomic move could fail depending on OS (mostly on old Windows versions)
+ // See OPENDJ-1811 for details
+ // Try to proceed with a non-atomic move
+ if (file.exists())
+ {
+ file.delete();
+ }
+ Files.move(tempFile.toPath(), file.toPath());
+ }
+ }
+
+ /** Returns a temporary file from provided file, by adding the ".tmp" suffix. */
+ private File getTempFileFor(File file) {
+ return new File(file.getParentFile(), file.getName() + FILE_EXTENSION_TEMP);
}
/** Returns a buffered reader on the provided file. */
--
Gitblit v1.10.0