Relax test fsync

- Add Durability, a process-wide fsync policy defaulting to real barriers and switchable to relaxed through the iris.durability system property
- Route the history, studio, journal, structure and datapack fsync barriers through Durability so relaxed mode skips both the force and the channel open
- Add the DurabilityMode test rule and enable relaxed durability on the twenty slowest core stores tests
- Split GenerationHistoryTest into epoch, legacy adoption and recovery classes over a shared GenerationHistorySupport base
This commit is contained in:
Brian Neumann-Fopiano committed 2026-09-08 04:18:01 -04:00
1 parent c75cfd387e
commit c332c86849
34 files changed
+862 -621

No files matched your search

@@ -37,6 +37,7 @@ import art.arcane.iris.core.structure.authoring.StructureTransactionWriter;
import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.iris.engine.object.IrisImportedStructureControl;
import art.arcane.iris.util.common.format.C;
import art.arcane.iris.util.common.io.Durability;
import art.arcane.iris.util.common.plugin.VolmitSender;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.io.IO;
@@ -5396,17 +5397,25 @@ public final class DatapackIngestService {
}
private static void forceFile(Path file) throws IOException {
if (!Durability.enabled()) {
return;
}
try (FileChannel channel = FileChannel.open(file, StandardOpenOption.WRITE)) {
channel.force(true);
Durability.force(channel);
}
}
private static void forceDirectoryIfSupported(Path directory) throws IOException {
if (!Durability.enabled()) {
return;
}
if (!Files.getFileStore(directory).supportsFileAttributeView("posix")) {
return;
}
try (FileChannel channel = FileChannel.open(directory, StandardOpenOption.READ)) {
channel.force(true);
Durability.force(channel);
}
}
@@ -2,6 +2,7 @@ package art.arcane.iris.core.lifecycle;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.util.common.io.Durability;
import java.io.File;
import java.io.IOException;
import java.nio.channels.FileChannel;
@@ -19,11 +20,15 @@ final class DirectoryDurability {
}
static void forceDirectoryRequired(Path directory) throws IOException {
if (!Durability.enabled()) {
return;
}
if (File.separatorChar == '\\') {
return;
}
try (FileChannel channel = FileChannel.open(directory, StandardOpenOption.READ)) {
channel.force(true);
Durability.force(channel);
} catch (UnsupportedOperationException failure) {
throw new IOException("Directory durability sync is unavailable for " + directory + ".", failure);
}
@@ -5,6 +5,7 @@ import art.arcane.iris.core.IrisWorldStorage;
import art.arcane.iris.core.WorldSlotKey;
import art.arcane.iris.core.lifecycle.BukkitWorldConfiguration.WorldGeneratorSnapshot;
import art.arcane.iris.util.common.io.Durability;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -345,7 +346,7 @@ public final class WorldReplacementJournal {
while (buffer.hasRemaining()) {
channel.write(buffer);
}
channel.force(true);
Durability.force(channel);
}
forceDirectoryRequired(parent);
try {
@@ -12,6 +12,7 @@ import art.arcane.iris.core.structure.authoring.StructureTransactionWriter;
import art.arcane.iris.core.structure.authoring.StructureWriteOptions;
import art.arcane.iris.core.structure.authoring.StructureWriteResult;
import art.arcane.iris.engine.framework.structure.StructureResourceBundleGraphCompiler;
import art.arcane.iris.util.common.io.Durability;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
@@ -303,7 +304,7 @@ final class JigsawStudioHistoryStore {
while (buffer.hasRemaining()) {
channel.write(buffer);
}
channel.force(true);
Durability.force(channel);
}
try {
Files.move(
@@ -374,6 +375,10 @@ final class JigsawStudioHistoryStore {
}
private static void forceDirectory(Path directory) throws IOException {
if (!Durability.enabled()) {
return;
}
// A directory can only be opened and fsynced on a POSIX filesystem; Windows rejects the
// open outright. Matches DirectoryDurability and DatapackIngestService, which already
// skip the barrier there.
@@ -381,7 +386,7 @@ final class JigsawStudioHistoryStore {
return;
}
try (FileChannel channel = FileChannel.open(directory, StandardOpenOption.READ)) {
channel.force(true);
Durability.force(channel);
}
}
@@ -18,6 +18,7 @@
package art.arcane.iris.core.structure.authoring;
import art.arcane.iris.util.common.io.Durability;
import java.io.IOException;
import java.io.InputStream;
import java.nio.channels.FileChannel;
@@ -70,17 +71,25 @@ interface StructureFileOperations {
}
default void forceFile(Path path) throws IOException {
if (!Durability.enabled()) {
return;
}
try (FileChannel channel = FileChannel.open(path, StandardOpenOption.WRITE)) {
channel.force(true);
Durability.force(channel);
}
}
default void forceDirectory(Path path) throws IOException {
if (!Durability.enabled()) {
return;
}
if (!Files.getFileStore(path).supportsFileAttributeView("posix")) {
return;
}
try (FileChannel channel = FileChannel.open(path, StandardOpenOption.READ)) {
channel.force(true);
Durability.force(channel);
}
}
}
@@ -1,6 +1,7 @@
package art.arcane.iris.engine.history;
import art.arcane.iris.core.pack.AtomicDirectoryPublisher;
import art.arcane.iris.util.common.io.Durability;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import java.io.File;
@@ -1153,11 +1154,15 @@ public final class GenerationHistory {
}
private static void forceDirectory(Path directory) throws IOException {
if (!Durability.enabled()) {
return;
}
if (File.separatorChar == '\\') {
return;
}
try (FileChannel channel = FileChannel.open(directory, StandardOpenOption.READ)) {
channel.force(true);
Durability.force(channel);
} catch (UnsupportedOperationException exception) {
throw new IOException("Generation history directory cannot be durability-synced: "
+ directory, exception);
@@ -1,5 +1,6 @@
package art.arcane.iris.engine.history;
import art.arcane.iris.util.common.io.Durability;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
@@ -282,7 +283,7 @@ public final class GenerationHistoryStore {
while (buffer.hasRemaining()) {
channel.write(buffer);
}
channel.force(true);
Durability.force(channel);
}
try {
if (replaceExisting) {
@@ -352,7 +353,7 @@ public final class GenerationHistoryStore {
while (buffer.hasRemaining()) {
channel.write(buffer);
}
channel.force(true);
Durability.force(channel);
}
try {
Files.move(temporary, metadata, StandardCopyOption.ATOMIC_MOVE);
@@ -439,11 +440,15 @@ public final class GenerationHistoryStore {
}
private static void forceDirectory(Path directory) throws IOException {
if (!Durability.enabled()) {
return;
}
if (File.separatorChar == '\\') {
return;
}
try (FileChannel channel = FileChannel.open(directory, StandardOpenOption.READ)) {
channel.force(true);
Durability.force(channel);
} catch (UnsupportedOperationException exception) {
throw new IOException("Generation history directory cannot be durability-synced: " + directory, exception);
}
@@ -1,6 +1,7 @@
package art.arcane.iris.engine.history;
import art.arcane.iris.engine.hydrology.HydrologyFeatureType;
import art.arcane.iris.util.common.io.Durability;
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
@@ -1949,14 +1950,14 @@ public final class GenerationSemanticIndex {
channel.write(entry);
}
}
channel.force(true);
Durability.force(channel);
if (created) {
RegionShard.forceDirectory(directory);
}
} catch (IOException failure) {
try {
channel.truncate(size);
channel.force(true);
Durability.force(channel);
if (created) {
RegionShard.forceDirectory(directory);
}
@@ -2079,7 +2080,7 @@ public final class GenerationSemanticIndex {
private static void truncateTail(RandomAccessFile file, long length) throws IOException {
file.setLength(length);
file.getFD().sync();
Durability.force(file.getFD());
}
private static IOException invalid(Path file, String reason) {
@@ -2951,16 +2952,20 @@ public final class GenerationSemanticIndex {
while (buffer.hasRemaining()) {
channel.write(buffer);
}
channel.force(true);
Durability.force(channel);
}
}
private static void forceDirectory(Path directory) throws IOException {
if (!Durability.enabled()) {
return;
}
if (File.separatorChar == '\\') {
return;
}
try (FileChannel channel = FileChannel.open(directory, StandardOpenOption.READ)) {
channel.force(true);
Durability.force(channel);
} catch (UnsupportedOperationException error) {
throw new IOException("Generation semantic directory cannot be durability-synced", error);
}
@@ -0,0 +1,33 @@
package art.arcane.iris.util.common.io;
import java.io.FileDescriptor;
import java.io.IOException;
import java.nio.channels.FileChannel;
public final class Durability {
public static final String MODE_PROPERTY = "iris.durability";
private static final String RELAXED = "relaxed";
private Durability() {
}
public static boolean enabled() {
return !RELAXED.equalsIgnoreCase(System.getProperty(MODE_PROPERTY));
}
public static void force(FileChannel channel) throws IOException {
if (!enabled()) {
return;
}
channel.force(true);
}
public static void force(FileDescriptor descriptor) throws IOException {
if (!enabled()) {
return;
}
descriptor.sync();
}
}