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:
parent
c9bb534799
commit
566315947b
@ -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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user