diff --git a/core/src/main/java/hudson/util/AtomicFileWriter.java b/core/src/main/java/hudson/util/AtomicFileWriter.java index e67eafbb64..968c061668 100644 --- a/core/src/main/java/hudson/util/AtomicFileWriter.java +++ b/core/src/main/java/hudson/util/AtomicFileWriter.java @@ -23,6 +23,8 @@ */ package hudson.util; +import jenkins.util.SystemProperties; + import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.io.File; @@ -52,6 +54,15 @@ public class AtomicFileWriter extends Writer { private static final Logger LOGGER = Logger.getLogger(AtomicFileWriter.class.getName()); + private static /* final */ boolean LOSE_INTEGRITY = SystemProperties.getBoolean( + AtomicFileWriter.class.getName() + ".LOSE_DATA_INTEGRITY_FOR_PERFORMANCE"); + + static { + if (LOSE_INTEGRITY) { + LOGGER.log(Level.SEVERE, "LOSE_DATA_INTEGRITY_FOR_PERFORMANCE flag used, YOU RISK LOSING DATA."); + } + } + private final Writer core; private final Path tmpPath; private final Path destPath; @@ -94,6 +105,21 @@ public class AtomicFileWriter extends Writer { * @param charset File charset to write. */ public AtomicFileWriter(@Nonnull Path destinationPath, @Nonnull Charset charset) throws IOException { + // See FileChannelWriter docs to understand why we do not cause a force() call on flush() from AtomicFileWriter. + this(destinationPath, charset, false, true); + } + + /** + * DO NOT USE THIS METHOD, OR YOU WILL LOSE DATA INTEGRITY. + * + * @param destinationPath the destination path where to write the content when committed. + * @param charset File charset to write. + * @param integrityOnFlush do not force writing to disk when flushing + * @param integrityOnClose do not force writing to disk when closing + * @deprecated use {@link AtomicFileWriter#AtomicFileWriter(Path, Charset)} + */ + @Deprecated + public AtomicFileWriter(@Nonnull Path destinationPath, @Nonnull Charset charset, boolean integrityOnFlush, boolean integrityOnClose) throws IOException { if (charset == null) { // be extra-defensive if people don't care throw new IllegalArgumentException("charset is null"); } @@ -117,7 +143,15 @@ public class AtomicFileWriter extends Writer { throw new IOException("Failed to create a temporary file in "+ dir,e); } - core = new FileChannelWriter(tmpPath, charset, false, StandardOpenOption.WRITE); + if (LOSE_INTEGRITY) { + // We should log it in WARN, but as this code is called often, this would create huge logs in prod system + // Hence why a similar log is logged once above in a static block. + LOGGER.log(Level.FINE, "WARNING: YOU SET A FLAG THAT COULD LEAD TO DATA LOSS."); + integrityOnFlush = false; + integrityOnClose = false; + } + + core = new FileChannelWriter(tmpPath, charset, integrityOnFlush, integrityOnClose, StandardOpenOption.WRITE); } @Override diff --git a/core/src/main/java/hudson/util/FileChannelWriter.java b/core/src/main/java/hudson/util/FileChannelWriter.java index 591009e8a5..8d623740d1 100644 --- a/core/src/main/java/hudson/util/FileChannelWriter.java +++ b/core/src/main/java/hudson/util/FileChannelWriter.java @@ -46,6 +46,11 @@ public class FileChannelWriter extends Writer { */ private boolean forceOnFlush; + /** + * See forceOnFlush. You probably never want to set forceOnClose to false. + */ + private boolean forceOnClose; + /** * @param filePath the path of the file to write to. * @param charset the charset to use when writing characters. @@ -53,9 +58,10 @@ public class FileChannelWriter extends Writer { * @param options the options for opening the file. * @throws IOException if something went wrong. */ - FileChannelWriter(Path filePath, Charset charset, boolean forceOnFlush, OpenOption... options) throws IOException { + FileChannelWriter(Path filePath, Charset charset, boolean forceOnFlush, boolean forceOnClose, OpenOption... options) throws IOException { this.charset = charset; this.forceOnFlush = forceOnFlush; + this.forceOnClose = forceOnClose; channel = FileChannel.open(filePath, options); } @@ -79,7 +85,9 @@ public class FileChannelWriter extends Writer { @Override public void close() throws IOException { if(channel.isOpen()) { - channel.force(true); + if (forceOnClose) { + channel.force(true); + } channel.close(); } } diff --git a/core/src/test/java/hudson/util/FileChannelWriterTest.java b/core/src/test/java/hudson/util/FileChannelWriterTest.java index 59ac01ff96..1b1307d8b6 100644 --- a/core/src/test/java/hudson/util/FileChannelWriterTest.java +++ b/core/src/test/java/hudson/util/FileChannelWriterTest.java @@ -27,7 +27,7 @@ public class FileChannelWriterTest { @Before public void setUp() throws Exception { file = temporaryFolder.newFile(); - writer = new FileChannelWriter(file.toPath(), StandardCharsets.UTF_8, true, StandardOpenOption.WRITE); + writer = new FileChannelWriter(file.toPath(), StandardCharsets.UTF_8, true, true, StandardOpenOption.WRITE); } @Test