From 22efbad9450fc95748e0bc3bee04ec49702d7ed2 Mon Sep 17 00:00:00 2001 From: Baptiste Mathus Date: Fri, 1 Dec 2017 23:17:18 +0100 Subject: [PATCH] [JENKINS-34855] Create a FileChannelWriter --- .../java/hudson/util/FileChannelWriter.java | 57 +++++++++++++++++ .../hudson/util/FileChannelWriterTest.java | 63 +++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 core/src/main/java/hudson/util/FileChannelWriter.java create mode 100644 core/src/test/java/hudson/util/FileChannelWriterTest.java diff --git a/core/src/main/java/hudson/util/FileChannelWriter.java b/core/src/main/java/hudson/util/FileChannelWriter.java new file mode 100644 index 0000000000..fd28d73427 --- /dev/null +++ b/core/src/main/java/hudson/util/FileChannelWriter.java @@ -0,0 +1,57 @@ +package hudson.util; + +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; + +import java.io.IOException; +import java.io.Writer; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.channels.FileChannel; +import java.nio.charset.Charset; +import java.nio.file.OpenOption; +import java.nio.file.Path; + +/** + * This class has been created to help make {@link AtomicFileWriter} hopefully more reliable in some corner cases. + * We created this wrapper to be able to access {@link FileChannel#force(boolean)} which seems to be one of the rare + * ways to actually have a guarantee that data be flushed to the physical device (only guaranteed for local, not for + * remote obviously though). + * + *

The goal using this is to reduce as much as we can the likeliness to see zero-length files be created in place + * of the original ones.

+ * + * @see JENKINS-34855 + * @see PR-2548 + */ +@Restricted(NoExternalUse.class) +public class FileChannelWriter extends Writer { + + private final Charset charset; + private final FileChannel channel; + + FileChannelWriter(Path filePath, Charset charset, OpenOption... options) throws IOException { + this.charset = charset; + channel = FileChannel.open(filePath, options); + } + + @Override + public void write(char cbuf[], int off, int len) throws IOException { + final CharBuffer charBuffer = CharBuffer.wrap(cbuf, off, len); + ByteBuffer byteBuffer = charset.encode(charBuffer); + channel.write(byteBuffer); + } + + @Override + public void flush() throws IOException { + channel.force(true); + } + + @Override + public void close() throws IOException { + if(channel.isOpen()) { + 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 new file mode 100644 index 0000000000..c8aaf4c35d --- /dev/null +++ b/core/src/test/java/hudson/util/FileChannelWriterTest.java @@ -0,0 +1,63 @@ +package hudson.util; + +import org.apache.commons.io.FileUtils; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.io.IOException; +import java.nio.channels.ClosedChannelException; +import java.nio.charset.StandardCharsets; +import java.nio.file.StandardOpenOption; + +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.core.IsEqual.equalTo; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +public class FileChannelWriterTest { + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + File file; + FileChannelWriter writer; + + @Before + public void setUp() throws Exception { + file = temporaryFolder.newFile(); + writer = new FileChannelWriter(file.toPath(), StandardCharsets.UTF_8, StandardOpenOption.WRITE); + } + + @Test + public void write() throws Exception { + writer.write("helloooo"); + writer.close(); + + assertContent("helloooo"); + } + + + @Test + public void flush() throws Exception { + writer.write("hello é è à".toCharArray()); + + writer.flush(); + assertContent("hello é è à"); + } + + @Test(expected = ClosedChannelException.class) + public void close() throws Exception { + writer.write("helloooo"); + writer.close(); + + writer.write("helloooo"); + fail("Should have failed the line above"); + } + + + private void assertContent(String string) throws IOException { + assertThat(FileUtils.readFileToString(file, StandardCharsets.UTF_8), equalTo(string)); + } +}