[#107] Add support for closing either input or output part of a channel
- Add ChannelOption.ALLOW_HALF_CLOSURE - If true, ChannelInputShutdownEvent is fired via userEventTriggered() when the remote peer shuts down its output, and the connection is not closed until a user calls close() explicitly. - If false, the connection is closed immediately as it did before. - Add SocketChannel.isInputShutdown() - Add & improve test cases related with half-closed sockets
This commit is contained in:
parent
bfdc28bd67
commit
d03de0f3ca
@ -0,0 +1,135 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012 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.testsuite.transport.socket;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
import io.netty.bootstrap.ServerBootstrap;
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.channel.ChannelInboundByteHandlerAdapter;
|
||||||
|
import io.netty.channel.ChannelInputShutdownEvent;
|
||||||
|
import io.netty.channel.ChannelOption;
|
||||||
|
import io.netty.channel.socket.SocketChannel;
|
||||||
|
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.SynchronousQueue;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class SocketShutdownOutputByPeerTest extends AbstractServerSocketTest {
|
||||||
|
|
||||||
|
@Test(timeout = 30000)
|
||||||
|
public void testShutdownOutput() throws Throwable {
|
||||||
|
run();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testShutdownOutput(ServerBootstrap sb) throws Throwable {
|
||||||
|
TestHandler h = new TestHandler();
|
||||||
|
Socket s = new Socket();
|
||||||
|
try {
|
||||||
|
sb.childHandler(h).childOption(ChannelOption.ALLOW_HALF_CLOSURE, true).bind().sync();
|
||||||
|
|
||||||
|
s.connect(addr, 10000);
|
||||||
|
s.getOutputStream().write(1);
|
||||||
|
|
||||||
|
assertEquals(1, (int) h.queue.take());
|
||||||
|
|
||||||
|
assertTrue(h.ch.isOpen());
|
||||||
|
assertTrue(h.ch.isActive());
|
||||||
|
assertFalse(h.ch.isInputShutdown());
|
||||||
|
assertFalse(h.ch.isOutputShutdown());
|
||||||
|
|
||||||
|
s.shutdownOutput();
|
||||||
|
|
||||||
|
h.halfClosure.await();
|
||||||
|
|
||||||
|
assertTrue(h.ch.isOpen());
|
||||||
|
assertTrue(h.ch.isActive());
|
||||||
|
assertTrue(h.ch.isInputShutdown());
|
||||||
|
assertFalse(h.ch.isOutputShutdown());
|
||||||
|
assertEquals(1, h.closure.getCount());
|
||||||
|
} finally {
|
||||||
|
s.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 30000)
|
||||||
|
public void testShutdownOutputWithoutOption() throws Throwable {
|
||||||
|
run();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testShutdownOutputWithoutOption(ServerBootstrap sb) throws Throwable {
|
||||||
|
TestHandler h = new TestHandler();
|
||||||
|
Socket s = new Socket();
|
||||||
|
try {
|
||||||
|
sb.childHandler(h).bind().sync();
|
||||||
|
|
||||||
|
s.connect(addr, 10000);
|
||||||
|
s.getOutputStream().write(1);
|
||||||
|
|
||||||
|
assertEquals(1, (int) h.queue.take());
|
||||||
|
|
||||||
|
assertTrue(h.ch.isOpen());
|
||||||
|
assertTrue(h.ch.isActive());
|
||||||
|
assertFalse(h.ch.isInputShutdown());
|
||||||
|
assertFalse(h.ch.isOutputShutdown());
|
||||||
|
|
||||||
|
s.shutdownOutput();
|
||||||
|
|
||||||
|
h.closure.await();
|
||||||
|
|
||||||
|
assertFalse(h.ch.isOpen());
|
||||||
|
assertFalse(h.ch.isActive());
|
||||||
|
assertTrue(h.ch.isInputShutdown());
|
||||||
|
assertTrue(h.ch.isOutputShutdown());
|
||||||
|
|
||||||
|
assertEquals(1, h.halfClosure.getCount());
|
||||||
|
} finally {
|
||||||
|
s.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class TestHandler extends ChannelInboundByteHandlerAdapter {
|
||||||
|
volatile SocketChannel ch;
|
||||||
|
final BlockingQueue<Byte> queue = new SynchronousQueue<Byte>();
|
||||||
|
final CountDownLatch halfClosure = new CountDownLatch(1);
|
||||||
|
final CountDownLatch closure = new CountDownLatch(1);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
ch = (SocketChannel) ctx.channel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
closure.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void inboundBufferUpdated(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
|
||||||
|
queue.offer(in.readByte());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
||||||
|
if (evt instanceof ChannelInputShutdownEvent) {
|
||||||
|
halfClosure.countDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -30,7 +30,7 @@ import java.util.concurrent.SynchronousQueue;
|
|||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
public class SocketShutdownOutputTest extends AbstractClientSocketTest {
|
public class SocketShutdownOutputBySelfTest extends AbstractClientSocketTest {
|
||||||
|
|
||||||
@Test(timeout = 30000)
|
@Test(timeout = 30000)
|
||||||
public void testShutdownOutput() throws Throwable {
|
public void testShutdownOutput() throws Throwable {
|
||||||
@ -51,10 +51,20 @@ public class SocketShutdownOutputTest extends AbstractClientSocketTest {
|
|||||||
ch.write(Unpooled.wrappedBuffer(new byte[] { 1 })).sync();
|
ch.write(Unpooled.wrappedBuffer(new byte[] { 1 })).sync();
|
||||||
assertEquals(1, s.getInputStream().read());
|
assertEquals(1, s.getInputStream().read());
|
||||||
|
|
||||||
|
assertTrue(h.ch.isOpen());
|
||||||
|
assertTrue(h.ch.isActive());
|
||||||
|
assertFalse(h.ch.isInputShutdown());
|
||||||
|
assertFalse(h.ch.isOutputShutdown());
|
||||||
|
|
||||||
// Make the connection half-closed and ensure read() returns -1.
|
// Make the connection half-closed and ensure read() returns -1.
|
||||||
ch.shutdownOutput();
|
ch.shutdownOutput();
|
||||||
assertEquals(-1, s.getInputStream().read());
|
assertEquals(-1, s.getInputStream().read());
|
||||||
|
|
||||||
|
assertTrue(h.ch.isOpen());
|
||||||
|
assertTrue(h.ch.isActive());
|
||||||
|
assertFalse(h.ch.isInputShutdown());
|
||||||
|
assertTrue(h.ch.isOutputShutdown());
|
||||||
|
|
||||||
// If half-closed, the peer should be able to write something.
|
// If half-closed, the peer should be able to write something.
|
||||||
s.getOutputStream().write(1);
|
s.getOutputStream().write(1);
|
||||||
assertEquals(1, (int) h.queue.take());
|
assertEquals(1, (int) h.queue.take());
|
||||||
@ -68,8 +78,14 @@ public class SocketShutdownOutputTest extends AbstractClientSocketTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static class TestHandler extends ChannelInboundByteHandlerAdapter {
|
private static class TestHandler extends ChannelInboundByteHandlerAdapter {
|
||||||
|
volatile SocketChannel ch;
|
||||||
final BlockingQueue<Byte> queue = new SynchronousQueue<Byte>();
|
final BlockingQueue<Byte> queue = new SynchronousQueue<Byte>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
ch = (SocketChannel) ctx.channel();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void inboundBufferUpdated(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
|
public void inboundBufferUpdated(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
|
||||||
queue.offer(in.readByte());
|
queue.offer(in.readByte());
|
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012 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;
|
||||||
|
|
||||||
|
public final class ChannelInputShutdownEvent {
|
||||||
|
|
||||||
|
public static final ChannelInputShutdownEvent INSTANCE = new ChannelInputShutdownEvent();
|
||||||
|
|
||||||
|
private ChannelInputShutdownEvent() { }
|
||||||
|
}
|
@ -32,6 +32,9 @@ public class ChannelOption<T> extends UniqueName {
|
|||||||
new ChannelOption<Integer>("CONNECT_TIMEOUT_MILLIS");
|
new ChannelOption<Integer>("CONNECT_TIMEOUT_MILLIS");
|
||||||
public static final ChannelOption<Integer> WRITE_SPIN_COUNT =
|
public static final ChannelOption<Integer> WRITE_SPIN_COUNT =
|
||||||
new ChannelOption<Integer>("WRITE_SPIN_COUNT");
|
new ChannelOption<Integer>("WRITE_SPIN_COUNT");
|
||||||
|
public static final ChannelOption<Boolean> ALLOW_HALF_CLOSURE =
|
||||||
|
new ChannelOption<Boolean>("ALLOW_HALF_CLOSURE");
|
||||||
|
|
||||||
|
|
||||||
public static final ChannelOption<Boolean> SO_BROADCAST =
|
public static final ChannelOption<Boolean> SO_BROADCAST =
|
||||||
new ChannelOption<Boolean>("SO_BROADCAST");
|
new ChannelOption<Boolean>("SO_BROADCAST");
|
||||||
|
@ -31,6 +31,7 @@ public class DefaultSocketChannelConfig extends DefaultChannelConfig
|
|||||||
implements SocketChannelConfig {
|
implements SocketChannelConfig {
|
||||||
|
|
||||||
private final Socket socket;
|
private final Socket socket;
|
||||||
|
private volatile boolean allowHalfClosure;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance.
|
* Creates a new instance.
|
||||||
@ -46,7 +47,8 @@ public class DefaultSocketChannelConfig extends DefaultChannelConfig
|
|||||||
public Map<ChannelOption<?>, Object> getOptions() {
|
public Map<ChannelOption<?>, Object> getOptions() {
|
||||||
return getOptions(
|
return getOptions(
|
||||||
super.getOptions(),
|
super.getOptions(),
|
||||||
SO_RCVBUF, SO_SNDBUF, TCP_NODELAY, SO_KEEPALIVE, SO_REUSEADDR, SO_LINGER, IP_TOS);
|
SO_RCVBUF, SO_SNDBUF, TCP_NODELAY, SO_KEEPALIVE, SO_REUSEADDR, SO_LINGER, IP_TOS,
|
||||||
|
ALLOW_HALF_CLOSURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -72,6 +74,9 @@ public class DefaultSocketChannelConfig extends DefaultChannelConfig
|
|||||||
if (option == IP_TOS) {
|
if (option == IP_TOS) {
|
||||||
return (T) Integer.valueOf(getTrafficClass());
|
return (T) Integer.valueOf(getTrafficClass());
|
||||||
}
|
}
|
||||||
|
if (option == ALLOW_HALF_CLOSURE) {
|
||||||
|
return (T) Boolean.valueOf(isAllowHalfClosure());
|
||||||
|
}
|
||||||
|
|
||||||
return super.getOption(option);
|
return super.getOption(option);
|
||||||
}
|
}
|
||||||
@ -94,6 +99,8 @@ public class DefaultSocketChannelConfig extends DefaultChannelConfig
|
|||||||
setSoLinger((Integer) value);
|
setSoLinger((Integer) value);
|
||||||
} else if (option == IP_TOS) {
|
} else if (option == IP_TOS) {
|
||||||
setTrafficClass((Integer) value);
|
setTrafficClass((Integer) value);
|
||||||
|
} else if (option == ALLOW_HALF_CLOSURE) {
|
||||||
|
setAllowHalfClosure((Boolean) value);
|
||||||
} else {
|
} else {
|
||||||
return super.setOption(option, value);
|
return super.setOption(option, value);
|
||||||
}
|
}
|
||||||
@ -236,4 +243,14 @@ public class DefaultSocketChannelConfig extends DefaultChannelConfig
|
|||||||
throw new ChannelException(e);
|
throw new ChannelException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAllowHalfClosure() {
|
||||||
|
return allowHalfClosure;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAllowHalfClosure(boolean allowHalfClosure) {
|
||||||
|
this.allowHalfClosure = allowHalfClosure;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,13 @@ public interface SocketChannel extends Channel {
|
|||||||
@Override
|
@Override
|
||||||
InetSocketAddress remoteAddress();
|
InetSocketAddress remoteAddress();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if and only if the remote peer shut down its output so that no more
|
||||||
|
* data is received from this channel. Note that the semantic of this method is different from
|
||||||
|
* that of {@link Socket#shutdownInput()} and {@link Socket#isInputShutdown()}.
|
||||||
|
*/
|
||||||
|
boolean isInputShutdown();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see Socket#isOutputShutdown()
|
* @see Socket#isOutputShutdown()
|
||||||
*/
|
*/
|
||||||
|
@ -125,4 +125,18 @@ public interface SocketChannelConfig extends ChannelConfig {
|
|||||||
* {@link Socket#setPerformancePreferences(int, int, int)}.
|
* {@link Socket#setPerformancePreferences(int, int, int)}.
|
||||||
*/
|
*/
|
||||||
void setPerformancePreferences(int connectionTime, int latency, int bandwidth);
|
void setPerformancePreferences(int connectionTime, int latency, int bandwidth);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if and only if the channel should not close itself when its remote
|
||||||
|
* peer shuts down output to make the connection half-closed. If {@code false}, the connection
|
||||||
|
* is closed automatically when the remote peer shuts down output.
|
||||||
|
*/
|
||||||
|
boolean isAllowHalfClosure();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether the channel should not close itself when its remote peer shuts down output to
|
||||||
|
* make the connection half-closed. If {@code true} the connection is not closed when the
|
||||||
|
* remote peer shuts down output. If {@code false}, the connection is closed automatically.
|
||||||
|
*/
|
||||||
|
void setAllowHalfClosure(boolean allowHalfClosure);
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import io.netty.buffer.ChannelBufType;
|
|||||||
import io.netty.channel.ChannelException;
|
import io.netty.channel.ChannelException;
|
||||||
import io.netty.channel.ChannelFlushFutureNotifier;
|
import io.netty.channel.ChannelFlushFutureNotifier;
|
||||||
import io.netty.channel.ChannelFuture;
|
import io.netty.channel.ChannelFuture;
|
||||||
|
import io.netty.channel.ChannelInputShutdownEvent;
|
||||||
import io.netty.channel.ChannelMetadata;
|
import io.netty.channel.ChannelMetadata;
|
||||||
import io.netty.channel.ChannelPipeline;
|
import io.netty.channel.ChannelPipeline;
|
||||||
import io.netty.channel.EventLoop;
|
import io.netty.channel.EventLoop;
|
||||||
@ -57,6 +58,7 @@ public class AioSocketChannel extends AbstractAioChannel implements SocketChanne
|
|||||||
}
|
}
|
||||||
|
|
||||||
private final AioSocketChannelConfig config;
|
private final AioSocketChannelConfig config;
|
||||||
|
private volatile boolean inputShutdown;
|
||||||
private volatile boolean outputShutdown;
|
private volatile boolean outputShutdown;
|
||||||
|
|
||||||
private boolean flushing;
|
private boolean flushing;
|
||||||
@ -96,6 +98,11 @@ public class AioSocketChannel extends AbstractAioChannel implements SocketChanne
|
|||||||
return METADATA;
|
return METADATA;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInputShutdown() {
|
||||||
|
return inputShutdown;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isOutputShutdown() {
|
public boolean isOutputShutdown() {
|
||||||
return outputShutdown;
|
return outputShutdown;
|
||||||
@ -209,6 +216,7 @@ public class AioSocketChannel extends AbstractAioChannel implements SocketChanne
|
|||||||
@Override
|
@Override
|
||||||
protected void doClose() throws Exception {
|
protected void doClose() throws Exception {
|
||||||
javaChannel().close();
|
javaChannel().close();
|
||||||
|
outputShutdown = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -245,7 +253,7 @@ public class AioSocketChannel extends AbstractAioChannel implements SocketChanne
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void beginRead() {
|
private void beginRead() {
|
||||||
if (readSuspended.get()) {
|
if (readSuspended.get() || inputShutdown) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -381,8 +389,16 @@ public class AioSocketChannel extends AbstractAioChannel implements SocketChanne
|
|||||||
if (read) {
|
if (read) {
|
||||||
pipeline.fireInboundBufferUpdated();
|
pipeline.fireInboundBufferUpdated();
|
||||||
}
|
}
|
||||||
if (closed && channel.isOpen()) {
|
|
||||||
|
if (closed) {
|
||||||
|
channel.inputShutdown = true;
|
||||||
|
if (channel.isOpen()) {
|
||||||
|
if (channel.config().isAllowHalfClosure()) {
|
||||||
|
pipeline.fireUserEventTriggered(ChannelInputShutdownEvent.INSTANCE);
|
||||||
|
} else {
|
||||||
channel.unsafe().close(channel.unsafe().voidFuture());
|
channel.unsafe().close(channel.unsafe().voidFuture());
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// start the next read
|
// start the next read
|
||||||
channel.beginRead();
|
channel.beginRead();
|
||||||
@ -446,6 +462,10 @@ public class AioSocketChannel extends AbstractAioChannel implements SocketChanne
|
|||||||
@Override
|
@Override
|
||||||
public void resumeRead() {
|
public void resumeRead() {
|
||||||
if (readSuspended.compareAndSet(true, false)) {
|
if (readSuspended.compareAndSet(true, false)) {
|
||||||
|
if (inputShutdown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (eventLoop().inEventLoop()) {
|
if (eventLoop().inEventLoop()) {
|
||||||
beginRead();
|
beginRead();
|
||||||
} else {
|
} else {
|
||||||
|
@ -34,6 +34,7 @@ final class AioSocketChannelConfig extends DefaultChannelConfig
|
|||||||
implements SocketChannelConfig {
|
implements SocketChannelConfig {
|
||||||
|
|
||||||
private final NetworkChannel channel;
|
private final NetworkChannel channel;
|
||||||
|
private volatile boolean allowHalfClosure;
|
||||||
private volatile long readTimeoutInMillis;
|
private volatile long readTimeoutInMillis;
|
||||||
private volatile long writeTimeoutInMillis;
|
private volatile long writeTimeoutInMillis;
|
||||||
|
|
||||||
@ -53,7 +54,7 @@ final class AioSocketChannelConfig extends DefaultChannelConfig
|
|||||||
return getOptions(
|
return getOptions(
|
||||||
super.getOptions(),
|
super.getOptions(),
|
||||||
SO_RCVBUF, SO_SNDBUF, TCP_NODELAY, SO_KEEPALIVE, SO_REUSEADDR, SO_LINGER, IP_TOS,
|
SO_RCVBUF, SO_SNDBUF, TCP_NODELAY, SO_KEEPALIVE, SO_REUSEADDR, SO_LINGER, IP_TOS,
|
||||||
AIO_READ_TIMEOUT, AIO_WRITE_TIMEOUT);
|
AIO_READ_TIMEOUT, AIO_WRITE_TIMEOUT, ALLOW_HALF_CLOSURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -86,6 +87,9 @@ final class AioSocketChannelConfig extends DefaultChannelConfig
|
|||||||
if (option == AIO_WRITE_TIMEOUT) {
|
if (option == AIO_WRITE_TIMEOUT) {
|
||||||
return (T) Long.valueOf(getWriteTimeout());
|
return (T) Long.valueOf(getWriteTimeout());
|
||||||
}
|
}
|
||||||
|
if (option == ALLOW_HALF_CLOSURE) {
|
||||||
|
return (T) Boolean.valueOf(isAllowHalfClosure());
|
||||||
|
}
|
||||||
|
|
||||||
return super.getOption(option);
|
return super.getOption(option);
|
||||||
}
|
}
|
||||||
@ -112,6 +116,8 @@ final class AioSocketChannelConfig extends DefaultChannelConfig
|
|||||||
setReadTimeout((Long) value);
|
setReadTimeout((Long) value);
|
||||||
} else if (option == AIO_WRITE_TIMEOUT) {
|
} else if (option == AIO_WRITE_TIMEOUT) {
|
||||||
setWriteTimeout((Long) value);
|
setWriteTimeout((Long) value);
|
||||||
|
} else if (option == ALLOW_HALF_CLOSURE) {
|
||||||
|
setAllowHalfClosure((Boolean) value);
|
||||||
} else {
|
} else {
|
||||||
return super.setOption(option, value);
|
return super.setOption(option, value);
|
||||||
}
|
}
|
||||||
@ -296,4 +302,14 @@ final class AioSocketChannelConfig extends DefaultChannelConfig
|
|||||||
public long getWriteTimeout() {
|
public long getWriteTimeout() {
|
||||||
return writeTimeoutInMillis;
|
return writeTimeoutInMillis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAllowHalfClosure() {
|
||||||
|
return allowHalfClosure;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAllowHalfClosure(boolean allowHalfClosure) {
|
||||||
|
this.allowHalfClosure = allowHalfClosure;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,8 @@ package io.netty.channel.socket.nio;
|
|||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.channel.Channel;
|
import io.netty.channel.Channel;
|
||||||
|
import io.netty.channel.ChannelInputShutdownEvent;
|
||||||
|
import io.netty.channel.ChannelOption;
|
||||||
import io.netty.channel.ChannelPipeline;
|
import io.netty.channel.ChannelPipeline;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -88,12 +90,20 @@ abstract class AbstractNioByteChannel extends AbstractNioChannel {
|
|||||||
if (read) {
|
if (read) {
|
||||||
pipeline.fireInboundBufferUpdated();
|
pipeline.fireInboundBufferUpdated();
|
||||||
}
|
}
|
||||||
if (closed && isOpen()) {
|
if (closed) {
|
||||||
|
setInputShutdown();
|
||||||
|
if (isOpen()) {
|
||||||
|
if (Boolean.TRUE.equals(config().getOption(ChannelOption.ALLOW_HALF_CLOSURE))) {
|
||||||
|
suspendReadTask.run();
|
||||||
|
pipeline.fireUserEventTriggered(ChannelInputShutdownEvent.INSTANCE);
|
||||||
|
} else {
|
||||||
close(voidFuture());
|
close(voidFuture());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doFlushByteBuffer(ByteBuf buf) throws Exception {
|
protected void doFlushByteBuffer(ByteBuf buf) throws Exception {
|
||||||
|
@ -40,6 +40,7 @@ public abstract class AbstractNioChannel extends AbstractChannel {
|
|||||||
private final SelectableChannel ch;
|
private final SelectableChannel ch;
|
||||||
private final int readInterestOp;
|
private final int readInterestOp;
|
||||||
private volatile SelectionKey selectionKey;
|
private volatile SelectionKey selectionKey;
|
||||||
|
private volatile boolean inputShutdown;
|
||||||
|
|
||||||
final Runnable suspendReadTask = new Runnable() {
|
final Runnable suspendReadTask = new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
@ -54,10 +55,8 @@ public abstract class AbstractNioChannel extends AbstractChannel {
|
|||||||
public void run() {
|
public void run() {
|
||||||
selectionKey().interestOps(selectionKey().interestOps() | readInterestOp);
|
selectionKey().interestOps(selectionKey().interestOps() | readInterestOp);
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The future of the current connection attempt. If not null, subsequent
|
* The future of the current connection attempt. If not null, subsequent
|
||||||
* connection attempts will fail.
|
* connection attempts will fail.
|
||||||
@ -117,6 +116,14 @@ public abstract class AbstractNioChannel extends AbstractChannel {
|
|||||||
return selectionKey;
|
return selectionKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean isInputShutdown() {
|
||||||
|
return inputShutdown;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setInputShutdown() {
|
||||||
|
inputShutdown = true;
|
||||||
|
}
|
||||||
|
|
||||||
public interface NioUnsafe extends Unsafe {
|
public interface NioUnsafe extends Unsafe {
|
||||||
java.nio.channels.Channel ch();
|
java.nio.channels.Channel ch();
|
||||||
void finishConnect();
|
void finishConnect();
|
||||||
@ -218,6 +225,10 @@ public abstract class AbstractNioChannel extends AbstractChannel {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void resumeRead() {
|
public void resumeRead() {
|
||||||
|
if (inputShutdown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
EventLoop loop = eventLoop();
|
EventLoop loop = eventLoop();
|
||||||
if (loop.inEventLoop()) {
|
if (loop.inEventLoop()) {
|
||||||
resumeReadTask.run();
|
resumeReadTask.run();
|
||||||
@ -242,7 +253,7 @@ public abstract class AbstractNioChannel extends AbstractChannel {
|
|||||||
protected Runnable doRegister() throws Exception {
|
protected Runnable doRegister() throws Exception {
|
||||||
NioEventLoop loop = (NioEventLoop) eventLoop();
|
NioEventLoop loop = (NioEventLoop) eventLoop();
|
||||||
selectionKey = javaChannel().register(
|
selectionKey = javaChannel().register(
|
||||||
loop.selector, isActive()? readInterestOp : 0, this);
|
loop.selector, isActive() && !inputShutdown ? readInterestOp : 0, this);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,9 +98,14 @@ public class NioSocketChannel extends AbstractNioByteChannel implements io.netty
|
|||||||
return ch.isOpen() && ch.isConnected();
|
return ch.isOpen() && ch.isConnected();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInputShutdown() {
|
||||||
|
return super.isInputShutdown();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isOutputShutdown() {
|
public boolean isOutputShutdown() {
|
||||||
return javaChannel().socket().isOutputShutdown();
|
return javaChannel().socket().isOutputShutdown() || !isActive();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -17,16 +17,24 @@ package io.netty.channel.socket.oio;
|
|||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.channel.Channel;
|
import io.netty.channel.Channel;
|
||||||
|
import io.netty.channel.ChannelInputShutdownEvent;
|
||||||
|
import io.netty.channel.ChannelOption;
|
||||||
import io.netty.channel.ChannelPipeline;
|
import io.netty.channel.ChannelPipeline;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
abstract class AbstractOioByteChannel extends AbstractOioChannel {
|
abstract class AbstractOioByteChannel extends AbstractOioChannel {
|
||||||
|
|
||||||
|
private volatile boolean inputShutdown;
|
||||||
|
|
||||||
protected AbstractOioByteChannel(Channel parent, Integer id) {
|
protected AbstractOioByteChannel(Channel parent, Integer id) {
|
||||||
super(parent, id);
|
super(parent, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean isInputShutdown() {
|
||||||
|
return inputShutdown;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected OioByteUnsafe newUnsafe() {
|
protected OioByteUnsafe newUnsafe() {
|
||||||
return new OioByteUnsafe();
|
return new OioByteUnsafe();
|
||||||
@ -37,6 +45,15 @@ abstract class AbstractOioByteChannel extends AbstractOioChannel {
|
|||||||
public void read() {
|
public void read() {
|
||||||
assert eventLoop().inEventLoop();
|
assert eventLoop().inEventLoop();
|
||||||
|
|
||||||
|
if (inputShutdown) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(SO_TIMEOUT);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final ChannelPipeline pipeline = pipeline();
|
final ChannelPipeline pipeline = pipeline();
|
||||||
final ByteBuf byteBuf = pipeline.inboundByteBuffer();
|
final ByteBuf byteBuf = pipeline.inboundByteBuffer();
|
||||||
boolean closed = false;
|
boolean closed = false;
|
||||||
@ -93,12 +110,19 @@ abstract class AbstractOioByteChannel extends AbstractOioChannel {
|
|||||||
if (read) {
|
if (read) {
|
||||||
pipeline.fireInboundBufferUpdated();
|
pipeline.fireInboundBufferUpdated();
|
||||||
}
|
}
|
||||||
if (closed && isOpen()) {
|
if (closed) {
|
||||||
|
inputShutdown = true;
|
||||||
|
if (isOpen()) {
|
||||||
|
if (Boolean.TRUE.equals(config().getOption(ChannelOption.ALLOW_HALF_CLOSURE))) {
|
||||||
|
pipeline.fireUserEventTriggered(ChannelInputShutdownEvent.INSTANCE);
|
||||||
|
} else {
|
||||||
close(voidFuture());
|
close(voidFuture());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doFlushByteBuffer(ByteBuf buf) throws Exception {
|
protected void doFlushByteBuffer(ByteBuf buf) throws Exception {
|
||||||
|
@ -103,9 +103,14 @@ public class OioSocketChannel extends AbstractOioByteChannel
|
|||||||
return !socket.isClosed() && socket.isConnected();
|
return !socket.isClosed() && socket.isConnected();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInputShutdown() {
|
||||||
|
return super.isInputShutdown();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isOutputShutdown() {
|
public boolean isOutputShutdown() {
|
||||||
return socket.isOutputShutdown();
|
return socket.isOutputShutdown() || !isActive();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
Loading…
Reference in New Issue
Block a user