Hi !

I tried to inject the benchmark we have in MINA 2 into MINA3, using the
NioTcpClient Julien just added. It was easy to migrate the code, but
now, I have some issues...

Basically I get a NPE when trying to manipulate the session
selectionKey, which may become null in some case. This is typically a
race condition, where this SelectionKey is deregistered from one
selector (the Connect one) and registered on the IoLoop selector.

Let me explain what's going on...

I have a client that tries to write some data when teh session get opened :

            public void sessionOpened(IoSession session) {
                sendMessage(session, data);
            }

There is nothing wrong here : it's perfectly elicit to write some data
as soon as we are informed that the session is now connected. The
problem is that, in the middle, we have *moved* the channel from one
selector to the other, and in the middle of this process, we set the
SelectionKey to null :

In NioTcpSession :

    public void ready(final boolean accept, boolean connect, final
boolean read, final ByteBuffer readBuffer,
            final boolean write) {
        if (connect) {
                    // cancel current registration for connection
                    selectionKey.cancel();
                    selectionKey = null;  
<<<<----------------------------- Here !

                    // Register for reading
                    selectorLoop.register(false, false, true, false,
this, channel, null);

                    // and inform the IoHandler that the session is
connected
                    setConnected();

The setConnected() call will itself call the processSessionOpen(). If it
goes faster than the processing of the selectorLoop.register()
processing, then we will have a null selectionKey when we process the
NioSelectorLoop.modifyRegistration() method :

    public void modifyRegistration(final boolean accept, final boolean
read, final boolean write,
            final SelectorListener listener, final SelectableChannel
channel) {

        final SelectionKey key = channel.keyFor(selector);
        if (key == null) {
            logger.error("Trying to modify the registration of a not
registered channel");

The stack trace from the setConnected() call is :

Daemon Thread [SelectorWorker connect-0] (Suspended)   
    NioSelectorLoop.modifyRegistration(boolean, boolean, boolean,
SelectorListener, SelectableChannel) line: 142   
    NioTcpSession.flushWriteQueue() line: 225   
    NioTcpSession(AbstractIoSession).enqueueWriteRequest(Object) line:
553   
    NioTcpSession(AbstractIoSession).enqueueFinalWriteMessage(Object)
line: 868   
    NioTcpSession(AbstractIoSession).processMessageWriting(Object,
IoFuture<Void>) line: 793   
    NioTcpSession(AbstractIoSession).doWriteWithFuture(Object,
IoFuture<Void>) line: 523   
    NioTcpSession(AbstractIoSession).write(Object) line: 501   
    Mina3BenchmarkClient$1.sendMessage(IoSession, byte[]) line: 50   
    Mina3BenchmarkClient$1.sessionOpened(IoSession) line: 55   
    NioTcpSession(AbstractIoSession).processSessionOpen() line: 666   
    NioTcpSession.setConnected() line: 202   
    NioTcpSession.ready(boolean, boolean, boolean, ByteBuffer, boolean)
line: 398   
    NioSelectorLoop$SelectorWorker.run() line: 209   


Again, this is random and not too frequent, but I can assure that it
actually *happens*.

How could we avoid such a behavior ?

I think we should wait for the selection key to be set to its new value
here, which means we have to add some kind of synchronization between
the two threads (yuk :/), or transfer the setConnected() call to the
second thread (which is, IMO, the best thing to do).

What about adding a call back that does that in the ready() call ? Like in :


    public void ready(final boolean accept, boolean connect, final
boolean read, final ByteBuffer readBuffer,
            final boolean write) {
        if (connect) {
                    // cancel current registration for connection
                    selectionKey.cancel();
                    selectionKey = null;  
<<<<----------------------------- Here !

                    // Register for reading
                    selectorLoop.register(false, false, true, false,
this, channel, new RegistrationCallback() {

                        @Override
                        public void done(SelectionKey selectionKey) {
                            setConnected();
                        }
                    } );

Wdyt ?

PS : I will commit the benchmark module shortly, so that anyone can play
with the code and see what's going on, if it's not clear.

Thanks !

-- 
Regards,
Cordialement,
Emmanuel Lécharny
www.iktek.com 

Reply via email to