Skip to content

Commit

Permalink
Allow to lazy create a DefaultFileRegion from a File
Browse files Browse the repository at this point in the history
Motivation:

We only provided a constructor in DefaultFileRegion that takes a FileChannel which means the File itself needs to get opened on construction. This has the problem that if you want to write a lot of Files very fast you may end up with may open FD's even if they are not needed yet. This can lead to hit the open FD limit of the OS.

Modifications:

Add a new constructor to DefaultFileRegion which allows to construct it from a File. The FileChannel will only be obtained when transferTo(...) is called or the DefaultFileRegion is explicit open'ed via open() (this is needed for the native epoll transport)

Result:

Less resource usage when writing a lot of DefaultFileRegion.
  • Loading branch information
normanmaurer committed Dec 11, 2014
1 parent 182c91f commit 12c9ce7
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1101,7 +1101,7 @@ JNIEXPORT jint JNICALL Java_io_netty_channel_epoll_Native_accept(JNIEnv * env, j
return socketFd;
}

JNIEXPORT jlong JNICALL Java_io_netty_channel_epoll_Native_sendfile(JNIEnv *env, jclass clazz, jint fd, jobject fileRegion, jlong base_off, jlong off, jlong len) {
JNIEXPORT jlong JNICALL Java_io_netty_channel_epoll_Native_sendfile0(JNIEnv *env, jclass clazz, jint fd, jobject fileRegion, jlong base_off, jlong off, jlong len) {
jobject fileChannel = (*env)->GetObjectField(env, fileRegion, fileChannelFieldId);
if (fileChannel == NULL) {
throwRuntimeException(env, "Unable to obtain FileChannel from FileRegion");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ void Java_io_netty_channel_epoll_Native_listen(JNIEnv * env, jclass clazz, jint
jboolean Java_io_netty_channel_epoll_Native_connect(JNIEnv * env, jclass clazz, jint fd, jbyteArray address, jint scopeId, jint port);
jboolean Java_io_netty_channel_epoll_Native_finishConnect(JNIEnv * env, jclass clazz, jint fd);
jint Java_io_netty_channel_epoll_Native_accept(JNIEnv * env, jclass clazz, jint fd);
jlong Java_io_netty_channel_epoll_Native_sendfile(JNIEnv *env, jclass clazz, jint fd, jobject fileRegion, jlong base_off, jlong off, jlong len);
jlong Java_io_netty_channel_epoll_Native_sendfile0(JNIEnv *env, jclass clazz, jint fd, jobject fileRegion, jlong base_off, jlong off, jlong len);
jobject Java_io_netty_channel_epoll_Native_remoteAddress(JNIEnv * env, jclass clazz, jint fd);
jobject Java_io_netty_channel_epoll_Native_localAddress(JNIEnv * env, jclass clazz, jint fd);
void Java_io_netty_channel_epoll_Native_setReuseAddress(JNIEnv * env, jclass clazz, jint fd, jint optval);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,16 @@ public static native long writevAddresses(int fd, long memoryAddress, int length
public static native int read(int fd, ByteBuffer buf, int pos, int limit) throws IOException;
public static native int readAddress(int fd, long address, int pos, int limit) throws IOException;

public static native long sendfile(
public static long sendfile(
int dest, DefaultFileRegion src, long baseOffset, long offset, long length) throws IOException {
// Open the file-region as it may be created via the lazy constructor. This is needed as we directly access
// the FileChannel field directly via JNI
src.open();

return sendfile0(dest, src, baseOffset, offset, length);
}

private static native long sendfile0(
int dest, DefaultFileRegion src, long baseOffset, long offset, long length) throws IOException;

public static int sendTo(
Expand Down
62 changes: 59 additions & 3 deletions transport/src/main/java/io/netty/channel/DefaultFileRegion.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,30 @@
package io.netty.channel;

import io.netty.util.AbstractReferenceCounted;
import io.netty.util.IllegalReferenceCountException;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;

/**
* Default {@link FileRegion} implementation which transfer data from a {@link FileChannel}.
* Default {@link FileRegion} implementation which transfer data from a {@link FileChannel} or {@link File}.
*
* Be aware that the {@link FileChannel} will be automatically closed once {@link #refCnt()} returns
* {@code 0}.
*/
public class DefaultFileRegion extends AbstractReferenceCounted implements FileRegion {

private static final InternalLogger logger = InternalLoggerFactory.getInstance(DefaultFileRegion.class);

private final FileChannel file;
private final File f;
private final long position;
private final long count;
private long transfered;
private FileChannel file;

/**
* Create a new instance
Expand All @@ -58,6 +61,47 @@ public DefaultFileRegion(FileChannel file, long position, long count) {
this.file = file;
this.position = position;
this.count = count;
f = null;
}

/**
* Create a new instance using the given {@link File}. The {@link File} will be opened lazily or
* explicitly via {@link #open()}.
*
* @param f the {@link File} which should be transfered
* @param position the position from which the transfer should start
* @param count the number of bytes to transfer
*/
public DefaultFileRegion(File f, long position, long count) {
if (f == null) {
throw new NullPointerException("f");
}
if (position < 0) {
throw new IllegalArgumentException("position must be >= 0 but was " + position);
}
if (count < 0) {
throw new IllegalArgumentException("count must be >= 0 but was " + count);
}
this.position = position;
this.count = count;
this.f = f;
}

/**
* Returns {@code true} if the {@link FileRegion} has a open file-descriptor
*/
public boolean isOpen() {
return file != null;
}

/**
* Explicitly open the underlying file-descriptor if not done yet.
*/
public void open() throws IOException {
if (!isOpen() && refCnt() > 0) {
// Only open if this DefaultFileRegion was not released yet.
file = new RandomAccessFile(f, "r").getChannel();
}
}

@Override
Expand Down Expand Up @@ -86,6 +130,11 @@ public long transferTo(WritableByteChannel target, long position) throws IOExcep
if (count == 0) {
return 0L;
}
if (refCnt() == 0) {
throw new IllegalReferenceCountException(0);
}
// Call open to make sure fc is initialized. This is a no-oop if we called it before.
open();

long written = file.transferTo(this.position + position, count, target);
if (written > 0) {
Expand All @@ -96,6 +145,13 @@ public long transferTo(WritableByteChannel target, long position) throws IOExcep

@Override
protected void deallocate() {
FileChannel file = this.file;

if (file == null) {
return;
}
this.file = null;

try {
file.close();
} catch (IOException e) {
Expand Down

0 comments on commit 12c9ce7

Please sign in to comment.