Issue Details (XML | Word | Printable)

Key: DIRMINA-119
Type: Improvement Improvement
Status: Closed Closed
Resolution: Fixed
Priority: Minor Minor
Assignee: Trustin Lee
Reporter: dave irving
Votes: 0
Watchers: 1
Operations

If you were logged in you would be able to see more operations.
MINA

Multiple selector loops

Created: 09/Nov/05 06:42 PM   Updated: 07/Dec/05 06:03 PM
Return to search
Component/s: None
Affects Version/s: 0.8.0
Fix Version/s: 0.9.0

Time Tracking:
Not Specified

File Attachments:
  Size
Zip Archive Licensed for inclusion in ASF works multipleIoProcessors.zip 2005-12-02 07:59 PM dave irving 32 kB
Zip Archive Licensed for inclusion in ASF works multipleIoProcessors.zip 2005-12-02 07:54 PM dave irving 0.0 kB
Zip Archive prototype.zip 2005-11-09 06:47 PM dave irving 6 kB
Environment: All. Benefit is dependant on environment

Resolution Date: 03/Dec/05 01:41 PM


 Description  « Hide
Mina's SocketIoProcessor currently owns a Selector and employs a single Worker to run the NIO "selector loop".
I have been running tests where Im trying to maximise throughput and have found - that in certain multi-cpu environments - this worker thread can encounter a large amount of starvation even though CPU usage is fairly low.

By testing 2 selector-loops instead of 1, I managed to improve my overall test throughput by just under 30%.

The general idea is to do this:

- Each SocketIoProcessor.Worker encapsulates its own work queues associated Selector
- It should be possible to configure the number of Workers (and thus selectors) employed by SocketIoProcessor
- When a SocketSession is added to the SocketIoProcessor, a Worker is selected (round-robin) which will be associated with the SocketSession for its lifetime. This association is managed by SocketSession (get/setWorker)
- When someone asks SocketIoProcessor to do some work to a session, instead of doing it directly, the processor now asks the session for its Worker, and delegates to the worker (i.e, the same worker is always used for an individual session)

I've done some prototyping, and have also checked that the concept works with the latest build.
The prototype is very hacky - mainly because there are some refactoring issues i'd like feed-back on before I submit a "proper" patch for review. Namely:

- How do you want me to tell the SocketIoProcessor how many workers to use? One option is a system property - but thats pretty hacky. I dont think we need to support changing the number of workers after operation has begun (It'll probably be a function of the number of available CPUs) - and this makes the code simpler. However, as SocketIoProcessor is a (non lazy created) singleton, we need a way to get the param in. We could refactor, or maybe introduce a ProcessorOptions class or something. The SocketIoProcessor could interrigate this when initializing. Any direction on your desired approach would be appreciated

Cheers,

Dave

 All   Comments   Work Log   Change History   Subversion Commits      Sort Order: Ascending order - Click to sort in descending order
dave irving added a comment - 09/Nov/05 06:47 PM
Very hacky prototype. Will refactor / improve following comments.
The one contentious issue is probably that I dont bother closing down "idle" worker threads once they've been opened - but that was just for the prototype really. I'll add support that back following initial feedback when I do the refactoring.

Julien Vermillard added a comment - 09/Nov/05 07:06 PM
I would really love to be able to specify a different SocketIOProcessor thread per Connector or ProtocolAcceptor. the common thread between all my processosr create a lot of deadlock when a connector event try to wait a reply on a different connector, blocking the whole socket processing.

dave irving added a comment - 09/Nov/05 07:14 PM
My proposed change wouldn't solve your particular problem.
Although it raises a further question: Maybe the best level of abstraction for the threading model is at the SocketIoProcessor level (I.e, a SocketIoProcessor has a selector and a selector loop), but we can potentially have multiple processors.

The reason I didn't do it this way in my prototype is that I wanted to keep refactoring to a minimum just to see if there was any support for the idea (and the easiest way was to bodge it in to SocketIoProcessor).

If we do the thread modelling at the SocketIoProcessor level, you could then have a ProcessorFactory or whatever.
This would enable any-old custom logic to map processors to SocketSessions.

Hmmmm - getting more complex though, and a wider ranging change :o)

Trustin?

Trustin Lee added a comment - 10/Nov/05 10:41 PM
What about providing some static methods for SocketIoProcessor and DatagramConnector such as...:

* getSelectorThreads()
* setSelectorThreads(int nThreads)

So user can control the number of selector loops?

And we could assign one SocketIoProcessor per SocketAcceptor (or SocketConnector) and make the methods above non-static. (Actually this gives a user more flexibility)

WDYT?

dave irving added a comment - 11/Nov/05 11:38 PM
Hi,

I've been out of the office for a couple of days, and have a few meetings this afternoon.
However, I'll try and take a look over the weekend, or maybe early next week - and I'll post a patch to this issue as soon as I can.

Thanks,

Dave

Trustin Lee added a comment - 12/Nov/05 12:09 AM
Hi Dave,

Great, actually I closed DIRMINA-113 today and added a new interface 'SocketSessionManager' that SocketAcceptor and SocketConnector implement. You'll have to implement the two methods in SocketSessionManager (SocketAcceptorDelegate and SocketConnectorDelegate, strictly speaking).

Please let me know if you have any problem with this. For now, I think we can use round-robin strategy. But it would be also great if we can make it so effective.

Thanks,
Trustin

dave irving added a comment - 12/Nov/05 12:12 AM
Sounds good!
I'll try to spend some time looking at this over the weekend.

Thanks,

Dave

Trustin Lee added a comment - 12/Nov/05 12:13 AM
I think we can make it included in 0.9.0

Trustin Lee added a comment - 01/Dec/05 04:35 PM
Users might not need to adjust the number of SocketIoProcessor in runtime. Letting user specify an appropriate number they like with the constructor will be fine for now. It would be great if there is any optimal number of processors for the number of CPUs. Any idea?

dave irving added a comment - 01/Dec/05 07:13 PM
Hi Trustin,

Yes, I think this is fine. There is no real reason I can think of why you'd want to change the number of processors at runtime. The most effective number is likely to be bound in to the number of available CPUs.
Certainly an easy way to configure this currently - as you say - would be to pass the number of worker threads to be employed at construction time of the SocketIoProcessor.

I've got some free time today, so I'll try and tidy up the prototype and submit a patch

Niklas Therning added a comment - 01/Dec/05 07:29 PM
How about creating more than one SocketIoProcessor instead of maintaining a number of worker threads? This would simplify things a lot I think. SocketAcceptorDelegate/SocketConnectorDelegate would have a list of SocketIoProcessors each. The number of SocketIoProcessors is configured using setProcessors() on the SocketSessionManager. When a new SocketSession is created the next (round-robin) SocketIoProcessor in the acceptor/connector's list of processors is passed in in the SocketSession constructor. Seems like an extremelly simple solution to me but there might be drawbacks?

dave irving added a comment - 01/Dec/05 07:44 PM
Yes, that is a good and logical solution. I did think a while back that keeping the threading at the io processor level might be a good way to go (see 4th post above). And infact, the refactoring which has been done for 0.9 such that SocketIoProcessor isn't a singleton anymore makes it much easier.

Drawbacks.....

The only drawback I can see is that in many systems it is most efficient to have a single pool of selector loops driving the whole IO system (as the number of threads is tied to the number of CPUs). Going this route would mean we'd have a bunch of threads for connectors and a bunch of threads for acceptors. Its not a huge problem though - just means we get slightly more context switching than we really need.

The other way round would be to change SocketSessionManager to just have a setter for a ProcessorFactory or something (as in 4th post above). That way we can use the same pool across the whole of mina (acceptors and connectors) - and we dont get any code duplication as the factory does the round-robining instead of the acceptor / connector delegates.

Niklas Therning added a comment - 01/Dec/05 07:54 PM
I like the idea with a SocketIoProcessorPool. Then the scheduling would be up to the pool. And you could change the pool implementation using setProcessorPool on a SocketSessionManager, right?

dave irving added a comment - 01/Dec/05 08:05 PM
Yes... the interface of SocketSessionManager would change from get / set processors to setSocketIoProcessorPool.

The only problem I can see with is that SocketIoProcessor needs a link back to its parent IoSessionManager so that it can notify it of (non session related) exceptions.

However, we could easily solve this by having the pool keep track of a list of exception monitors to notify when an exception occurs (i.e, the list of SSMs)

Niklas Therning added a comment - 01/Dec/05 08:40 PM
Sounds good. The SocketIoProcessor constructor takes an exception monitor as an argument instead of the parent IoSessionManager. Then the SocketIoProcessorPool could be configured with an exception monitor.

Would it really be necessary to maintain a list of exception monitors? Then you would still have to make sure that an exception is passed to the correct exception monitor associated with the SessionManager the session was originally created by? Or am I missing something here?

dave irving added a comment - 01/Dec/05 09:12 PM
> The SocketIoProcessor constructor takes an exception monitor as an argument instead of the parent IoSessionManager

Yes

> Then the SocketIoProcessorPool could be configured with an exception monitor.

Possibily, but I think that is maybe forcing knowledge of too many implementation details to the user.
Today, you just configure an ExceptionMonitor on an IoSessionManager (connectors / acceptors).
So non-session related exceptions are provided to this monitor.
I dont think pooling changes that. Sure, we now have multiple processors, but they are all working on behalf of all SessionManagers which are using them. As such, if an exception occurs in a pooled IoProcessor, I think we should notify the exception monitors of all session managers which are using the pool.




Niklas Therning added a comment - 01/Dec/05 09:24 PM
I see and agree. We could use a proxy ExceptionManager which broadcasts exceptions to a set of exception managers then. No need for the SocketIoProcessor to know there are more than one ExceptionMonitor. It's important that it is a set though (and not a list) otherwise the same exception monitor could be notified multiple times about the same exception.

dave irving added a comment - 01/Dec/05 09:34 PM
Yes - that sounds excellent.
So, we have:

interface SocketIoProcessorPool {

  public void associateProcessor(IoSession s);

}

interface SocketSessionManager {

  public void setSocketIoProcessorPool(SocketIoProcessorPool pool);

}

SimpleServiceRegistry (and spring integration?) then sets a proxy Exception monitor on its pool.

dave irving added a comment - 01/Dec/05 11:32 PM
Ok, code is nearly done - just writing the plumbing in SimpleServiceRegistry to hook it all together now.

One small thing I'd like to discuss though....

Today, users create SocketConnectors directly (i.e, IoConnector connector = new SocketConnector() ).
However, we'll need some way to tell the SocketConnector what processor pool it is using (we want it using the same one employed by the SimpleServiceRegistry's SocketAcceptor).

SimpleServiceRegistry is nice because it hides creational logic. Likewise, when configuring with Spring integration there is also no issue.

However, for non spring configuration use cases, it would be nice if the user didn't have to do anything themselves to get hooked up with the processing pool (its really an internal implementation detail).

Any ideas?


Niklas Therning added a comment - 02/Dec/05 12:20 AM
There could be a default singleton SocketIoProcessorPool: SocketIoProcessorPoolImpl.getDefaultInstance() which is used by all SocketIoAcceptor/Connector instances by default.

One question: why do we need associateProcessor(IoSession s)? Couldn't it just have a getIoProcessor() method? SocketIoAcceptor/SocketIoConnector would get a processor using this method and give it to the SocketSessionImpl when constructed.

dave irving added a comment - 02/Dec/05 12:37 AM
> There could be a default singleton SocketIoProcessorPool: SocketIoProcessorPoolImpl.getDefaultInstance()
> which is used by all SocketIoAcceptor/Connector instances by default.

The only problem is that there is no "close" (or similar) in IoConnector - and users can create connectors when ever they like. Thus theres no clean way to de-register the exception monitor from the exception monitor proxy for the connector. Hmmm........

> One question: why do we need associateProcessor(IoSession s)? Couldn't it just have a getIoProcessor()
> method? SocketIoAcceptor/SocketIoConnector would get a processor using this method and give it to the
> SocketSessionImpl when constructed.

I dont mind either way. The reason I did it this way is to avoid package dependency leak.
The main interfaces (SocketSessionManager / SocketIoProcessorPool) are in socket.nio. The "how its done" (including the SocketIoProcessor class and SocketSessionImpl) is in socket.nio.support.
So this way all the implementation details of the processing strategy for a session is within nio.support.

Maybe a better name would be SocketIoProcessingStrategy (and maybe startForSession instead of associateProcessor).

I dont mind either way though.



dave irving added a comment - 02/Dec/05 01:39 AM
For now a simple approach will be taken.

1) Currently, there is not much value in supplying an ExceptionMonitor per IoSessionManager. Instead, a single global ExceptionMonitor will be employed (which defaults to the "DefaultExceptionMonitor"
2) Likewise, the source of the exception is not really of any use to the user (it will be an internal Mina class). The "source" parameter will be removed from ExceptionMonitor
3) We dont need a pool interface for now. Instead, we'll add the pooling functionality straight to SocketIoProcessor. The pool size will be determined by a system property, and static utility methods will be added to SocketIoProcessor to obtain an instance to be bound to a session


dave irving added a comment - 02/Dec/05 07:54 PM
Proposed patch

dave irving added a comment - 02/Dec/05 07:55 PM
Hi,

I've attached a patch which I believe provides the functionality we agreed on yesterday.

Cheers,

Dave

dave irving added a comment - 02/Dec/05 07:59 PM
First attachment was empty... Sorry :o)

Trustin Lee added a comment - 03/Dec/05 01:41 PM
I checked in your patch after some modification. It works great. Thanks for the great job!

dave irving added a comment - 03/Dec/05 10:07 PM
no problem at all - glad to help!
were any of the modifications serious? let me know if I got something wrong so I can make patches better in the future :o)

cheers,

dave

Trustin Lee added a comment - 04/Dec/05 08:20 PM
Nothing serious. The only one issue I want to address is that GlobalExceptionMonitor is in 'support' package, in which users shouldn't need to access the classes. That's why I merged GlobalExceptionMonitor into ExceptionMonitor which is located in org.apache.mina.common. That's all! The patch was good. :)

dave irving added a comment - 07/Dec/05 06:03 PM
Completed