Hi Colin,

On 4/18/06, Colin Cullen <colincullen@earthlink.net> wrote:

Hi,

I am seeking some help regarding Mina.  I have completed development of a server using Mina 0.8.2.  This server will be providing financial deal ticket information to multiple clients.  Functionally everything works great but I do seem to have a memory leak and slowly degrading performance.  I am testing using a single production level client (not Mina based).  The server protocol sends a deal ticket down to the client and then awaits a high level acknowledgement from the client indicating whether the ticket has passed or failed client side validation.  The performance degradation occurs between the writing of the message object (deal ticket) at the protocol level and the reception of the response message in its message handler.  BTW I am using Mina's DemuxingProtocolCodecFactory and DemuxingProtocolHandler to model the server and provide the codec/handler glue.  I am concerned about whether I am using Mina's ByteBuffer properly.  I have also read on the board that Mina will automatically place an allocated ByteBuffer in its ByteBuffer pool.

MINA 0.8 doesn't release pooled buffers once allocated, so the pool size will never decrease.  This problem has been addressed in 0.9.3 which has been released recently.  Except that, there's no known memory leak in 0.8.  To prevent MINA from pooling the allocated buffers, just acquire them once more, for example:

ByteBuffer buf = ByteBuffer.allocate(....);
buf.acquire();  // acquire once more.

My output buffer requirements are pretty minimal.  The deal ticket message is around 1K bytes.  I allocate a single ByteBuffer for each session and keep it in the session object, which is stored in Mina's session context map, when a deal ticket needs to be written to the client the pre-allocated ByteBuffer is retrieved from the session context object and used for building the outbound data set.  Here are the actual code fragments:

This factory creates a session context object for each connection:

public class SessionContextFactory

{

    static private SessionContextFactory instance = new SessionContextFactory();

    static public SessionContextFactory getInstance()

    {

        return SessionContextFactory.instance;

    }

    private SessionContextFactory()

    {

    }

    public SessionContext createSessionContext(ProtocolSession session)

    {

        return new SessionContext(session);

    }

    public final class SessionContext implements SessionContext, LifeSpanControllable

    {

        ByteBuffer dataBuffer;

 

        private SessionContext(ProtocolSession session)

        {

            dataBuffer = ByteBuffer.allocate(1024);

        }

        public ByteBuffer getDataBuffer()

        {

            dataBuffer.acquire();'

            return dataBuffer;

        }

    }

}

 

This class performs the message encoding using the single ByteBuffer retrieved from the session context object:

 

public abstract class EasiAbstractMessageEncoder implements MessageEncoder

{

    private final String type;

 

    protected EasiAbstractMessageEncoder(String type)

    {

        this.type = type;

    }

    protected String getType()

    {

        return type;

    }

    public void encode(ProtocolSession session, Object message, ProtocolEncoderOutput out) throws ProtocolViolationException

    {

        SessionContext sessCtx = (SessionContext)session.getAttribute(ServerConstants.SESSION_CONTEXT);

        EasiAbstractMessage m = (EasiAbstractMessage)message;

 

        ByteBuffer buf = sessCtx.getDataBuffer();

        buf.clear();

        // Encode the header

        encodeHeader(session, m, buf); // will perform several buf.put() calls

        // Encode the body

        encodeBody(session, m, buf); // will perform many buf.put() calls

        buf.flip();

        out.write(buf);

    }

    protected abstract void encodeMetadata(ProtocolSession session, EasiAbstractMessage message, ByteBuffer buf);

    protected abstract void encodeHeader(ProtocolSession session, EasiAbstractMessage message, ByteBuffer buf);

    protected abstract void encodeBody(ProtocolSession session, EasiAbstractMessage message, ByteBuffer buf);

}

 

As you can see the buffer is acquired every time it is retrieved from the session context object.  Apparently Mina is releasing the buffer in the out.write(buf) call, is that true?

Yes, it's true.  This also means your dataBuffer will never return to the MINA buffer pool because you acquired the buffer once more.

At a high level, what this server needs to do is to be able to send 40 50 1K deal ticket per second down to a single client (with the nagle algorithm disabled).  BTW the client is very fast and almost always responds to the deal ticket message within the same millisecond in which it was received.

Is it reasonable to expect this kind of performance from Mina?

Actually myself didn't push MINA that much.  Anyone in the list might give you some information.

Also worth noting is that the decoding, handling, retrieval of deal ticket from the database, encoding and all of the related parsing etc. is very fast and usually only takes around 10 to 20 milliseconds to complete.

Your code looks OK to me. It would be nice if you can give us heap profiler data so we can find out which objects are dominant.

Any help would be greatly appreciated!  (Sorry for being so long winded!)

No problem.  Thank you for detailed information! :)

Thanks for all who work on Mina, a very elegant design which renders NIO much more approachable.

Your feedback will make MINA more robust and beautiful.  Please don't hesitate! ;)
 
HTH,
Trustin
--
what we call human nature is actually human habit
--
http://gleamynode.net/
--
PGP key fingerprints:
* E167 E6AF E73A CBCE EE41  4A29 544D DE48 FE95 4E7E
* B693 628E 6047 4F8F CFA4  455E 1C62 A7DC 0255 ECA6