This page refers to MINA 1 only !
One of the most tricky part in using MINA is configuring your application's thread model, but once understood, it is fairly easy.
Disable the default ThreadModel
setting.
MINA 2.0 and later versions doesn't have ThreadModel
anymore. Please skip this section if you are using them.
ThreadModel
setting has been introduced since MINA 1.0. Later, we realized using ThreadModel
increases the complexity of configuration. It is strongly recommended to disable the default ThreadModel
setting.
SocketAcceptor acceptor = ...; SocketAcceptorConfig acceptorConfig = acceptor.getDefaultConfig(); acceptorConfig.setThreadModel(ThreadModel.MANUAL);
Please note the other parts of this tutorial assume that you disabled ThreadModel
as addressed in this section.
Configure the number of I/O worker threads.
This section relates to the NIO socket implementation only. Other implementations such as the NIO datagram and in-VM pipe doesn't have this configuration.
In MINA, there are three kinds of I/O worker threads in the NIO socket implementation.
- Acceptor thread accepts incoming connections, and forwards the connection to the I/O processor thread for read and write operations.
- Each
SocketAcceptor
creates one acceptor thread. You can't configure the number of the acceptor threads.
- Each
- Connector thread attempts connections to a remote peer, and forwards the succeeded connection to the I/O processor thread for read and write operations.
- Each
SocketConnector
creates one connector thread. You can't configure the number of the connector threads, either.
- Each
- I/O processor thread performs the actual read and write operation until the connection is closed.
- Each
SocketAcceptor
orSocketConnector
creates its own I/O processor thread(s). You can configure the number of the I/O processor threads. The default maximum number of the I/O processor threads isthe number of CPU cores + 1
.
- Each
Therefore, the available configuration is the number of the I/O processor threads per IoService
. The following code creates a SocketAcceptor
with 4 I/O processor threads and one acceptor thread.
SocketAcceptor acceptor = new SocketAcceptor(4, Executors.newCachedThreadPool());
There's no rule of thumb in determining the number of I/O processor threads yet. You could probably start from adding 1 to the number or CPUs (cores). The following code creates a SocketAcceptor
with nCPU
+ 1 I/O processor thrads and one acceptor thread.
SocketAcceptor acceptor = new SocketAcceptor(Runtime.getRuntime().availableProcessors() + 1, Executors.newCachedThreadPool());
Please note that you can do the same for SocketConnector
. There's no difference except that a connector thread is created instead of an acceptor thread.
SocketConnector connector = new SocketConnector(Runtime.getRuntime().availableProcessors() + 1, Executors.newCachedThreadPool());
Add an ExecutorFilter
to an IoFilterChain
.
ExecutorFilter
is an IoFilter
that forwards incoming I/O events to a java.util.concurrent.Executor
implementation. The events are forwarded to the next IoFilter
via the Executor
, which is usually a thread pool. You can add any number of ExecutorFilter
anywhere in the IoFilterChain
to implement any kind of thread model including from a simple thread pool to complex SEDA.
We didn't add any ExecutorFilter
so far. If no ExecutorFilter
is added, events are forwarded to an IoHandler
via method invocations. This means your business logic in your IoHandler
implementation will run in the I/O processor thread. We call this thread model a 'single thread model'. The single thread model is known to be adequate for low-latency network applications with CPU-bound business logic (e.g. game servers).
Typical network applications need an ExecutorFilter
to be inserted into the IoFilterChain
because the business logic has different resource usage pattern from the I/O processor threads. If you didn't add an ExecutorFilter
with an IoHandler
implementation which performs database operations, your entire server can block when a database operation occurs, especially when the database is under load. The following example configures an IoService
to add an ExecutorFilter
when a new IoSession
is created.
SocketAcceptor acceptor = ...; DefaultIoFilterChainBuilder filterChainBuilder = acceptor.getDefaultConfig().getFilterChain(); filterChainBuilder.addLast("threadPool", new ExecutorFilter(Executors.newCachedThreadPool());
Please note that the ExecutorFilter
doesn't manage the life cycle of the specified Executor
at all. You have to shut down all worker threads of the specified Executor
when you are finished with it.
ExecutorService executor = ...; SocketAcceptor acceptor = ...; DefaultIoFilterChainBuilder filterChainBuilder = acceptor.getDefaultConfig().getFilterChain(); filterChainBuilder.addLast("threadPool", new ExecutorFilter(executor); // Start the server. acceptor.bind(...); ... // Shut down the server. acceptor.unbind(...); executor.shutdown();
Using an ExecutorFilter
doesn't always mean that you use a thread pool though. There's no limitation on what Executor
implementation you specify at all.
Where should I put an ExecutorFilter
in an IoFilterChain
?
It depends on the characteristics of your application. For an application with a ProtocolCodecFilter
implemetation and a usual IoHandler
implementation with database operations, I'd suggest you to add the ExecutorFilter
after the ProtocolCodecFilter
implementation. It is because the performance characteristic of most protocol codec implementations is CPU-bound, which is the same with I/O processor threads.
SocketAcceptor acceptor = ...; DefaultIoFilterChainBuilder filterChainBuilder = acceptor.getDefaultConfig().getFilterChain(); // Add CPU-bound job first, filterChainBuilder.addLast("codec", new ProtocolCodecFactory(...)); // and then a thread pool. filterChainBuilder.addLast("threadPool", new ExecutorFilter(Executors.newCachedThreadPool());
Be careful when you choose the thread pool type for IoService
.
Executors.newCachedThreadPool()
is always preferred by IoService
. It is because using other thread pool type can lead to unpredictable performance side effect in IoService
. Once all threads in the pool become in use, IoService
will start to block while it tries to acquire a thread from the pool and to start to show weird performance degradation, which is sometimes very hard to trace.
It is not recommended to share one thread pool for IoServices
and ExecutorFilters
.
You might want to share one thread pool for IoServices
and ExecutorFilters
instead of using one thread pool for each. It is not prohibited at all, but please note that it can cause a lot of problems unless you use a cached thread pool for IoServices
in such a case.
3 Comments
Sanjeev Sachdev
This page talks about class NioSocketAcceptor which, I think, does not exist. I think that class meant is org.apache.mina.transport.socket.nio.SocketAcceptor. If so, could this be corrected ?
Emmanuel Lécharny
Fixed...
Sanjeev Sachdev
Thanks for your response. But I wonder why IoAcceptor has been replaced by SocketAcceptor !