dev@grizzly.java.net

Re: About recycling the server channel in XXXSelectorHandler

From: Jeanfrancois Arcand <Jeanfrancois.Arcand_at_Sun.COM>
Date: Mon, 15 Jun 2009 12:22:35 -0400

Salut,
Bongjae Chang wrote:
> Hi,
>
> I wrote the proposed patch about this issue experimentally.
>
> In UDP, the patch is simple because it overrides some TCPSelectorHandler's methods.
>
> But in TCP, if only accepted socket channel existed and the socket's remote address was equal to each other(ConnectorHandler's remote address), I recycled it.
>
> I used a Map and a List for caching accepted socket channels and client socket channels which ConnectorHandler used for connecting in TCPSelectorHandler.
>
> But frankly speaking, I didn't like it completely and I didn't find any better ideas in TCP.

It looks OK except I'm afraid of a little performance regression due to
the Concurrent Map used. Also I think calling

    acceptedSocket.getRemoteSocketAddress();

for every request may introduce performance regression as well. We need
to test, but I do think it will be a hit.


>
> Once, I attached the patch and a simple unit test case.
>
> When I tested it, it worked well and all unit tests passed locally.
>
> But I think that more tests needed and it should be reviewed carefully because any regressions which I have not thought could exist.
>
> I look forward to your feedback or any ideas.

see inline

Since we never got request for such mechanism, I think we should explore
the possible solutions offered:

   * (1) Apply the current patch
   * (2) Create new SelectorHandler like RecyclableTCP|UDPSelectorHandler
   * (3) Don't enable the mechanism by default, use a property

I would think (2) might be the cleaner solution. We can possibly add a
new Factory for creating SelectorHandler like:

   * SelectorHandlerFactory.new(Protocol, boolean recycled);

which return the proper instance of SelectorHandler.

What do you think?

A+

-Jeanfrancois


>
> Thanks!
>
> --
> Bongjae Chang
>
>
> ----- Original Message -----
> From: "Bongjae Chang" <carryel_at_korea.com>
> To: <dev_at_grizzly.dev.java.net>
> Sent: Saturday, June 13, 2009 5:26 PM
> Subject: Re: About recycling the server channel in XXXSelectorHandler
>
>
>> Hi Jeanfrancois,
>>
>> Jeanfrancois wrote:
>>> I like the idea. I still need to catch up on the "Strange behavior..."
>>> but if the above solution is implemented, would it fix Minoru issue? At
>>> least you discovered an issue with the BindingException (duplicated). So
>>> I'm all for fixing that.
>> Then, I will try to implement recycling the server channel once.
>>
>> I am not sure whether this solution will fix Minoru issue or not, but I think that at least it may be able to help the similar case with workaround.
>>
>> For fixing the issue with recycling the server channel, I think that Minoru application should be modified in order to avoid the duplicated binding situation.
>>
>> i.g. Should use only one Controller.
>>
>> So I also think that it is helpful for me that you catch up on the issue because I don't know how Grizzly can prevent a user from binding duplicated address completely.
>>
>> After finishing the impl and testing it, I will reply for being reviewed.
>>
>> Thanks!
>> --
>> Bongjae Chang
>>
>>
>> ----- Original Message -----
>> From: "Jeanfrancois Arcand" <Jeanfrancois.Arcand_at_Sun.COM>
>> To: <dev_at_grizzly.dev.java.net>
>> Sent: Saturday, June 13, 2009 5:02 AM
>> Subject: Re: About recycling the server channel in XXXSelectorHandler
>>
>>
>>> Salut,
>>>
>>> Bongjae Chang wrote:
>>>> Hi,
>>>>
>>>> I have seen Minoru's issue("Strange behavior of Grizzly") for several days.
>>>>
>>>> Meanwhile, I thought that sometimes it was possible that a user would
>>>> like to use the Grizzly for both server and client.
>>>>
>>>> Of course, XXXSelectorHandler( Role.CLIENT_SERVER ) is used for both
>>>> server and client purpose.
>>>>
>>>> See the following use case.
>>>>
>>>> ---
>>>> ..
>>>> XXXSelectorHandler selectorHandler = new XXXSelectorHandler(
>>>> Role.CLIENT_SERVER );
>>>> selectorHandler.setPort( port );
>>>> selectorHandler.setInet( localAddress );
>>>> controller.addSelectorHandler( selectorHandler );
>>>> ...
>>>> ConnectorHandler connectorHandler = controller.acquireConnectorHandler(
>>>> Controller.Protocol.XXX );
>>>> connectorHandler.connect( remoteSocketAddress, new InetSocketAddress(
>>>> localAddress, port ); ---- (1)
>>>> connectorHandler.write(...);
>>>> ...
>>>> ---
>>>>
>>>> At above (1), the following method is called finally. (TCP is similar to
>>>> UDP)
>>>>
>>>> In UDPSelectorHandler.java
>>>> ---
>>>> protected void connect(SocketAddress remoteAddress, SocketAddress
>>>> localAddress, CallbackHandler callbackHandler) throws IOException {
>>>> DatagramChannel newDatagramChannel = DatagramChannel.open();
>>>> newDatagramChannel.socket().setReuseAddress(reuseAddress);
>>>> if( localAddress != null ) {
>>>> newDatagramChannel.socket().bind(localAddress);
>>>> }
>>>> ...
>>>> }
>>>> ---
>>>>
>>>> If user made XXXSelectorHandler with Role.CLIENT_SERVER, and user used
>>>> setInet( localAddress ), binding operation will be duplicated because
>>>> XXXSelectorHandler#initSelector() makes the server channel which binds
>>>> the localAddress.
>>>>
>>>> If setReuseAddress set to be false, maybe user can't use the specific
>>>> address for sending through Controller#acquireConnectorHandler() and
>>>> ConnectorHandler#connect(remote, local). But user can use only the any
>>>> address like "0.0.0.0" for sending.
>>>>
>>>> If setReuseAddress set to be true, default is true, maybe at least above
>>>> case, there is no problem.
>>>>
>>>> But if user used one more Controller locally, unexpected result could be
>>>> occurred according to platform(See the "Strange behavior of Grizzly"
>>>> issue of dev mailing).
>>>>
>>>> So I think that it is better that Grizzly can provide a client with the
>>>> way of being able to use server's channel which already has been created.
>>>>
>>>> How about recycling the server channel in XXXSelectorHandler#connect()
>>>> if it exists?
>>> I like the idea. I still need to catch up on the "Strange behavior..."
>>> but if the above solution is implemented, would it fix Minoru issue? At
>>> least you discovered an issue with the BindingException (duplicated). So
>>> I'm all for fixing that.
>>>
>>>>
>>>> Then, could any problems be occurred?
>>> I don't think. On the contrary, I think it will help.
>>>
>>> Great work!
>>>
>>> -- Jeanfrancois
>>>
>>>>
>>>> Please advice me.
>>>>
>>>> Thanks.
>>>>
>
> Index: test/java/com/sun/grizzly/RecycledChannelTest.java
> ===================================================================
> --- test/java/com/sun/grizzly/RecycledChannelTest.java (revision 0)
> +++ test/java/com/sun/grizzly/RecycledChannelTest.java (revision 0)
> @@ -0,0 +1,143 @@
> +package com.sun.grizzly;
> +
> +import junit.framework.TestCase;
> +
> +import java.net.InetAddress;
> +import java.net.InetSocketAddress;
> +import java.net.UnknownHostException;
> +import java.net.Socket;
> +import java.net.BindException;
> +import java.net.SocketAddress;
> +import java.io.IOException;
> +import java.util.logging.Level;
> +
> +import com.sun.grizzly.utils.ControllerUtils;
> +
> +/**
> + * @author Bongjae Chang
> + * @date 2009. 6. 15
> + */
> +public class RecycledChannelTest extends TestCase {
> +
> + private static final int PORT = 17520;
> + private static final int SLEEP_TIME = 3000; // ms
> +
> + private final InetAddress localInetAddress;
> + private final InetSocketAddress localInetSocketAddress;
> +
> + public RecycledChannelTest() throws UnknownHostException {
> + localInetAddress = InetAddress.getLocalHost();
> + localInetSocketAddress = new InetSocketAddress( localInetAddress, PORT );
> + }
> +
> + public void testSimpleTCPConnect() throws IOException {
> + final Controller controller = new Controller();
> + SelectorHandler selectorHandler = new TCPSelectorHandler();
> + ( (TCPSelectorHandler)selectorHandler ).setPort( PORT );
> + ( (TCPSelectorHandler)selectorHandler ).setInet( localInetAddress );
> + ( (TCPSelectorHandler)selectorHandler ).setReuseAddress( false );
> + controller.addSelectorHandler( selectorHandler );
> +
> + Socket clientSocket = null;
> + try {
> + ControllerUtils.startController( controller );
> +
> + boolean result = false;
> + Controller.logger().log( Level.INFO, "Try to get a connector handler with the local address which already has been bound." );
> + try {
> + tryToConnect( controller, Controller.Protocol.TCP, null, localInetSocketAddress );
> + } catch( IOException ie ) {
> + if( ie instanceof BindException ) {
> + result = true;
> + Controller.logger().log( Level.INFO, "Got the expected BindException." );
> + assertTrue( "Got the expected BindException.", true );
> + } else {
> + Controller.logger().log( Level.INFO, "Got the unexpected error.", ie );
> + assertTrue( "Got the unexpected error.", false );
> + }
> + }
> + if( !result )
> + assertTrue( "The BindException was expected.", false );
> +
> + Controller.logger().log( Level.INFO, "Try to connect the local server." );
> + clientSocket = new Socket( localInetAddress, PORT );
> + Controller.logger().log( Level.INFO, "Wait for " + SLEEP_TIME + "(ms)" );
> + try {
> + Thread.sleep( SLEEP_TIME );
> + } catch( InterruptedException e ) {
> + }
> +
> + Controller.logger().log( Level.INFO, "Try to get a connector handler with the local address which already has been bound again." );
> + try {
> + tryToConnect( controller, Controller.Protocol.TCP, clientSocket.getLocalSocketAddress(), localInetSocketAddress );
> + } catch( IOException ie ) {
> + Controller.logger().log( Level.INFO, "Got the unexpected error.", ie );
> + assertTrue( "Got the unexpected error.", false );
> + throw ie;
> + }
> + } finally {
> + if( clientSocket != null ) {
> + try {
> + clientSocket.shutdownInput();
> + } catch( IOException e ) {
> + }
> + try {
> + clientSocket.shutdownOutput();
> + } catch( IOException e ) {
> + }
> + try {
> + clientSocket.close();
> + } catch( IOException e ) {
> + }
> + }
> + controller.stop();
> + }
> + }
> +
> + public void testSimpleUDPConnect() throws IOException {
> + final Controller controller = new Controller();
> + SelectorHandler selectorHandler = new UDPSelectorHandler();
> + ( (UDPSelectorHandler)selectorHandler ).setPort( PORT );
> + ( (UDPSelectorHandler)selectorHandler ).setInet( localInetAddress );
> + ( (UDPSelectorHandler)selectorHandler ).setReuseAddress( false );
> + controller.addSelectorHandler( selectorHandler );
> +
> + try {
> + ControllerUtils.startController( controller );
> +
> + Controller.logger().log( Level.INFO, "Try to get a connector handler with the local address which already has been bound." );
> + try {
> + tryToConnect( controller, Controller.Protocol.UDP, localInetSocketAddress, localInetSocketAddress );
> + } catch( IOException ie ) {
> + Controller.logger().log( Level.INFO, "Got the unexpected error.", ie );
> + assertTrue( "Got the unexpected error.", false );
> + throw ie;
> + }
> + } finally {
> + controller.stop();
> + }
> + }
> +
> + private void tryToConnect( Controller controller, Controller.Protocol protocol, SocketAddress remote, SocketAddress local ) throws IOException {
> + ConnectorHandler connectorHandler = null;
> + try {
> + connectorHandler = controller.acquireConnectorHandler( protocol );
> + connectorHandler.connect( remote, local );
> + } finally {
> + if( connectorHandler != null ) {
> + try {
> + connectorHandler.close();
> + } catch( IOException e ) {
> + e.printStackTrace();
> + }
> + controller.releaseConnectorHandler( connectorHandler );
> + }
> + }
> + }
> +
> + public static void main( String[] args ) throws IOException {
> + RecycledChannelTest test = new RecycledChannelTest();
> + test.testSimpleTCPConnect();
> + test.testSimpleUDPConnect();
> + }
> +}
> Index: main/java/com/sun/grizzly/UDPSelectorHandler.java
> ===================================================================
> --- main/java/com/sun/grizzly/UDPSelectorHandler.java (revision 3298)
> +++ main/java/com/sun/grizzly/UDPSelectorHandler.java (working copy)
> @@ -38,11 +38,9 @@
>
> package com.sun.grizzly;
>
> -import com.sun.grizzly.SelectionKeyOP.ConnectSelectionKeyOP;
> import com.sun.grizzly.async.UDPAsyncQueueReader;
> import com.sun.grizzly.async.UDPAsyncQueueWriter;
> import com.sun.grizzly.util.Copyable;
> -import com.sun.grizzly.util.State;
> import java.io.IOException;
> import java.net.BindException;
> import java.net.DatagramSocket;
> @@ -138,9 +136,9 @@
> ConcurrentQueueDelegateCIH(
> getConnectorInstanceHandlerDelegate());
>
> - datagramChannel = DatagramChannel.open();
> selector = Selector.open();
> if (role != Role.CLIENT){
> + datagramChannel = DatagramChannel.open();
> datagramSocket = datagramChannel.socket();
> datagramSocket.setReuseAddress(reuseAddress);
> if (inet == null)
> @@ -159,35 +157,22 @@
> }
> }
>
> -
> - /**
> - * Register a CallBackHandler to this Selector.
> - *
> - * @param remoteAddress remote address to connect
> - * @param localAddress local address to bin
> - * @param callbackHandler {_at_link CallbackHandler}
> - * @throws java.io.IOException
> - */
> @Override
> - protected void connect(SocketAddress remoteAddress, SocketAddress localAddress,
> - CallbackHandler callbackHandler) throws IOException {

Can you explain why we need to remove that method? Is it because it is
never used? We should leave it there as some application might still use it.


> + protected SelectableChannel getUsedSelectableChannel( SocketAddress remoteAddress ) {

Do we need to make a difference between getNew and getUsed? Since this
API is not exposed directly to the user, should we just have
getSelectableChannel() (like you did below for TCPSelectorHandler()).

Remaining looks good.

A+

- jeanfrancois


> + if( role != Role.CLIENT && datagramChannel != null && datagramSocket != null )
> + return datagramChannel;
> + else
> + return null;
> + }
>
> + @Override
> + protected SelectableChannel getNewSelectableChannel( SocketAddress localAddress ) throws IOException {
> DatagramChannel newDatagramChannel = DatagramChannel.open();
> newDatagramChannel.socket().setReuseAddress(reuseAddress);
> - if (localAddress != null) {
> + if (localAddress != null)
> newDatagramChannel.socket().bind(localAddress);
> - }
> -
> newDatagramChannel.configureBlocking(false);
> -
> - SelectionKeyOP.ConnectSelectionKeyOP keyOP = new ConnectSelectionKeyOP();
> -
> - keyOP.setOp(SelectionKey.OP_CONNECT);
> - keyOP.setChannel(newDatagramChannel);
> - keyOP.setRemoteAddress(remoteAddress);
> - keyOP.setCallbackHandler(callbackHandler);
> - opToRegister.offer(keyOP);
> - selector.wakeup();
> + return newDatagramChannel;
> }
>
> /**
> @@ -196,19 +181,19 @@
> @Override
> protected void onConnectOp(Context ctx,
> SelectionKeyOP.ConnectSelectionKeyOP selectionKeyOp) throws IOException {
> - DatagramChannel newDatagramChannel = (DatagramChannel) selectionKeyOp.getChannel();
> + DatagramChannel datagramChannel = (DatagramChannel) selectionKeyOp.getChannel();
> SocketAddress remoteAddress = selectionKeyOp.getRemoteAddress();
> CallbackHandler callbackHandler = selectionKeyOp.getCallbackHandler();
>
> CallbackHandlerSelectionKeyAttachment attachment =
> new CallbackHandlerSelectionKeyAttachment(callbackHandler);
>
> - SelectionKey key = newDatagramChannel.register(selector,
> + SelectionKey key = datagramChannel.register(selector,
> SelectionKey.OP_READ | SelectionKey.OP_WRITE, attachment);
> attachment.associateKey(key);
>
> try {
> - newDatagramChannel.connect(remoteAddress);
> + datagramChannel.connect(remoteAddress);
> } catch(Exception e) {
> if (logger.isLoggable(Level.FINE)) {
> logger.log(Level.FINE, "Exception occured when tried to connect datagram channel", e);
> @@ -224,44 +209,26 @@
> */
> @Override
> public void shutdown(){
> - // If shutdown was called for this SelectorHandler
> - if (isShutDown.getAndSet(true)) return;
> -
> - stateHolder.setState(State.STOPPED);
> -
> + super.shutdown();
> try {
> - if ( datagramSocket != null )
> + if ( datagramSocket != null ) {
> datagramSocket.close();
> + datagramSocket = null;
> + }
> } catch (Throwable ex){
> Controller.logger().log(Level.SEVERE,
> "closeSocketException",ex);
> }
>
> try{
> - if ( datagramChannel != null)
> + if ( datagramChannel != null) {
> datagramChannel.close();
> + datagramChannel = null;
> + }
> } catch (Throwable ex){
> Controller.logger().log(Level.SEVERE,
> "closeSocketException",ex);
> }
> -
> - try{
> - if ( selector != null)
> - selector.close();
> - } catch (Throwable ex){
> - Controller.logger().log(Level.SEVERE,
> - "closeSocketException",ex);
> - }
> -
> - if (asyncQueueReader != null) {
> - asyncQueueReader.close();
> - asyncQueueReader = null;
> - }
> -
> - if (asyncQueueWriter != null) {
> - asyncQueueWriter.close();
> - asyncQueueWriter = null;
> - }
> }
>
>
> @@ -385,6 +352,8 @@
>
> @Override
> public void closeChannel(SelectableChannel channel) {
> + if( datagramChannel == channel )
> + return;
> try{
> channel.close();
> } catch (IOException ex){
> Index: main/java/com/sun/grizzly/TCPSelectorHandler.java
> ===================================================================
> --- main/java/com/sun/grizzly/TCPSelectorHandler.java (revision 3298)
> +++ main/java/com/sun/grizzly/TCPSelectorHandler.java (working copy)
> @@ -72,6 +72,8 @@
> import java.util.Set;
> import java.util.concurrent.Callable;
> import java.util.concurrent.ExecutorService;
> +import java.util.concurrent.ConcurrentHashMap;
> +import java.util.concurrent.CopyOnWriteArrayList;
> import java.util.concurrent.atomic.AtomicBoolean;
> import java.util.logging.Level;
> import java.util.logging.Logger;
> @@ -274,7 +276,12 @@
>
> private long lastSpinTimestamp;
> private int emptySpinCounter;
> -
> +
> + private final ConcurrentHashMap<SocketAddress, SocketChannel> acceptedSocketChannelMap =
> + new ConcurrentHashMap<SocketAddress, SocketChannel>();
> + private final CopyOnWriteArrayList<SocketChannel> recycledSocketChannels =
> + new CopyOnWriteArrayList<SocketChannel>();
> +
> public TCPSelectorHandler(){
> this(Role.CLIENT_SERVER);
> }
> @@ -397,15 +404,13 @@
>
> serverSocketChannel.configureBlocking(false);
> serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
> +
> + serverSocket.setSoTimeout(serverTimeout);
> }
> ctx.getController().notifyReady();
> } catch (SocketException ex){
> throw new BindException(ex.getMessage() + ": " + port + "=" + this);
> }
> -
> - if (role != Role.CLIENT){
> - serverSocket.setSoTimeout(serverTimeout);
> - }
> }
>
> /**
> @@ -531,7 +536,7 @@
> }else{
> ((Runnable)obj).run();
> }
> - }catch(Throwable t){
> + }catch(Throwable t){
> logger.log(Level.FINEST, "doExecutePendiongIO failed.", t);
> }
> }
> @@ -567,7 +572,7 @@
> * Register a SelectionKey to this Selector.<br>
> * Storing each interest type in different queues removes the need of wrapper (SelectionKeyOP)
> * while lowering thread contention due to the load is spread out on different queues.
> - *
> + *
> * @param key
> * @param ops
> */
> @@ -595,11 +600,11 @@
> opToRegister.offer(new SelectionKeyOP(null,ops,channel));
> wakeUp();
> }
> -
> +
> /**
> * Workaround for NIO issue 6524172
> */
> - private void wakeUp(){
> + protected void wakeUp(){
> try{
> selector.wakeup();
> } catch (NullPointerException ne){
> @@ -617,25 +622,52 @@
> */
> protected void connect(SocketAddress remoteAddress, SocketAddress localAddress,
> CallbackHandler callbackHandler) throws IOException {
> -
> - SocketChannel socketChannel = SocketChannel.open();
> - socketChannel.socket().setReuseAddress(reuseAddress);
> - if (localAddress != null) {
> - socketChannel.socket().bind(localAddress);
> - }
> -
> - socketChannel.configureBlocking(false);
> -
> + SelectableChannel selectableChannel = getSelectableChannel( remoteAddress, localAddress );
> SelectionKeyOP.ConnectSelectionKeyOP keyOP = new ConnectSelectionKeyOP();
> -
> keyOP.setOp(SelectionKey.OP_CONNECT);
> - keyOP.setChannel(socketChannel);
> + keyOP.setChannel(selectableChannel);
> keyOP.setRemoteAddress(remoteAddress);
> keyOP.setCallbackHandler(callbackHandler);
> opToRegister.offer(keyOP);
> wakeUp();
> }
>
> + private SelectableChannel getSelectableChannel( SocketAddress remoteAddress, SocketAddress localAddress ) throws IOException {
> + SelectableChannel selectableChannel = null;
> + if( localAddress != null ) {
> + if( inet != null && localAddress instanceof InetSocketAddress ) {
> + InetSocketAddress inetSocketAddress = (InetSocketAddress)localAddress;
> + if( inet.equals( inetSocketAddress.getAddress() ) )
> + selectableChannel = getUsedSelectableChannel( remoteAddress );
> + }
> + } else {
> + selectableChannel = getUsedSelectableChannel( remoteAddress );
> + }
> + if( selectableChannel == null )
> + selectableChannel = getNewSelectableChannel( localAddress );
> + return selectableChannel;
> + }
> +
> + protected SelectableChannel getUsedSelectableChannel( SocketAddress remoteAddress ) {
> + if( remoteAddress != null ) {
> + SocketChannel acceptedSocketChannel = acceptedSocketChannelMap.get( remoteAddress );
> + if( acceptedSocketChannel != null )
> + recycledSocketChannels.add( acceptedSocketChannel );
> + return acceptedSocketChannel;
> + } else {
> + return null;
> + }
> + }
> +
> + protected SelectableChannel getNewSelectableChannel( SocketAddress localAddress ) throws IOException {
> + SocketChannel newSocketChannel = SocketChannel.open();
> + newSocketChannel.socket().setReuseAddress( reuseAddress );
> + if( localAddress != null )
> + newSocketChannel.socket().bind( localAddress );
> + newSocketChannel.configureBlocking(false);
> + return newSocketChannel;
> + }
> +
> /**
> * {_at_inheritDoc}
> */
> @@ -691,16 +723,20 @@
> }
>
> try{
> - if (serverSocket != null)
> + if (serverSocket != null) {
> serverSocket.close();
> + serverSocket = null;
> + }
> } catch (Throwable ex){
> Controller.logger().log(Level.SEVERE,
> "serverSocket.close",ex);
> }
>
> try{
> - if (serverSocketChannel != null)
> + if (serverSocketChannel != null) {
> serverSocketChannel.close();
> + serverSocketChannel = null;
> + }
> } catch (Throwable ex){
> Controller.logger().log(Level.SEVERE,
> "serverSocketChannel.close",ex);
> @@ -727,6 +763,8 @@
> readOpToRegister.clear();
> writeOpToRegister.clear();
> opToRegister.clear();
> + acceptedSocketChannelMap.clear();
> + recycledSocketChannels.clear();
>
> attributes = null;
> }
> @@ -735,7 +773,16 @@
> * {_at_inheritDoc}
> */
> public SelectableChannel acceptWithoutRegistration(SelectionKey key) throws IOException {
> - return ((ServerSocketChannel) key.channel()).accept();
> + SocketChannel acceptedSocketChannel = ((ServerSocketChannel) key.channel()).accept();
> + if( acceptedSocketChannel != null ) {
> + SocketAddress remoteSocketAddress = null;
> + Socket acceptedSocket = acceptedSocketChannel.socket();
> + if( acceptedSocket != null )
> + remoteSocketAddress = acceptedSocket.getRemoteSocketAddress();
> + if( remoteSocketAddress != null )
> + acceptedSocketChannelMap.put( remoteSocketAddress, acceptedSocketChannel );
> + }
> + return acceptedSocketChannel;
> }
>
> /**
> @@ -845,7 +892,7 @@
> // Added because of incompatibility with Grizzly 1.6.0
> context.setSelectorHandler(this);
>
> - CallbackHandlerContextTask task =
> + CallbackHandlerContextTask task =
> context.getCallbackHandlerContextTask(callbackHandler);
> boolean isRunInSeparateThread = true;
>
> @@ -1224,7 +1271,15 @@
> public void closeChannel(SelectableChannel channel) {
> // channel could be either SocketChannel or ServerSocketChannel
> if (channel instanceof SocketChannel) {
> - Socket socket = ((SocketChannel) channel).socket();
> + SocketChannel socketChannel = (SocketChannel)channel;
> + if( recycledSocketChannels.remove( socketChannel ) )
> + return;
> + Socket socket = socketChannel.socket();
> + SocketAddress remoteSocketAddress = null;
> + if( socket != null )
> + remoteSocketAddress = socket.getRemoteSocketAddress();
> + if( remoteSocketAddress != null )
> + acceptedSocketChannelMap.remove( remoteSocketAddress );
>
> try {
> if (!socket.isInputShutdown()) socket.shutdownInput();
> @@ -1268,14 +1323,14 @@
> * @return {_at_link Context}
> */
> protected NIOContext pollContext(final Context serverContext,
> - final SelectionKey key, final Context.OpType opType) {
> + final SelectionKey key, final Context.OpType opType) {
> Controller c = serverContext.getController();
> ProtocolChain protocolChain = instanceHandler != null ?
> instanceHandler.poll() :
> c.getProtocolChainInstanceHandler().poll();
>
> final NIOContext context = (NIOContext)c.pollContext();
> - c.configureContext(key, opType, context, this);
> + c.configureContext(key, opType, context, this);
> context.setProtocolChain(protocolChain);
> return context;
> }
> @@ -1379,7 +1434,7 @@
> * {_at_inheritDoc}
> */
> public void resetSpinCounter(){
> - emptySpinCounter = 0;
> + emptySpinCounter = 0;
> }
>
> /**
>
>