dev@grizzly.java.net

Connection management cache's problems

From: Bongjae Chang <carryel_at_korea.com>
Date: Tue, 28 Apr 2009 11:31:19 +0900

Hi.

I tried to test com.sun.grizzly.connectioncache.client.CacheableConnectorHandlerPool with com.sun.grizzly.DefaultCallbackHandler, but I had some problems.

Here is simple test code.

-----
[init part]
Controller controller = new Controller();
ConnectorHandlerPool cacheableHandlerPool = new CacheableConnectorHandlerPool( controller, 100, 10, 1 );
controller.setConnectorHandlerPool( cacheableHandlerPool );

TCPSelectorHandler tcpSelectorHandler = new TCPSelectorHandler();
tcpSelectorHandler.setPort( myPort );
tcpSelectorHandler.setSelectionKeyHandler( new DefaultSelectionKeyHandler() );
controller.addSelectorHandler( tcpSelectorHandler );
...

[send part]
ConnectorHandler connectorHandler;
connectorHandler = controller.acquireConnectorHandler( Controller.Protocol.TCP );
..
connectorHandler.connect( remoteAddress, localAddress, new DefaultCallbackHandler( connectorHandler ) );
OutputWrite.flushChannel( connectorHandler.getUnderlyingChannel(), appMessage, timeout );
...
-----


Here is result.
-----
java.lang.IllegalStateException: Invalid Response State. SocketChannel cannot be null.
 at com.sun.grizzly.util.OutputWriter.flushChannel(OutputWriter.java:94)
 at SimpleTestServer.sendTest(SimpleTestServer.java:134)
 at SimpleTestServer.main(SimpleTestServer.java:44)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:623)
 at com.intellij.rt.execution.application.AppMain.main(AppMain.java:90)
java.lang.NullPointerException
 at com.sun.grizzly.connectioncache.client.CacheableConnectorHandler.notifyCallbackHandlerPseudoConnect(CacheableConnectorHandler.java:222)
 at com.sun.grizzly.connectioncache.client.CacheableConnectorHandler.doConnect(CacheableConnectorHandler.java:164)
 at com.sun.grizzly.connectioncache.client.CacheableConnectorHandler.connect(CacheableConnectorHandler.java:117)
 at SimpleTestServer.sendTest(SimpleTestServer.java:132)
 at SimpleTestServer.main(SimpleTestServer.java:45)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:623)
 at com.intellij.rt.execution.application.AppMain.main(AppMain.java:90)
-----


Here is the NPE's line.

In CacheableConnectorHandler.java
-----
private voide notifyCallbackHandlerPseudoConnect() throws ClosedChannelException {
    ...
    SelectionKey key = underlyingConnectorHandler.getUnderlyingChannel().keyFor( protocolSelector );
   ...
}

I think that ConnectorHandler's socketChannel can be null if unexpected exceptions are occurred in CallbackHandler. ex) if an error was occurred before calling ConnectorHandler#finishConnect(SelectionKey key)

So I think that graceful handling of NPE and more check logic are needed about underlyingConnectorHandler.getUnderlyingChannel().

And I reviewed why this error had been occurred for a while.

When I inserted some test code for debugging, the following error was occurred.
-----
java.lang.ClassCastException: com.sun.grizzly.connectioncache.client.CacheableConnectorHandler cannot be cast to com.sun.grizzly.TCPConnectorHandler
 at com.sun.grizzly.DefaultCallbackHandler.onConnect(DefaultCallbackHandler.java:128)
 at com.sun.grizzly.connectioncache.client.CacheableConnectorHandler.onConnect(CacheableConnectorHandler.java:250)
 at com.sun.grizzly.CallbackHandlerContextTask.doCall(CallbackHandlerContextTask.java:68)
 at com.sun.grizzly.SelectionKeyContextTask.call(SelectionKeyContextTask.java:57)
 at com.sun.grizzly.ContextTask.run(ContextTask.java:69)
 at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
 at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
 at java.lang.Thread.run(Thread.java:717)
-----

Here is DefaultCallbackHandler#onConnect(IOEvent<Context> ioEvent);

In DefaultCallbackHandler.java
-----
public void onConnect( IOEvent<Context> ioEvent ) {
   SelectionKey key = ioEvent.attachment().getSelectionKey();
   if( ioEvent.attachment().getProtocol() == Controller.Protocol.TCP ) {
      ((TCPConnectorHandler)connectorHandler).setUnderlyingChannel((SocketChannel)key.channel());
   } else if( ioEvent.attachment().getProtocol() == Controller.Protocol.TLS) {
      ((SSLConnectorHandler)connectorHandler).setUnderlyingChannel((SocketChannel)key.channel());
   }
   try {
      connectorHandler.finishConnect(key);
      ...
   }
   ...

}
-----

I think that above calling setUnderlyingChannel() is redundant because both TCPConnectorHandler#finishConnect() and SSLConnectorHandler#finishConnect() already assign socketChannel key's channel.

Of course, it maybe has no problem if I don't use DefaultCallbackHandler.

But I hope that above reduntant code could be removed and I am curious to know if my use and point have any faults.

Please give me advice.

Thanks.

--
Bongjae Chang