Hi,

here is a memo describing the debugging session we did yesterday with Julien. I have added TODO each time I think we can do better.



MINA analyze when processing one single message. The server receives "hello" and should return "HELLO".

- We start in the main IoProcessor loop, after a select() returning 1 on a channel set with OP_READ, as we have just received the message.
- the process() method is called. It does two things :
1) if the session is readable and not suspended, read the channel
2) is the session is writable and not suspended, add the session to the queue of session ready for flush, if not already there.

TODO : at this point, we have no idea if the session has some pending writes. Check if the OP_WRTE flag has been set on the selectionKey

- in the read() method, we allocate a buffer with the size configured for the session. Usually, it's way to big if you just have hundreds of bytes to read, and if you set the config to 65000…

TODO : use smaller buffers

- Then we call the chain's messageReceived() method

- In the ProtocolCodecFilter, we grab the decoder instance, which is stored in the session's Attributes

TODO : There is no good reason to store the instance in the attributes. It should be part of the session parameters.

- then we create a new instance of a decoderOut.

TODO : The only reason we have such an object created here is that we need to create a queue to store the result of the decoding.

- We now decode the incoming data, until the buffer is empty. We may have more than one message in the buffer, so each one of them is decoded and enqueued in the encoderOut queue.

- The doDecode() method is called. This is the user provided code. The decoded message is enqueued, and we do that until the doDecode() method returns false (meaning we don't have anymore message to decode)

- We call the encodedOut.flush() method to go up in the chain

TODO : it should be done directly here

- For each decoded message in the queue, call the next filter messageReceived() event

TODO : we went through the tail filter, which is updating some stats used by the idle session handler, but this should be done in another place. Everyone does not want to deal with idle sessions … My be an IdleFilter could help ?

- In the handler, we process the message, and write the response calling session.write( response )

- The session.write() method create a writeFuture and a WriteRequest object containing the response, the recipient's address and the future

TODO : this WriteFuture is never used.

- We go back to the chain in reverse order, firing the FilterWrite method

TODO : This should be a separate chain

- We get the encoded and we create an encoderOut instance, containing a queue

TODO : here, unless the encoder creates more than one buffer to be sent, there is no need to create a queue. We can also call the encoder, get back the result and flush it, doing so receptively until all the encoded pieces has been generated (but I feel like we better get back a set of buffers instead as a response. No need of a queue)

- The user's provided encoder method is called, the encoded buffer is enqueued. It can still be a plain Message, if we want to go though another encoder

- At this point, we call the flushWithoutFuture() method which loop on the encoderOut queue to send all the encoded buffers

- for each encoded message in the queue, we create an EncodedWriteRequest encapsulating the encoded buffer, and we go down in the chain

- The WriteRequest is enqueued in the session WriteRequestQueue, and if the session is not suspended for write, we call the processor.flus() method

TODO : there is no reason to call this method now. Also the session.getProcessor() method ask the processor pool for the session's processor, which is useless, as each session is attached to a single IoProtocol. We must store not the pool, but the processor

- If the session is not already in the queue of session waiting to be flushed, we add it into this queue, then we wake up the selector

TODO : Waking up the selector at this point is questionable. It's probably totally useless.

- Ok, we go back, and call the filterWrite() method once again in the protocolCodecFilter, with a new MessageWriteRequest instance, encapsulating the response.

TODO : WTF ??? Why do we process the same message twice ???

- In the HeadFilter, we grab the message, which is empty. In fact, this empty message is used as a marker for the 'end of message'. Nevertheless, we add this empty message to the WriteRequestQueue

- We ask the processor to flush again the session, but as the session has already been added into the scheduledForFlush queue, nothing is done

- And we are done with all the process() method. We come back to the main select loop

- Time to process the session waiting to be flushed now… This is done with a call to the flush(currentTime) method. We check that we have sessions ready to be flushed first. If so, we loop on all those sessions.

Note : a session may be marked as ready to be flushed either because we just have some new message for it, or because a big message hasn't be written to the client completely n a previous loop.

At first, we remove the session from the queue of scheduled sessions. We will put it back if the full message hasn't been written later. If the session is open, we call the pocessor.flushNow() method

- There, we grab he WriteRequestQueue for this session. There is something obscure done here : we compute some number based on the max read and write buffer.

TODO : remove all this crapity…

We reset the OP_WRITE flag (it may have been set in a previous call).

The session stores the current buffer being flushed in a special holder. if it's null, we take the first message from the queue and stores it into this session.

TODO : This is absolutely useless. At this point, we know exactly where we left when we wrote it to the client. it's enough to keep the buffer in the queue, peeking it instead of polling it. We just remove it when the message has been completely sent.

- We now call the writeBuffer() method, responsible for the writing of the data to the client.

TODO : There is a totally useless loop in this method, where we try to write to the client up to 256 times, just in case the client is slow, I guess. This is overkilling. We have *no idea* why this loop exists, except that back in the past, some strange bug was fixed with such a 'workaround'

- The buffer is written to the channel, and we get back the number of written bytes. If we have written all the bytes, we call the fireMessage() method. The currentWriteRequest is cleared in the session. The WriteFuture is now set to Written state (useless, as nobody uses this information).

TODO : Get rid of useless tasks

- In the PotocolCodecFilter, as the WriteRequest is the encodedWriteRequest, we immediately return.

TODO Why the hell are we calling the messageSent() method at all as we just don't process the information here ??? probably because we have no clue about the fact that the WriteRequest is a message at this point. It has to be fixed, it's sucking useless CPU

- Then we are back, and process the ext message in the queue, which is the empty message (used as a end-of-message marker)

- The writeBuffer() method just do nothing, as this message is empty. It just call the messageSent() method to inform the handler that the message has been sent. So we go up in the chain to the handler, and back.

- There is now a stupid things done (one more …) : as we didn't wrote anything, the number of written bytes is 0, so we consider that the socket is full, and we switch the OP_WRITE flag to true. This is a 100% guarantee that we will do a full select loop again, for nothing…

TODO : FIX ME !!!

- When we are back from the flushNow() method, we get a false, as we didn't' flushed the empty message. But we are done with the list of sessions scheduled for a flush.

- and we get back to the select(), which will exit immediately with at least one selectionKey set with OP_WRITE, as wear still waiting to write the empty message !!!. That means we will process two reads for one write.

- once we enter again in the mail select loop, the select() returns 1, we go back to the process() method, do nothing in the read part, but put back the session onto the scheduledForFlush queue, in order to be processed again in the flush() call.

- In the flush() method, we reset the OP_WRITE flag, so that we aren't woke up again, and we are done.



That's it !!! Lot of hacks, lot of useless things, lot of potential speedup expected !




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


Reply via email to