I am developing a java application using Apache Mina 2.0.16 as a NIO library. I admire the work and efforts that people used to create this kind of library so NIO beginners can simply create a non-blocking threaded TCP server and client. After creating my custom class TCPServer class and writing my main application logic I tried using the Netbeans java profiler to see the performance of this application.
This is the screen shot of the java profiler:
I noticed that I have 2 NioSocketAcceptors and that is correct because I use 2 separate TCP servers with different codec filter chains (one for java to java application communication using object serialization and one for java to C application using UTF-8 or windows-1251). This screen shot was taken with 0 active clients on the both servers. As I can see from the java profiler both are running 99% from the application time even when they are idle. The whole application with everything (GUI, database, timer-tasks, threads ...) on Windows and Linux uses approximately 20% of processor time on my gaming laptop with intel core i7 processor.
This is my TCPServer java class:
package es.modules.communication.tcp;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import org.apache.mina.core.future.WriteFuture;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.service.IoServiceStatistics;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.serialization.ObjectSerializationCodecFactory;
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
/**
*
* @author adrian
*/
public abstract class TCPServer {
private final int tcp_server_port; //Порта на која слуша серверот
private final NioSocketAcceptor acceptor; //Акцептор на сесии со клиенти
private final IoServiceStatistics statistics;
private Timer timer = new Timer("Timer for: TCP Server");
public static final int PROTOCOL_TRANSFER_OBJECT = 1;
public static final int PROTOCOL_TRANSFER_UTF_8 = 2;
public static final int PROTOCOL_TRANSFER_WINDOWS_1251 = 3;
/**
* Конструктор на кластата
*
* @param tcp_server_port Порта на која слуша серверот
* @param idle_timeout_sec Време во секунди после колку време клиентот ќе ја
* прогласи сесијата за IDLE односно доколку не прими било какви податоци од
* серверот
* @param write_timeout_sec После колку секунди клиентот да се откаже од
* испраќање на податоци до серверот
* @param trasfer_protocol Протокол за пренос на дата
* @throws java.io.IOException проблем при подигање на TCP серверот
*/
public TCPServer(int tcp_server_port, int idle_timeout_sec, int write_timeout_sec, int trasfer_protocol) throws IOException {
this.tcp_server_port = tcp_server_port;
this.acceptor = new NioSocketAcceptor();
this.acceptor.setReuseAddress(true);
this.acceptor.setCloseOnDeactivation(true);
this.acceptor.getSessionConfig().setWriteTimeout(write_timeout_sec);
this.acceptor.getSessionConfig().setIdleTime(IdleStatus.READER_IDLE, idle_timeout_sec);
switch (trasfer_protocol) {
case PROTOCOL_TRANSFER_OBJECT: {
this.acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new ObjectSerializationCodecFactory()));
break;
}
case PROTOCOL_TRANSFER_UTF_8: {
this.acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new TextLineCodecFactory(Charset.forName("UTF-8"))));
break;
}
case PROTOCOL_TRANSFER_WINDOWS_1251: {
TextLineCodecFactory text_line_codec_factory = new TextLineCodecFactory(Charset.forName("windows-1251"));
text_line_codec_factory.setDecoderMaxLineLength(64 * 1024);
text_line_codec_factory.setEncoderMaxLineLength(64 * 1024);
this.acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(text_line_codec_factory));
break;
}
}
this.acceptor.setHandler(new TCPServerHandler());
this.acceptor.bind(new InetSocketAddress(this.tcp_server_port));
this.statistics = this.acceptor.getStatistics();
this.statistics.setThroughputCalculationInterval(1);
this.timer.schedule(new IOStatistics(), 0, 1000);
}
/**
* Изгаси го серверот
*/
public void shutdown_server() {
if (this.acceptor != null && this.acceptor.isActive()) {
this.acceptor.unbind();
acceptor.getManagedSessions().values().stream().forEach((ss) -> {
ss.close(true);
});
this.acceptor.unbind(); //Just to be sure
this.acceptor.dispose(true);
}
}
/**
* Дали серверот е активен
*
* @return true/false активен/неактивен
*/
public boolean is_active() {
return this.acceptor.isActive();
}
/**
* Број на отворени сесии
*
* @return бројот на отворени сесии
*/
public int number_of_opened_session() {
return this.acceptor.getManagedSessionCount();
}
public ArrayList<String> get_all_connected_sessions() {
ArrayList<String> result_list = new ArrayList<>();
Map<Long, IoSession> sessions_map = acceptor.getManagedSessions();
sessions_map.values().stream().forEach((session) -> {
result_list.add(get_tcp_address(session.getRemoteAddress()));
});
return result_list;
}
/**
* Синхороно испраќање користејки ја сесијата
*
* @param session TCP/IP сесија
* @param message порка
* @throws Exception грешка при испраќање
*/
public void send_message_sync(IoSession session, Object message) throws Exception {
WriteFuture write_future = session.write(message);
if (write_future.getException() != null) {
throw new Exception(write_future.getException().getMessage(), write_future.getException());
}
}
/**
* Асинхороно испраќање користејки ја сесијата
*
* @param session TCP/IP сесија
* @param message порка
*/
public void send_message_async(IoSession session, Object message) {
session.write(message);
}
/**
* Синхороно испраќање користејки идентификатор на сесија
*
* @param sessionID идентификатор на сесија
* @param message порка
* @throws Exception грешка при испраќање
*/
public void send_message_sync(long sessionID, Object message) throws Exception {
WriteFuture write_future = this.acceptor.getManagedSessions().get(sessionID).write(message);
write_future.awaitUninterruptibly();
if (write_future.getException() != null) {
throw new Exception(write_future.getException().getMessage(), write_future.getException());
}
}
/**
* Асинхороно испраќање користејки идентификатор на сесија
*
* @param sessionID идентификатор на сесија
* @param message порка
*/
public void send_message_async(long sessionID, Object message) {
this.acceptor.getManagedSessions().get(sessionID).write(message);
}
/**
* Синхороно испраќање користејки адреса/порта на сесијата
*
* @param remote_tcp_address адреса на клиентот
* @param remote_tcp_port порта на клиентот
* @param message порка
* @throws Exception грешка при испраќање
*/
public void send_message_sync(String remote_tcp_address, int remote_tcp_port, Object message) throws Exception {
for (Map.Entry entry : this.acceptor.getManagedSessions().entrySet()) {
IoSession session = (IoSession) entry.getValue();
if (get_tcp_address(session.getRemoteAddress()).equals(remote_tcp_address) && get_tcp_port(session.getRemoteAddress()) == remote_tcp_port) {
WriteFuture write_future = session.write(message);
write_future.awaitUninterruptibly();
if (write_future.getException() == null) {
return;
} else {
throw new Exception(write_future.getException().getMessage(), write_future.getException());
}
}
}
throw new Exception("Threre is no session with " + remote_tcp_address + ":" + remote_tcp_port);
}
/**
* Асинхороно испраќање користејки адреса/порта на сесијата
*
* @param remote_tcp_address адреса на клиентот
* @param remote_tcp_port порта на клиентот
* @param message порка
* @throws Exception грешка при испраќање
*/
public void send_message_async(String remote_tcp_address, int remote_tcp_port, Object message) throws Exception {
for (Map.Entry entry : this.acceptor.getManagedSessions().entrySet()) {
IoSession session = (IoSession) entry.getValue();
if (get_tcp_address(session.getRemoteAddress()).equals(remote_tcp_address) && get_tcp_port(session.getRemoteAddress()) == remote_tcp_port) {
session.write(message);
return;
}
}
throw new Exception("Threre is no session with " + remote_tcp_address + ":" + remote_tcp_port);
}
public void broadcast_message_sync(Object message) throws BroadCastException {
boolean at_least_on_exception = false;
ArrayList<Exception> error_list = new ArrayList<>();
Set<WriteFuture> write_futures = this.acceptor.broadcast(message);
for (WriteFuture write_future : write_futures) {
if (write_future.getException() != null) {
at_least_on_exception = true;
error_list.add(new Exception(write_future.getException().getMessage(), write_future.getException()));
}
}
if (at_least_on_exception) {
throw new BroadCastException(error_list);
}
}
public void broadcast_message_async(Object message) {
this.acceptor.broadcast(message);
}
/**
* (настан) Сесија креирана
*
* @param session tcp/ip сесија
* @param ID идентификатор на сесијата
* @param remote_tcp_address оддалечена TCP/IP адреса
* @param remote_tcp_port одлачена TCP/IP порта
*/
public abstract void session_created(IoSession session, long ID, String remote_tcp_address, int remote_tcp_port);
/**
* (настан) Сесија отворена
*
* @param session tcp/ip сесија
* @param ID идентификатор на сесијата
* @param remote_tcp_address оддалечена TCP/IP адреса
* @param remote_tcp_port одлачена TCP/IP порта
*/
public abstract void session_opened(IoSession session, long ID, String remote_tcp_address, int remote_tcp_port);
/**
* (настан) Сесија затворена
*
* @param session tcp/ip сесија
* @param ID идентификатор на сесијата
* @param remote_tcp_address оддалечена TCP/IP адреса
* @param remote_tcp_port одлачена TCP/IP порта
*/
public abstract void session_closed(IoSession session, long ID, String remote_tcp_address, int remote_tcp_port);
/**
* (настан) Примена порака
*
* @param session tcp/ip сесија
* @param ID идентификатор на сесијата
* @param remote_tcp_address оддалечена TCP/IP адреса
* @param remote_tcp_port одлачена TCP/IP порта
* @param message порака добиена од серверот
*/
public abstract void message_received(IoSession session, long ID, String remote_tcp_address, int remote_tcp_port, Object message);
/**
* (настан) Испратена порака
*
* @param session tcp/ip сесија
* @param ID идентификатор на сесијата
* @param remote_tcp_address оддалечена TCP/IP адреса
* @param remote_tcp_port одлачена TCP/IP порта
* @param message порака испратена до серверот
*/
public abstract void message_sent(IoSession session, long ID, String remote_tcp_address, int remote_tcp_port, Object message);
/**
* (настан) Грешка во сесијата
*
* @param session tcp/ip сесија
* @param ID идентификатор на сесијата
* @param remote_tcp_address оддалечена TCP/IP адреса
* @param remote_tcp_port одлачена TCP/IP порта
* @param cause грека која е настаната
*/
public abstract void session_exception(IoSession session, long ID, String remote_tcp_address, int remote_tcp_port, Throwable cause);
/**
* (настан) Неактивна сесија
*
* @param session tcp/ip сесија
* @param ID идентификатор на сесијата
* @param remote_tcp_address оддалечена TCP/IP адреса
* @param remote_tcp_port одлачена TCP/IP порта
*/
public abstract void session_idle(IoSession session, long ID, String remote_tcp_address, int remote_tcp_port);
public abstract void read_write_statisctics(String read_bytes_throughput, String written_bytes_throughput);
private String get_tcp_address(SocketAddress address) {
return address.toString().split(":")[0].substring(1);
}
private int get_tcp_port(SocketAddress address) {
return Integer.parseInt(address.toString().split(":")[1]);
}
private class TCPServerHandler extends IoHandlerAdapter {
@Override
public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
session_exception(session, session.getId(), get_tcp_address(session.getRemoteAddress()), get_tcp_port(session.getRemoteAddress()), cause);
}
@Override
public void messageReceived(IoSession session, Object message) throws Exception {
message_received(session, session.getId(), get_tcp_address(session.getRemoteAddress()), get_tcp_port(session.getRemoteAddress()), message);
}
@Override
public void messageSent(IoSession session, Object message) throws Exception {
message_sent(session, session.getId(), get_tcp_address(session.getRemoteAddress()), get_tcp_port(session.getRemoteAddress()), message);
}
@Override
public void sessionIdle(IoSession session, IdleStatus status) throws Exception {
session_idle(session, session.getId(), get_tcp_address(session.getRemoteAddress()), get_tcp_port(session.getRemoteAddress()));
}
@Override
public void sessionCreated(IoSession session) throws Exception {
session_created(session, session.getId(), get_tcp_address(session.getRemoteAddress()), get_tcp_port(session.getRemoteAddress()));
}
@Override
public void sessionClosed(IoSession session) throws Exception {
session_closed(session, session.getId(), get_tcp_address(session.getRemoteAddress()), get_tcp_port(session.getRemoteAddress()));
}
@Override
public void sessionOpened(IoSession session) throws Exception {
session_opened(session, session.getId(), get_tcp_address(session.getRemoteAddress()), get_tcp_port(session.getRemoteAddress()));
}
}
private class IOStatistics extends TimerTask {
@Override
public void run() {
statistics.updateThroughput(System.currentTimeMillis());
String read_bytes_throughput = String.format("%.2f", statistics.getReadBytesThroughput());
String written_bytes_throughput = String.format("%.2f", statistics.getWrittenBytesThroughput());
read_write_statisctics(read_bytes_throughput, written_bytes_throughput);
}
}
public class BroadCastException extends Exception {
final private ArrayList<Exception> error_list;
public BroadCastException(ArrayList<Exception> error_list) {
super();
this.error_list = error_list;
}
public ArrayList<Exception> getError_list() {
return error_list;
}
}
}
I started searching on the web and found that version 2.0.12 had a bug with 100% CPU using, but at that time I was using 2.0.13. Still I downloaded the latest version 2.0.16 but still the same. This is a quote from the official web site.
MINA 2.0.12 released posted on February, 07, 2016
This new release of MINA is a bug fix release. There are a few new bugs that were wound in the way we handle closure, leading to some infinite loop consuming 100% CPU, and a bad counter update forbidding the main loop to be exited.
In this link: https://issues.apache.org/jira/browse/DIRMINA-1006 I found a solution that needs changing the AbstractPollingIoProcessor class and adding IDLE_TIMEOUT, but i cannot find the source-code so I can edit it. This is how this AbstractPollingIoProcessor should look like: https://git1-us-west.apache.org/repos/asf?p=mina.git;a=blob;f=mina-core/src/main/java/org/apache/mina/core/polling/AbstractPollingIoProcessor.java;h=abd704580e65a2f6e381655efa971fd7ee18752e;hb=2d6f8256.
On this forum thread: http://apache-mina.10907.n7.nabble.com/MINA-hogs-the-CPU-100-with-resetWakeupSocket-td41471.html it says that:
Roughly 1.5 months ago. It worked fine until a few days ago. Another interesting thing I just noted: When I nullroute the external IP (by removing it from the network adapter), the CPU usage goes to 0%. When I add it back, it slowly raises back up to what it was before (~78 or 100%).
I am not expert in network configuration and I read from wiki what nullroute means but cannot see how that will help.
I will continue my search for solving this problem and if I find a solution I will post it.
If anyone working with apache mina has a solutions for this problem please help me.