Hi guys, some thoughts about the TCP server. feel free to comment.

TCP server MINA 3
-----------------

As we are reworking the server part of MINA, we can review the current architecture. There are a few problems we can address.

1) One vs Many selectors
In MINA 2, we do have at least 2 selectors used :
- one for the OP_ACCEPT event
- One or many for the OP_READ/OP_WRITE

I don't think that it makes a lot of sense to force such a choice. IMO, we could perfectly start with one single selector to handle all the events, assuming we are fast enough to process them. Otherwise, we can also configure the server to use more selectors, if needed.

Typically, the acceptor selector will just deal with incoming new connections, and the created channel will be registred on an IoProcessor selector on MINA 2. We could register this channel on the acceptor selector.

In any case, if we do create more than one selector, I suggest that the first one would always handle OP_ACCEPT events, and that's all.

The general algorithm will look likes :

signal started

while true
  nbSelect = select( delay )

  if nbSelect != 0
    for each selectionKey do
case isAccept // Only if the selector is the first one, otherwise we don't need to heck this case
        create session

      case isRead
        process read

      case isWrite
        process write
    done

  if dispose // In order to stop the server
    process dispose
    break
done

The key is to start all the selector workers *before* accepting any connection, otherwise we may lose some messages. One more thing : each selector should signal that there have started before entering in the loop, so that the caller don't have to wait a random period of time for the selectors to be started.

2) Events processing
Now, in order to limit the number of selectors to use, we need to limit the time it takes to process the read/write/accept events. But even if we have many selectors, we should try to minimize the risk that one selector is blocked by a single session blocked somewhere while doing some heavy prcoessing, as it will block all the other sessions.

Having more than a selector is one way to mitigate this issue : as we have many threads (one per selector), we spread the loads on as many threads. Another solution would be to use an executor in charge of processing the events, with a queue between the selector and the executor, queue that is used to process the events as fast as possible on the selector (this is really important for UDP, as we don't want to lose messages simply because the OS buffer is full).

The problem is that we are just not solving the problem of a rogue service that block a thread for a long time (if we use a limited size executor), or we may end with so many threads that it may kill the server. But anyway, it sounds like a better solution, as incoming events that won't require a long processing will be favored in the long term.

3) Write processing
This is a complex issue too : we may not be able to push all the data we want into the socket, if it becoms full (or was already full). In this case, we will have to store the data in a queue. The following algorithm describe this situation and a proposal to solve it

    if there are some data in the writeQueue
      then
        // We need to enqueue the data, and write the head of the queue
        enqueue data

        // Now, we shoudl try to write as much of the queue as we can
        while ( queue not empty)
          do
              poll the data from the queue
nbWritten = channel.write( remaining data ) // We may have already written some part of the head data

            if nbWritten < data.remaining
              then
// the socket is already full, set its OP_WRITE interest, and don't touch the queue
                selectionKey.ops = OP_WRITE
                break // We can stop, the socket is full anyway
              else
pop the data from the queue // We just remove the head, and continue with the next data
          done

          if queue is empry
            then
selectionKeys.ops = ! OP_WRITE // We remoe this flag, it's ueless now
      else
nbWritten = channel.write( remaining data ) // We may have already written some part of the head data

        if nbWritten < data.remaining
          then
// the socket is already full, set its OP_WRITE interest, and add the data in the queue
            selectionKey.ops = OP_WRITE
            writeQueue.add( remaining data )

4) Read/Write order
MINA 2 was processing all the reads first, then all the writes. This is not necessarilly a good idea. We may rather process read and write on a per session basis. It's perfectly possible that a session has to process some reads and some write at the same time. Waiting for all the reads to be processed create some memory load, as we will have to wait until all the reads are done, storing all the data to be written in the mean time, until we are done with the last session.

5) MINA events
Atm, we are processing the following events :
- messageReceived (when we have read some data from the channel)
- messageSent (when a user message has been sentcompletely)
- exceptionCaugth (when an exception has been caught)
- sessionIdle (if the session has not received or sent anything for a period of time)
- sessionCreated (when a new session is created)
- sessionOpened
- sessionClosed (when the session has been closed)
- filterWrite
- filterClose

The sessionOpened, filterWrite and filterClose are a bit mysterious. I don't see why we need a special event SessionOpened when we alreayd have the sessionCreated event. That may seems we *may* create a session, but not accept any incoming or outgoing messages for this session, until it's initialized. I'm not sure this is necessary.

The two other events are probably some YAGNY iplementation...

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

Reply via email to