Child pages
  • Configuring Thread Model
Skip to end of metadata
Go to start of metadata

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.
  • 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.
  • I/O processor thread performs the actual read and write operation until the connection is closed.
    • Each SocketAcceptor or SocketConnector 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 is the number of CPU cores + 1.

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.

  • No labels

3 Comments

  1. 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 ?

      1. Thanks for your response. But I wonder why IoAcceptor has been replaced by SocketAcceptor !