Skip to content

Commit

Permalink
[JENKINS-34855] Create a FileChannelWriter
Browse files Browse the repository at this point in the history
  • Loading branch information
batmat committed Dec 2, 2017
1 parent 616a62a commit 22efbad
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 0 deletions.
57 changes: 57 additions & 0 deletions 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).
*
* <p>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.</p>
*
* @see <a href="https://issues.jenkins-ci.org/browse/JENKINS-34855">JENKINS-34855</a>
* @see <a href="https://github.com/jenkinsci/jenkins/pull/2548">PR-2548</a>
*/
@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();
}
}
}
63 changes: 63 additions & 0 deletions 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));
}
}

0 comments on commit 22efbad

Please sign in to comment.