1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-12-14 20:20:30 +00:00

Try to ensure atomic writes to our ID file

- We now write to a "ids.json.new" file, then move that on top of the
   original ids.json file instead.

 - Use FileChannel.force to ensure the new file is properly flushed to
   disk. I can't really guarantee this'll work with the later
   Files.move, but it's better than not doing it!

Closes #1346.
This commit is contained in:
Jonathan Coates 2023-03-04 16:01:00 +00:00
parent c9bb534799
commit 566315947b
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06

View File

@ -12,13 +12,15 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Type;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.*;
import java.util.HashMap;
import java.util.Map;
@ -31,10 +33,13 @@ public final class IDAssigner {
}.getType();
private final Path idFile;
private final Path newIdFile;
private boolean atomicMove = true;
private @Nullable Map<String, Integer> ids;
public IDAssigner(Path path) {
idFile = path;
newIdFile = path.resolveSibling(path.getFileName() + ".new");
}
public synchronized int getNextId(String kind) {
@ -44,9 +49,22 @@ public final class IDAssigner {
var next = existing == null ? 0 : existing + 1;
ids.put(kind, next);
// We've changed the ID file, so save it back again.
try (Writer writer = Files.newBufferedWriter(idFile, StandardCharsets.UTF_8)) {
GSON.toJson(ids, writer);
// We've changed the ID file, so save it back again. We save to a temporary ".new" file, then move that over the
// original. This should reduce the risk of corrupting the file if Minecraft (or the computer!) is stopped.
try {
try (var channel = FileChannel.open(newIdFile, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE)) {
Writer writer = new BufferedWriter(Channels.newWriter(channel, StandardCharsets.UTF_8));
GSON.toJson(ids, writer);
writer.flush();
channel.force(false);
}
try {
Files.move(newIdFile, idFile, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
} catch (AtomicMoveNotSupportedException | UnsupportedOperationException e) {
Files.move(newIdFile, idFile, StandardCopyOption.REPLACE_EXISTING);
}
} catch (IOException e) {
LOG.error("Cannot update ID file '{}'", idFile, e);
}
@ -57,7 +75,11 @@ public final class IDAssigner {
private Map<String, Integer> loadIds() {
if (Files.isRegularFile(idFile)) {
try (Reader reader = Files.newBufferedReader(idFile, StandardCharsets.UTF_8)) {
return GSON.fromJson(reader, ID_TOKEN);
Map<String, Integer> result = GSON.fromJson(reader, ID_TOKEN);
if (result != null) return result;
// This happens when the file is empty. Odd, I know!
LOG.error("ID file {} is corrupted, computer IDs may be duplicated", idFile);
} catch (Exception e) {
LOG.error("Cannot load id file '" + idFile + "'", e);
}