/*
* Copyright 2016 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.kqueue;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.AbstractChannel;
import io.netty.channel.Channel;
import io.netty.channel.ChannelConfig;
import io.netty.channel.ChannelMetadata;
import io.netty.channel.EventLoop;
import io.netty.channel.RecvByteBufAllocator;
import io.netty.channel.socket.ChannelInputShutdownEvent;
import io.netty.channel.socket.ChannelInputShutdownReadComplete;
import io.netty.channel.unix.FileDescriptor;
import io.netty.channel.unix.UnixChannel;
import io.netty.util.ReferenceCountUtil;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.NotYetConnectedException;
import java.nio.channels.UnresolvedAddressException;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
abstract class AbstractKQueueChannel extends AbstractChannel implements UnixChannel {
private static final ChannelMetadata METADATA = new ChannelMetadata(false);
final BsdSocket socket;
private boolean readFilterEnabled = true;
private boolean writeFilterEnabled;
boolean readReadyRunnablePending;
boolean inputClosedSeenErrorOnRead;
/**
* This member variable means we don't have to have a map in {@link KQueueEventLoop} which associates the FDs
* from kqueue to instances of this class. This field will be initialized by JNI when modifying kqueue events.
* If there is no global reference when JNI gets a kqueue evSet call (aka this field is 0) then a global reference
* will be created and the address will be saved in this member variable. Then when we process a kevent in Java
* we can ask JNI to give us the {@link AbstractKQueueChannel} that corresponds to that event.
*/
long jniSelfPtr;
protected volatile boolean active;
AbstractKQueueChannel(Channel parent, BsdSocket fd, boolean active) {
this(parent, fd, active, false);
}
AbstractKQueueChannel(Channel parent, BsdSocket fd, boolean active, boolean writeFilterEnabled) {
super(parent);
socket = checkNotNull(fd, "fd");
this.active = active;
this.writeFilterEnabled = writeFilterEnabled;
}
@Override
public final FileDescriptor fd() {
return socket;
}
@Override
public boolean isActive() {
return active;
}
@Override
public ChannelMetadata metadata() {
return METADATA;
}
@Override
protected void doClose() throws Exception {
active = false;
// Even if we allow half closed sockets we should give up on reading. Otherwise we may allow a read attempt on a
// socket which has not even been connected yet. This has been observed to block during unit tests.
inputClosedSeenErrorOnRead = true;
// The FD will be closed, which will take of deleting from kqueue.
readFilterEnabled = writeFilterEnabled = false;
try {
((KQueueEventLoop) eventLoop()).remove(this);
} finally {
socket.close();
}
}
@Override
protected void doDisconnect() throws Exception {
doClose();
}
@Override
protected boolean isCompatible(EventLoop loop) {
return loop instanceof KQueueEventLoop;
}
@Override
public boolean isOpen() {
return socket.isOpen();
}
@Override
protected void doDeregister() throws Exception {
// Make sure we unregister our filters from kqueue!
readFilter(false);
writeFilter(false);
((KQueueEventLoop) eventLoop()).remove(this);
// Set the filters back to the initial state in case this channel is registered with another event loop.
readFilterEnabled = true;
}
@Override
protected final void doBeginRead() throws Exception {
// Channel.read() or ChannelHandlerContext.read() was called
final AbstractKQueueUnsafe unsafe = (AbstractKQueueUnsafe) unsafe();
unsafe.readPending = true;
// We must set the read flag here as it is possible the user didn't read in the last read loop, the
// executeReadReadyRunnable could read nothing, and if the user doesn't explicitly call read they will
// never get data after this.
readFilter(true);
// If auto read was toggled off on the last read loop then we may not be notified
// again if we didn't consume all the data. So we force a read operation here if there maybe more data.
if (unsafe.maybeMoreDataToRead) {
unsafe.executeReadReadyRunnable(config());
}
}
@Override
protected void doRegister() throws Exception {
// Just in case the previous EventLoop was shutdown abruptly, or an event is still pending on the old EventLoop
// make sure the readReadyRunnablePending variable is reset so we will be able to execute the Runnable on the
// new EventLoop.
readReadyRunnablePending = false;
// Add the write event first so we get notified of connection refused on the client side!
if (writeFilterEnabled) {
evSet0(Native.EVFILT_WRITE, Native.EV_ADD_CLEAR_ENABLE);
}
if (readFilterEnabled) {
evSet0(Native.EVFILT_READ, Native.EV_ADD_CLEAR_ENABLE);
}
}
@Override
protected abstract AbstractKQueueUnsafe newUnsafe();
@Override
public abstract KQueueChannelConfig config();
/**
* Returns an off-heap copy of the specified {@link ByteBuf}, and releases the original one.
*/
protected final ByteBuf newDirectBuffer(ByteBuf buf) {
return newDirectBuffer(buf, buf);
}
/**
* Returns an off-heap copy of the specified {@link ByteBuf}, and releases the specified holder.
* The caller must ensure that the holder releases the original {@link ByteBuf} when the holder is released by
* this method.
*/
protected final ByteBuf newDirectBuffer(Object holder, ByteBuf buf) {
final int readableBytes = buf.readableBytes();
if (readableBytes == 0) {
ReferenceCountUtil.safeRelease(holder);
return Unpooled.EMPTY_BUFFER;
}
final ByteBufAllocator alloc = alloc();
if (alloc.isDirectBufferPooled()) {
return newDirectBuffer0(holder, buf, alloc, readableBytes);
}
final ByteBuf directBuf = ByteBufUtil.threadLocalDirectBuffer();
if (directBuf == null) {
return newDirectBuffer0(holder, buf, alloc, readableBytes);
}
directBuf.writeBytes(buf, buf.readerIndex(), readableBytes);
ReferenceCountUtil.safeRelease(holder);
return directBuf;
}
private static ByteBuf newDirectBuffer0(Object holder, ByteBuf buf, ByteBufAllocator alloc, int capacity) {
final ByteBuf directBuf = alloc.directBuffer(capacity);
directBuf.writeBytes(buf, buf.readerIndex(), capacity);
ReferenceCountUtil.safeRelease(holder);
return directBuf;
}
protected static void checkResolvable(InetSocketAddress addr) {
if (addr.isUnresolved()) {
throw new UnresolvedAddressException();
}
}
/**
* Read bytes into the given {@link ByteBuf} and return the amount.
*/
protected final int doReadBytes(ByteBuf byteBuf) throws Exception {
int writerIndex = byteBuf.writerIndex();
int localReadAmount;
unsafe().recvBufAllocHandle().attemptedBytesRead(byteBuf.writableBytes());
if (byteBuf.hasMemoryAddress()) {
localReadAmount = socket.readAddress(byteBuf.memoryAddress(), writerIndex, byteBuf.capacity());
} else {
ByteBuffer buf = byteBuf.internalNioBuffer(writerIndex, byteBuf.writableBytes());
localReadAmount = socket.read(buf, buf.position(), buf.limit());
}
if (localReadAmount > 0) {
byteBuf.writerIndex(writerIndex + localReadAmount);
}
return localReadAmount;
}
protected final int doWriteBytes(ByteBuf buf, int writeSpinCount) throws Exception {
int readableBytes = buf.readableBytes();
int writtenBytes = 0;
if (buf.hasMemoryAddress()) {
long memoryAddress = buf.memoryAddress();
int readerIndex = buf.readerIndex();
int writerIndex = buf.writerIndex();
for (int i = writeSpinCount; i > 0; --i) {
int localFlushedAmount = socket.writeAddress(memoryAddress, readerIndex, writerIndex);
if (localFlushedAmount > 0) {
writtenBytes += localFlushedAmount;
if (writtenBytes == readableBytes) {
return writtenBytes;
}
readerIndex += localFlushedAmount;
} else {
break;
}
}
} else {
ByteBuffer nioBuf;
if (buf.nioBufferCount() == 1) {
nioBuf = buf.internalNioBuffer(buf.readerIndex(), buf.readableBytes());
} else {
nioBuf = buf.nioBuffer();
}
for (int i = writeSpinCount; i > 0; --i) {
int pos = nioBuf.position();
int limit = nioBuf.limit();
int localFlushedAmount = socket.write(nioBuf, pos, limit);
if (localFlushedAmount > 0) {
nioBuf.position(pos + localFlushedAmount);
writtenBytes += localFlushedAmount;
if (writtenBytes == readableBytes) {
return writtenBytes;
}
} else {
break;
}
}
}
if (writtenBytes < readableBytes) {
// Returned EAGAIN need to wait until we are allowed to write again.
writeFilter(true);
}
return writtenBytes;
}
final boolean shouldBreakReadReady(ChannelConfig config) {
return socket.isInputShutdown() && (inputClosedSeenErrorOnRead || !isAllowHalfClosure(config));
}
final boolean isAllowHalfClosure(ChannelConfig config) {
return config instanceof KQueueSocketChannelConfig &&
((KQueueSocketChannelConfig) config).isAllowHalfClosure();
}
final void clearReadFilter() {
// Only clear if registered with an EventLoop as otherwise
if (isRegistered()) {
final EventLoop loop = eventLoop();
final AbstractKQueueUnsafe unsafe = (AbstractKQueueUnsafe) unsafe();
if (loop.inEventLoop()) {
unsafe.clearReadFilter0();
} else {
// schedule a task to clear the EPOLLIN as it is not safe to modify it directly
loop.execute(new Runnable() {
@Override
public void run() {
if (!unsafe.readPending && !config().isAutoRead()) {
// Still no read triggered so clear it now
unsafe.clearReadFilter0();
}
}
});
}
} else {
// The EventLoop is not registered atm so just update the flags so the correct value
// will be used once the channel is registered
readFilterEnabled = false;
}
}
void readFilter(boolean readFilterEnabled) throws IOException {
if (this.readFilterEnabled != readFilterEnabled) {
this.readFilterEnabled = readFilterEnabled;
evSet(Native.EVFILT_READ, readFilterEnabled ? Native.EV_ADD_CLEAR_ENABLE : Native.EV_DELETE_DISABLE);
}
}
void writeFilter(boolean writeFilterEnabled) throws IOException {
if (this.writeFilterEnabled != writeFilterEnabled) {
this.writeFilterEnabled = writeFilterEnabled;
evSet(Native.EVFILT_WRITE, writeFilterEnabled ? Native.EV_ADD_CLEAR_ENABLE : Native.EV_DELETE_DISABLE);
}
}
private void evSet(short filter, short flags) {
if (isOpen() && isRegistered()) {
evSet0(filter, flags);
}
}
private void evSet0(short filter, short flags) {
((KQueueEventLoop) eventLoop()).evSet(this, filter, flags, 0);
}
abstract class AbstractKQueueUnsafe extends AbstractUnsafe {
boolean readPending;
boolean maybeMoreDataToRead;
private KQueueRecvByteAllocatorHandle allocHandle;
private final Runnable readReadyRunnable = new Runnable() {
@Override
public void run() {
readReadyRunnablePending = false;
readReady(recvBufAllocHandle());
}
};
final void readReady(long numberBytesPending) {
KQueueRecvByteAllocatorHandle allocHandle = recvBufAllocHandle();
allocHandle.numberBytesPending(numberBytesPending);
readReady(allocHandle);
}
abstract void readReady(KQueueRecvByteAllocatorHandle allocHandle);
final void readReadyBefore() { maybeMoreDataToRead = false; }
final void readReadyFinally(ChannelConfig config) {
maybeMoreDataToRead = allocHandle.maybeMoreDataToRead();
// Check if there is a readPending which was not processed yet.
// This could be for two reasons:
// * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(...) method
// * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(...) method
//
// See https://github.com/netty/netty/issues/2254
if (!readPending && !config.isAutoRead()) {
clearReadFilter0();
} else if (readPending && maybeMoreDataToRead) {
// trigger a read again as there may be something left to read and because of ET we
// will not get notified again until we read everything from the socket
//
// It is possible the last fireChannelRead call could cause the user to call read() again, or if
// autoRead is true the call to channelReadComplete would also call read, but maybeMoreDataToRead is set
// to false before every read operation to prevent re-entry into readReady() we will not read from
// the underlying OS again unless the user happens to call read again.
executeReadReadyRunnable(config);
}
}
void writeReady() {
if (socket.isOutputShutdown()) {
return;
}
// directly call super.flush0() to force a flush now
super.flush0();
}
/**
* Shutdown the input side of the channel.
*/
void shutdownInput(boolean readEOF) {
if (!socket.isInputShutdown()) {
if (isAllowHalfClosure(config())) {
try {
socket.shutdown(true, false);
} catch (IOException ignored) {
// We attempted to shutdown and failed, which means the input has already effectively been
// shutdown.
fireEventAndClose(ChannelInputShutdownEvent.INSTANCE);
return;
} catch (NotYetConnectedException ignore) {
// We attempted to shutdown and failed, which means the input has already effectively been
// shutdown.
}
pipeline().fireUserEventTriggered(ChannelInputShutdownEvent.INSTANCE);
} else {
close(voidPromise());
}
} else if (!readEOF) {
inputClosedSeenErrorOnRead = true;
pipeline().fireUserEventTriggered(ChannelInputShutdownReadComplete.INSTANCE);
}
}
final void readEOF() {
// This must happen before we attempt to read. This will ensure reading continues until an error occurs.
final KQueueRecvByteAllocatorHandle allocHandle = recvBufAllocHandle();
allocHandle.readEOF();
if (isActive()) {
// If it is still active, we need to call readReady as otherwise we may miss to
// read pending data from the underlying file descriptor.
// See https://github.com/netty/netty/issues/3709
readReady(allocHandle);
} else {
// Just to be safe make sure the input marked as closed.
shutdownInput(true);
}
}
@Override
public KQueueRecvByteAllocatorHandle recvBufAllocHandle() {
if (allocHandle == null) {
allocHandle = new KQueueRecvByteAllocatorHandle(
(RecvByteBufAllocator.ExtendedHandle) super.recvBufAllocHandle());
}
return allocHandle;
}
final void executeReadReadyRunnable(ChannelConfig config) {
if (readReadyRunnablePending || !isActive() || shouldBreakReadReady(config)) {
return;
}
readReadyRunnablePending = true;
eventLoop().execute(readReadyRunnable);
}
protected final void clearReadFilter0() {
assert eventLoop().inEventLoop();
try {
readPending = false;
readFilter(false);
} catch (IOException e) {
// When this happens there is something completely wrong with either the filedescriptor or epoll,
// so fire the exception through the pipeline and close the Channel.
pipeline().fireExceptionCaught(e);
unsafe().close(unsafe().voidPromise());
}
}
private void fireEventAndClose(Object evt) {
pipeline().fireUserEventTriggered(evt);
close(voidPromise());
}
}
}