[#107] Add support for closing either input or output part of a channel
- Add shutdownOutput() and isOutputShutdown() to SocketChannel
This commit is contained in:
parent
47f26d219d
commit
02f3df55a8
@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* 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 io.netty.bootstrap.Bootstrap;
|
||||||
|
import io.netty.logging.InternalLogger;
|
||||||
|
import io.netty.logging.InternalLoggerFactory;
|
||||||
|
import io.netty.testsuite.transport.socket.SocketTestPermutation.Factory;
|
||||||
|
import io.netty.testsuite.util.TestUtils;
|
||||||
|
import io.netty.util.NetworkConstants;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.rules.TestName;
|
||||||
|
|
||||||
|
public abstract class AbstractClientSocketTest {
|
||||||
|
|
||||||
|
private static final List<Factory<Bootstrap>> COMBO = SocketTestPermutation.clientSocket();
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final TestName testName = new TestName();
|
||||||
|
|
||||||
|
protected final InternalLogger logger = InternalLoggerFactory.getInstance(getClass());
|
||||||
|
|
||||||
|
protected volatile Bootstrap cb;
|
||||||
|
protected volatile InetSocketAddress addr;
|
||||||
|
|
||||||
|
protected void run() throws Throwable {
|
||||||
|
int i = 0;
|
||||||
|
for (Factory<Bootstrap> e: COMBO) {
|
||||||
|
cb = e.newInstance();
|
||||||
|
addr = new InetSocketAddress(
|
||||||
|
NetworkConstants.LOCALHOST, TestUtils.getFreePort());
|
||||||
|
cb.remoteAddress(addr);
|
||||||
|
|
||||||
|
logger.info(String.format(
|
||||||
|
"Running: %s %d of %d", testName.getMethodName(), ++ i, COMBO.size()));
|
||||||
|
try {
|
||||||
|
Method m = getClass().getDeclaredMethod(
|
||||||
|
testName.getMethodName(), Bootstrap.class);
|
||||||
|
m.invoke(this, cb);
|
||||||
|
} catch (InvocationTargetException ex) {
|
||||||
|
throw ex.getCause();
|
||||||
|
} finally {
|
||||||
|
cb.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* 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.Bootstrap;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
import io.netty.channel.socket.SocketChannel;
|
||||||
|
import io.netty.handler.logging.ByteLoggingHandler;
|
||||||
|
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class SocketShutdownOutputTest extends AbstractClientSocketTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testShutdownOutput() throws Throwable {
|
||||||
|
run();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testShutdownOutput(Bootstrap cb) throws Throwable {
|
||||||
|
ServerSocket ss = new ServerSocket();
|
||||||
|
Socket s = null;
|
||||||
|
try {
|
||||||
|
ss.bind(addr);
|
||||||
|
SocketChannel ch = (SocketChannel) cb.handler(new ByteLoggingHandler()).connect().sync().channel();
|
||||||
|
assertTrue(ch.isActive());
|
||||||
|
assertFalse(ch.isOutputShutdown());
|
||||||
|
|
||||||
|
s = ss.accept();
|
||||||
|
ch.write(Unpooled.wrappedBuffer(new byte[] { 1 })).sync();
|
||||||
|
assertEquals(1, s.getInputStream().read());
|
||||||
|
|
||||||
|
ch.shutdownOutput();
|
||||||
|
|
||||||
|
assertEquals(-1, s.getInputStream().read());
|
||||||
|
assertTrue(s.isConnected());
|
||||||
|
} finally {
|
||||||
|
if (s != null) {
|
||||||
|
s.close();
|
||||||
|
}
|
||||||
|
ss.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -44,27 +44,7 @@ final class SocketTestPermutation {
|
|||||||
List<Factory<ServerBootstrap>> sbfs = serverSocket();
|
List<Factory<ServerBootstrap>> sbfs = serverSocket();
|
||||||
|
|
||||||
// Make the list of Bootstrap factories.
|
// Make the list of Bootstrap factories.
|
||||||
List<Factory<Bootstrap>> cbfs =
|
List<Factory<Bootstrap>> cbfs = clientSocket();
|
||||||
new ArrayList<Factory<Bootstrap>>();
|
|
||||||
cbfs.add(new Factory<Bootstrap>() {
|
|
||||||
@Override
|
|
||||||
public Bootstrap newInstance() {
|
|
||||||
return new Bootstrap().group(new NioEventLoopGroup()).channel(new NioSocketChannel());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
cbfs.add(new Factory<Bootstrap>() {
|
|
||||||
@Override
|
|
||||||
public Bootstrap newInstance() {
|
|
||||||
AioEventLoopGroup loop = new AioEventLoopGroup();
|
|
||||||
return new Bootstrap().group(loop).channel(new AioSocketChannel(loop));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
cbfs.add(new Factory<Bootstrap>() {
|
|
||||||
@Override
|
|
||||||
public Bootstrap newInstance() {
|
|
||||||
return new Bootstrap().group(new OioEventLoopGroup()).channel(new OioSocketChannel());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Populate the combinations
|
// Populate the combinations
|
||||||
for (Factory<ServerBootstrap> sbf: sbfs) {
|
for (Factory<ServerBootstrap> sbf: sbfs) {
|
||||||
@ -178,6 +158,30 @@ final class SocketTestPermutation {
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static List<Factory<Bootstrap>> clientSocket() {
|
||||||
|
List<Factory<Bootstrap>> list = new ArrayList<Factory<Bootstrap>>();
|
||||||
|
list.add(new Factory<Bootstrap>() {
|
||||||
|
@Override
|
||||||
|
public Bootstrap newInstance() {
|
||||||
|
return new Bootstrap().group(new NioEventLoopGroup()).channel(new NioSocketChannel());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
list.add(new Factory<Bootstrap>() {
|
||||||
|
@Override
|
||||||
|
public Bootstrap newInstance() {
|
||||||
|
AioEventLoopGroup loop = new AioEventLoopGroup();
|
||||||
|
return new Bootstrap().group(loop).channel(new AioSocketChannel(loop));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
list.add(new Factory<Bootstrap>() {
|
||||||
|
@Override
|
||||||
|
public Bootstrap newInstance() {
|
||||||
|
return new Bootstrap().group(new OioEventLoopGroup()).channel(new OioSocketChannel());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
private SocketTestPermutation() {}
|
private SocketTestPermutation() {}
|
||||||
|
|
||||||
interface Factory<T> {
|
interface Factory<T> {
|
||||||
|
@ -16,8 +16,10 @@
|
|||||||
package io.netty.channel.socket;
|
package io.netty.channel.socket;
|
||||||
|
|
||||||
import io.netty.channel.Channel;
|
import io.netty.channel.Channel;
|
||||||
|
import io.netty.channel.ChannelFuture;
|
||||||
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A TCP/IP socket {@link Channel} which was either accepted by
|
* A TCP/IP socket {@link Channel} which was either accepted by
|
||||||
@ -32,4 +34,14 @@ public interface SocketChannel extends Channel {
|
|||||||
InetSocketAddress localAddress();
|
InetSocketAddress localAddress();
|
||||||
@Override
|
@Override
|
||||||
InetSocketAddress remoteAddress();
|
InetSocketAddress remoteAddress();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see Socket#isOutputShutdown()
|
||||||
|
*/
|
||||||
|
boolean isOutputShutdown();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see Socket#shutdownOutput()
|
||||||
|
*/
|
||||||
|
ChannelFuture shutdownOutput();
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ import io.netty.channel.ChannelFlushFutureNotifier;
|
|||||||
import io.netty.channel.ChannelFuture;
|
import io.netty.channel.ChannelFuture;
|
||||||
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.socket.SocketChannel;
|
import io.netty.channel.socket.SocketChannel;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -56,8 +57,9 @@ public class AioSocketChannel extends AbstractAioChannel implements SocketChanne
|
|||||||
}
|
}
|
||||||
|
|
||||||
private final AioSocketChannelConfig config;
|
private final AioSocketChannelConfig config;
|
||||||
private boolean flushing;
|
private volatile boolean outputShutdown;
|
||||||
|
|
||||||
|
private boolean flushing;
|
||||||
private final AtomicBoolean readSuspended = new AtomicBoolean();
|
private final AtomicBoolean readSuspended = new AtomicBoolean();
|
||||||
private final AtomicBoolean readInProgress = new AtomicBoolean();
|
private final AtomicBoolean readInProgress = new AtomicBoolean();
|
||||||
|
|
||||||
@ -94,6 +96,34 @@ public class AioSocketChannel extends AbstractAioChannel implements SocketChanne
|
|||||||
return METADATA;
|
return METADATA;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOutputShutdown() {
|
||||||
|
return outputShutdown;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture shutdownOutput() {
|
||||||
|
ChannelFuture future = newFuture();
|
||||||
|
EventLoop loop = eventLoop();
|
||||||
|
if (loop.inEventLoop()) {
|
||||||
|
try {
|
||||||
|
javaChannel().shutdownOutput();
|
||||||
|
outputShutdown = true;
|
||||||
|
future.setSuccess();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
future.setFailure(t);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
loop.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
shutdownOutput();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doConnect(SocketAddress remoteAddress, SocketAddress localAddress, final ChannelFuture future) {
|
protected void doConnect(SocketAddress remoteAddress, SocketAddress localAddress, final ChannelFuture future) {
|
||||||
if (localAddress != null) {
|
if (localAddress != null) {
|
||||||
|
@ -19,7 +19,9 @@ import io.netty.buffer.ByteBuf;
|
|||||||
import io.netty.buffer.ChannelBufType;
|
import io.netty.buffer.ChannelBufType;
|
||||||
import io.netty.channel.Channel;
|
import io.netty.channel.Channel;
|
||||||
import io.netty.channel.ChannelException;
|
import io.netty.channel.ChannelException;
|
||||||
|
import io.netty.channel.ChannelFuture;
|
||||||
import io.netty.channel.ChannelMetadata;
|
import io.netty.channel.ChannelMetadata;
|
||||||
|
import io.netty.channel.EventLoop;
|
||||||
import io.netty.channel.socket.DefaultSocketChannelConfig;
|
import io.netty.channel.socket.DefaultSocketChannelConfig;
|
||||||
import io.netty.channel.socket.SocketChannelConfig;
|
import io.netty.channel.socket.SocketChannelConfig;
|
||||||
import io.netty.logging.InternalLogger;
|
import io.netty.logging.InternalLogger;
|
||||||
@ -96,6 +98,33 @@ public class NioSocketChannel extends AbstractNioByteChannel implements io.netty
|
|||||||
return ch.isOpen() && ch.isConnected();
|
return ch.isOpen() && ch.isConnected();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOutputShutdown() {
|
||||||
|
return javaChannel().socket().isOutputShutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture shutdownOutput() {
|
||||||
|
ChannelFuture future = newFuture();
|
||||||
|
EventLoop loop = eventLoop();
|
||||||
|
if (loop.inEventLoop()) {
|
||||||
|
try {
|
||||||
|
javaChannel().socket().shutdownOutput();
|
||||||
|
future.setSuccess();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
future.setFailure(t);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
loop.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
shutdownOutput();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected SocketAddress localAddress0() {
|
protected SocketAddress localAddress0() {
|
||||||
return javaChannel().socket().getLocalSocketAddress();
|
return javaChannel().socket().getLocalSocketAddress();
|
||||||
|
@ -19,7 +19,9 @@ import io.netty.buffer.ByteBuf;
|
|||||||
import io.netty.buffer.ChannelBufType;
|
import io.netty.buffer.ChannelBufType;
|
||||||
import io.netty.channel.Channel;
|
import io.netty.channel.Channel;
|
||||||
import io.netty.channel.ChannelException;
|
import io.netty.channel.ChannelException;
|
||||||
|
import io.netty.channel.ChannelFuture;
|
||||||
import io.netty.channel.ChannelMetadata;
|
import io.netty.channel.ChannelMetadata;
|
||||||
|
import io.netty.channel.EventLoop;
|
||||||
import io.netty.channel.socket.DefaultSocketChannelConfig;
|
import io.netty.channel.socket.DefaultSocketChannelConfig;
|
||||||
import io.netty.channel.socket.SocketChannel;
|
import io.netty.channel.socket.SocketChannel;
|
||||||
import io.netty.channel.socket.SocketChannelConfig;
|
import io.netty.channel.socket.SocketChannelConfig;
|
||||||
@ -101,6 +103,33 @@ public class OioSocketChannel extends AbstractOioByteChannel
|
|||||||
return !socket.isClosed() && socket.isConnected();
|
return !socket.isClosed() && socket.isConnected();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOutputShutdown() {
|
||||||
|
return socket.isOutputShutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture shutdownOutput() {
|
||||||
|
ChannelFuture future = newFuture();
|
||||||
|
EventLoop loop = eventLoop();
|
||||||
|
if (loop.inEventLoop()) {
|
||||||
|
try {
|
||||||
|
socket.shutdownOutput();
|
||||||
|
future.setSuccess();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
future.setFailure(t);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
loop.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
shutdownOutput();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected SocketAddress localAddress0() {
|
protected SocketAddress localAddress0() {
|
||||||
return socket.getLocalSocketAddress();
|
return socket.getLocalSocketAddress();
|
||||||
|
Loading…
Reference in New Issue
Block a user