Background
We are developing a Java service in Heroku with 1 dyno, which is using Memcached cloud.
Issue
Meanwhile we were developing and testing it, It was working fine. However, when we decided to test it in a real environment, It started to return the following error:
java.lang.OutOfMemoryError: unable to create new native thread
java.lang.Thread.start0(Native Method)
java.lang.Thread.start(Thread.java:717)
net.spy.memcached.MemcachedConnection.<init>(MemcachedConnection.java:306) net.spy.memcached.DefaultConnectionFactory.createConnection(DefaultConnectionFactory.java:209)
net.spy.memcached.MemcachedClient.<init>(MemcachedClient.java:209)
Memcached.<init>(Memcached.java:34)
Main.lambda$main$1(Main.java:101)
spark.SparkBase$1.handle(SparkBase.java:311)
spark.webserver.MatcherFilter.doFilter(MatcherFilter.java:159)
spark.webserver.JettyHandler.doHandle(JettyHandler.java:60)
org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:179)
org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:136)
org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)
org.eclipse.jetty.server.Server.handle(Server.java:451)
org.eclipse.jetty.server.HttpChannel.run(HttpChannel.java:252)
org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:266)
org.eclipse.jetty.io.AbstractConnection$ReadCallback.run(AbstractConnection.java:240)
org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:596)
org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:527)
java.lang.Thread.run(Thread.java:748)
We didn't have so much clients to arrive at 256 process or threads (limit of 1 dyno)
Workaround
We don't know it yet
Code
<!--Main-->
//...
try {
memcached = new Memcached ();
}
catch (Exception e) {
memcached = null;
}
//...
<!--Memcached-->
public class Memcached {
private MemcachedClient memcachedClient;
private boolean connected;
public Memcached () throws Exception {
setConnected(true);
try {
AuthDescriptor ad = new AuthDescriptor(new String[] { "PLAIN" },
new PlainCallbackHandler(System.getenv("MEMCACHEDCLOUD_USERNAME"),
System.getenv("MEMCACHEDCLOUD_PASSWORD")
)
);
setMemcachedClient(new MemcachedClient(
new ConnectionFactoryBuilder()
.setDaemon(true)
.setFailureMode(FailureMode.Retry)
.setProtocol(ConnectionFactoryBuilder.Protocol.BINARY) //this is the line 34
.setAuthDescriptor(ad).build(),
AddrUtil.getAddresses(System.getenv("MEMCACHEDCLOUD_SERVERS"))
));
} catch (Exception ex) {
// the Memcached client could not be initialized
setConnected(false);
throw new Exception ("{\"ErrorCode\":20,\"Portal\":\"Memcached\",\"ResponseCode\":\"\",\"Message\":\""
+ ex.getMessage() + "\"}");
}
}
public String get (String service, String fromCurrency, String toCurrency) {
if (!isConnected()) {
return null;
}
try {
return (String) getMemcachedClient().get(service + ";" + fromCurrency + ";" + toCurrency);
}
catch (Exception e) {
return null;
}
}
public boolean set (String service, String fromCurrency, String toCurrency, String value) {
if (!isConnected()) {
return false;
}
try {
double hour = Double.parseDouble(System.getenv("TIME_CACHE_HOUR"));
getMemcachedClient().set(service + ";" + fromCurrency + ";" + toCurrency, (int)(60 * 60 * hour), value);
return true;
}catch (Exception e) {
return false;
}
}
public boolean isConnected () {
return this.connected;
}
private void setConnected (boolean connected) {
this.connected = connected;
}
private MemcachedClient getMemcachedClient () {
return this.memcachedClient;
}
private void setMemcachedClient (MemcachedClient memcachedClient) {
this.memcachedClient = memcachedClient;
}
Anyone knows how can we fix this error?
EDIT: I have read the post Java: Unable to create new native thread, but It isn't our problem. We checked the attributes that they said there, but we have good values. However, I could see that everytime that memcached is initialized, It create a new thread, but that thread isn't deleted when the service stop. There's the problem.
Related
I'm using redis with the help of jedis client. Attaching the code snippet for key value set/get here. Here I'm expecting my jedisPool to get initialised only once but it is getting initialised multiple times. Not sure where I'm going wrong. Scratching my head for several days with it. I have no clues why it does multiple initialisation.
//$Id$
package experiments.with.truth;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class RedisClientUtil {
private static JedisPool pool; //I persume the deafult value initialised in my static variable would be null
static int maxActiveConnections = 8;
static int maxWaitInMillis = 2000;
static String host = "127.0.0.1";
static int port = 6379;
static int REDIS_DB = 1;
public static void initRedisClient() throws Exception {
try {
Class classObj = Class.forName("redis.clients.jedis.JedisPool");
if (classObj != null && pool == null) {
JedisPoolConfig jedisConfig = new JedisPoolConfig();
jedisConfig.setMaxTotal(maxActiveConnections);
jedisConfig.setMaxWaitMillis(maxWaitInMillis);
pool = new JedisPool(jedisConfig, host, port);
System.out.println("Pool initialised successfully !");
}
} catch(ClassNotFoundException ex) {
System.out.println("Couldn't initialize redis due to unavailability of jedis jar in your machine. Exception : " + ex);
}
}
public Jedis getJedisConnection() {
if(pool == null) {
initRedisClient();
}
return pool.getResource();
}
private static void returnJedis(Jedis jedis) {
try {
pool.returnResource(jedis);
} catch(Exception ex) {
ex.printStackTrace();
}
}
public static String getValue(String key) throws Exception{
Jedis jedisCon = null;
try {
jedisCon = getJedisConnection();
jedisCon.select(REDIS_DB);
String val = jedisCon.get(key);
return val;
} catch (Exception e) {
e.printStackTrace();
} finally {
if (jedisCon != null) {
returnJedis(jedisCon);
}
}
return null;
}
public void addValueToRedis(String key, String value) {
Jedis jedisCon = null;
try {
jedisCon = getJedisConnection();
jedisCon.select(REDIS_DB);
jedisCon.set(key, value);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (jedisCon != null) {
returnJedis(jedisCon);
}
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println("Value : " + getValue("a"));
System.out.println("Value : " + getValue("b"));
System.out.println("Value : " + getValue("c"));
}
}
I could see this debug log Pool initialised successfully multiple times when my program runs. Can someone help me find the loophole in this? Or how could I make this better (or make to behave it as expected) by initialising only once throughout the entire program.
Looks like a basic multithreading case. Your app asks for 5 connections in a short time. All of them see that pool==null and proceed initializing it.
Easy solution: public static synchronized void initRedisClient() throws Exception {
update and private static volatile JedisPool pool; otherwise you may get null pointer exception.
For more complex and performant solutions search 'efficient lazy singletor in java', which will most probably lead you to Enum solution.
i am creating a class(CheckCon.java) that implements callable interface which basically returns the no. of devices Connected to the network. the problem is that i donot know how to execute it correctly because the results returned are really slow as compared to traditional multithreading. and i need to return values to a class in (NetScan.java). Kindly Help me execute it properly.
code for CheckCon.java (callable implementing class):
public class CheckCon implements Callable<Object>{
int startindex,endindex;
ArrayList<Object> list;
byte[] ip;
public CheckCon(int startindex, int endindex, byte[] ip) {
this.startindex = startindex;
this.endindex = endindex;
this.ip = ip;
list = new ArrayList<>();
}
#Override
public ArrayList<Object> call() throws Exception {
for(int i =startindex;i<endindex;i++){
try {
ip[3] = (byte)i;
InetAddress address = InetAddress.getByAddress(ip);
if (address.isReachable(1000))
{
System.out.println("Name is......"+address.getHostName()+"\tIP is......."+address.getHostAddress());
}
else if (!address.getHostAddress().equals(address.getHostName()))
{
String host = address.getCanonicalHostName();
String ipaddress =address.toString().replace(host+"/", "");
Object[] data = {host,ipaddress};
list.add(data);
System.out.println("Name is......"+address.getHostName()+"\tIP is......."+address.getHostAddress());
}
else
{
System.out.println("nothing");
// the host address and host name are equal, meaning the host name could not be resolved
} } catch (UnknownHostException ex) {
Logger.getLogger(NetScan.class.getName()).log(Level.SEVERE, null, ex);
} catch (IOException ex) {
Logger.getLogger(NetScan.class.getName()).log(Level.SEVERE, null, ex);
}
}
return list;
}
}
and the class Calling NetScan.java
private void getDataForTable() throws InterruptedException, ExecutionException {
ExecutorService executor =Executors.newCachedThreadPool();
for(int i =0; i<26 ; i++){
if(s == 250){
Future<Object> f =executor.submit(new CheckCon(s,s+5,ip));
list.add(f);
break;
}else{
Future<Object> f =executor.submit(new CheckCon(s,s+10,ip));
list.add(f);
s= s+10;
}
}
dm = new DefaultTableModel(ColumnName,0){
#Override
public boolean isCellEditable(int row, int column) {
return false;
}
};
for(Future<Object> f : list){
dm.addRow((Object[]) f.get());
}}
I am creating 25 threads checking 10 IP's in each thread.
I removed the CheckCon.java and edited the NetScan to get what i Want thanks to #amit-bera . i Created a thread and asked the completablefuture to runasync with executor defining the no.of threads i require.
ExecutorService e =Executors.newFixedThreadPool(25);
for(int i =0;i<255;i++){
final int j =i;
f = CompletableFuture.runAsync(new Runnable() {
#Override
public void run() {
try {
ip[3] = (byte)j;
InetAddress address = InetAddress.getByAddress(ip);
if (address.isReachable(1000))
{
System.out.println("Name is......"+address.getHostName()+"\tIP is......."+address.getHostAddress()+j);
}
else if (!address.getHostAddress().equals(address.getHostName()))
{
String host = address.getCanonicalHostName();
String ipaddress =address.toString().replace(host+"/", "");
Object[] data = {host,ipaddress};
dm.addRow(data);
System.out.println("Name is......"+address.getHostName()+"\tIP is......."+address.getHostAddress()+j);
}
else
{
System.out.println("nothing"+j);
// the host address and host name are equal, meaning the host name could not be resolved
} } catch (UnknownHostException ex) {
Logger.getLogger(NetScan.class.getName()).log(Level.SEVERE, null, ex);
} catch (IOException ex) {
Logger.getLogger(NetScan.class.getName()).log(Level.SEVERE, null, ex);
}
}
},e);
}
added this content to a method.
As per my obervationa please used singolton java pettern.example i am giving below.singolton alwaus retun instance so you save request response time on thread.
Thread is light-wight process but still initilised thread at whenever need.please make sure that thread work based on your server configrationa.
if you are using linux system
used TOP command to usage of your system perfomance and process execution on threadiwse.
i try and its work fine on my system.
class Singleton
{
private static Singleton single_instance = null;
public String s;
private Singleton()
{
s = "Hello I am a string part of Singleton class";
}
public static Singleton getInstance()
{
if (single_instance == null)
single_instance = new Singleton();
return single_instance;
}
}
I'm using protocol buffer to send stream of data to Apache Flink.
I have two classes. one is Producer and one is Consumer.
Producer is a java thread class which reads the data from socket and Protobuf deserializes it and then I store it in my BlockingQueue
Consumer is a class which implements SourceFunction in Flink.
I tested this program with using:
DataStream<Event.MyEvent> stream = env.fromCollection(queue);
instead of custom source and it works fine.
But when I try to use a SourceFunction class it throws this exception:
Caused by: java.lang.RuntimeException: Unable to find proto buffer class
at com.google.protobuf.GeneratedMessageLite$SerializedForm.readResolve(GeneratedMessageLite.java:775)
...
Caused by: java.lang.ClassNotFoundException: event.Event$MyEvent
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
...
And in another attempt I mixed both classed into one (the class which implements SourceFunction). I get data from socket and deserialize it with protobuf and store it in BlockingQueue and then I read from BlockingQueue right after that. My code works fine with this approach too.
But I want to use two separate classes (multi-threading) but it throws that exception.
I'm trying to solve it in last 2 days and also did lots of searching but no luck.
Any help would be apperciated.
Producer:
public class Producer implements Runnable {
Boolean running = true;
Socket socket = null, bufferSocket = null;
PrintStream ps = null;
BlockingQueue<Event.MyEvent> queue;
final int port;
public Producer(BlockingQueue<Event.MyEvent> queue, int port){
this.port = port;
this.queue = queue;
}
#Override
public void run() {
try {
socket = new Socket("127.0.0.1", port);
bufferSocket = new Socket(InetAddress.getLocalHost(), 6060);
ps = new PrintStream(bufferSocket.getOutputStream());
while (running) {
queue.put(Event.MyEvent.parseDelimitedFrom(socket.getInputStream()));
ps.println("Items in Queue: " + queue.size());
}
}catch (Exception e){
e.printStackTrace();
}
}
}
Consumer:
public class Consumer implements SourceFunction<Event.MyEvent> {
Boolean running = true;
BlockingQueue<Event.MyEvent> queue;
Event.MyEvent event;
public Consumer(BlockingQueue<Event.MyEvent> queue){
this.queue = queue;
}
#Override
public void run(SourceContext<Event.MyEvent> sourceContext) {
try {
while (running) {
event = queue.take();
sourceContext.collect(event);
}
}catch (Exception e){
e.printStackTrace();
}
}
#Override
public void cancel() {
running = false;
}
}
Event.MyEvent is my protobuf class. I'm using version 2.6.1 and I compiled classes with v2.6.1 . I double checked the versions to be sure it's not the problem.
The Producer class is working fine.
I tested this with both Flink v1.1.3 and v1.1.4.
I'm running it in local mode.
EDIT: Answer was included in question, posted it separately and removed it here.
UPDATE 12/28/2016
...
But I'm still curious. What is causing this error? Is it a bug in Flink or am I doing something wrong?
...
The asker already found a way to make this working. I have extracted the relevant part from the question. Note that the reason why it happened remains unexplained.
I did not use quote syntax as it is a lot of text, but the below was shared by the asker:
So finally I got it to work. I created my BlockingQueue object inside SourceFunction (Consumer), and called Producer class from inside the SourceFunction class (Consumer) instead of making BlockingQueue and calling Producer class in main method of the program. and it now works!
Here's my full working code in Flink:
public class Main {
public static void main(String[] args) throws Exception {
final int port, buffer;
//final String ip;
try {
final ParameterTool params = ParameterTool.fromArgs(args);
port = params.getInt("p");
buffer = params.getInt("b");
} catch (Exception e) {
System.err.println("No port number and/or buffer size specified.");
return;
}
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
DataStream<Event.MyEvent> stream = env.addSource(new Consumer(port, buffer));
//DataStream<Event.MyEvent> stream = env.fromCollection(queue);
Pattern<Event.MyEvent, ?> crashedPattern = Pattern.<Event.MyEvent>begin("start")
.where(new FilterFunction<Event.MyEvent>() {
#Override
public boolean filter(Event.MyEvent myEvent) throws Exception {
return (myEvent.getItems().getValue() >= 120);
}
})
.<Event.MyEvent>followedBy("next").where(new FilterFunction<Event.MyEvent>() {
#Override
public boolean filter(Event.MyEvent myEvent) throws Exception {
return (myEvent.getItems().getValue() <= 10);
}
})
.within(Time.seconds(3));
PatternStream<Event.MyEvent> crashed = CEP.pattern(stream.keyBy(new KeySelector<Event.MyEvent, String>() {
#Override
public String getKey(Event.MyEvent myEvent) throws Exception {
return myEvent.getEventType();
}
}), crashedPattern);
DataStream<String> alarm = crashed.select(new PatternSelectFunction<Event.MyEvent, String>() {
#Override
public String select(Map<String, Event.MyEvent> pattern) throws Exception {
Event.MyEvent start = pattern.get("start");
Event.MyEvent next = pattern.get("next");
return start.getEventType() + " | Speed from " + start.getItems().getValue() + " to " + next.getItems().getValue() + " in 3 seconds\n";
}
});
DataStream<String> rate = alarm.windowAll(TumblingProcessingTimeWindows.of(Time.seconds(1)))
.apply(new AllWindowFunction<String, String, TimeWindow>() {
#Override
public void apply(TimeWindow timeWindow, Iterable<String> iterable, Collector<String> collector) throws Exception {
int sum = 0;
for (String s: iterable) {
sum ++;
}
collector.collect ("CEP Output Rate: " + sum + "\n");
}
});
rate.writeToSocket(InetAddress.getLocalHost().getHostName(), 7070, new SimpleStringSchema());
env.execute("Flink Taxi Crash Streaming");
}
private static class Producer implements Runnable {
Boolean running = true;
Socket socket = null, bufferSocket = null;
PrintStream ps = null;
BlockingQueue<Event.MyEvent> queue;
final int port;
Producer(BlockingQueue<Event.MyEvent> queue, int port){
this.port = port;
this.queue = queue;
}
#Override
public void run() {
try {
socket = new Socket("127.0.0.1", port);
bufferSocket = new Socket(InetAddress.getLocalHost(), 6060);
ps = new PrintStream(bufferSocket.getOutputStream());
while (running) {
queue.put(Event.MyEvent.parseDelimitedFrom(socket.getInputStream()));
ps.println("Items in Queue: " + queue.size());
}
}catch (Exception e){
e.printStackTrace();
}
}
}
private static class Consumer implements SourceFunction<Event.MyEvent> {
Boolean running = true;
final int port;
BlockingQueue<Event.MyEvent> queue;
Consumer(int port, int buffer){
queue = new ArrayBlockingQueue<>(buffer);
this.port = port;
}
#Override
public void run(SourceContext<Event.MyEvent> sourceContext) {
try {
new Thread(new Producer(queue, port)).start();
while (running) {
sourceContext.collect(queue.take());
}
}catch (Exception e){
e.printStackTrace();
}
}
#Override
public void cancel() {
running = false;
}
}
I use the method below to keep my connection alive if it closes. I noticed that when it had run for a week and there was hundreth socket going on, the RAM usage had increased by 700 MB. Am I doing something wrong?
If it runs for a week without needing to initialize so many new sockets, the RAM usage is smaller.
import ws.JettyWebSocketClient;
public class connectionKeeper extends Thread {
public void run(){
lib.print(">>> Connection thread running");
do{
lib.writeLog("Opening new websocket connection");
try{
GVL.socketCounter++;
GVL.ws = new JettyWebSocketClient();
GVL.ws.run();
}catch(Exception e){
e.printStackTrace();
lib.error("Error: connectionKeeper: " + e.toString());
}
// if we are here, we got an error or the socket has executed the run() -method to end
lib.sleep(2000);
}while(true);
}
}
-
public class JettyWebSocketClient {
private boolean connected=false;
private WebSocketClient client=new WebSocketClient();
public void run() {
MyWebSocket socket = new MyWebSocket();
ClientUpgradeRequest request;
URI destinationUri = null;
try {
destinationUri = new URI("wss://example.com:3000/ws");
} catch (URISyntaxException e1) {
e1.printStackTrace();
lib.error("Jetty.runA(): " + e1.toString());
}
SslContextFactory sslContextFactory = new SslContextFactory();
Resource keyStoreResource = Resource.newResource(this.getClass().getResource("/cert.jks"));
sslContextFactory.setKeyStoreResource(keyStoreResource);
sslContextFactory.setKeyStorePassword("pass");
client=new WebSocketClient(sslContextFactory);
connected=false;
try {
client.start();
request = new ClientUpgradeRequest();
System.out.println("SOCKET" + GVL.socketCounter+ ":\tConnecting to " + destinationUri.toString());
client.connect(socket, destinationUri, request);
do{
socket.awaitClose(10);
}while(connected);
} catch (Throwable t) {
t.printStackTrace();
lib.error("Jetty.runB(): " + t.toString());
}
}
public boolean send(JSONObject message){
String msg=message.toString();
System.out.println("SOCKET" + GVL.socketCounter+ ":\tSending msg:\t" + msg);
for(Session s: client.getOpenSessions()) {
if (s.isOpen()) {
try {
s.getRemote().sendString(msg);
return true;
} catch (IOException e) {
e.printStackTrace();
lib.error(e.toString());
}
}
}
return false;
}
public String status(){
return this.client.getState();
}
public boolean isConnected() {
return connected;
}
public void disconnect(){
lib.print("Disconnecting...");
setConnected(false);
try {
try{
client.stop();
} catch (InterruptedException e) {
// sometimes it gets here, sometimes not.. hmm
}
} catch(Exception a){
lib.error("Jetty.disconnect():\t" + a.toString());
}
lib.print("Disconnected...");
}
public void setConnected(boolean newval) {
connected=newval;
}
#WebSocket
public class MyWebSocket {
private final CountDownLatch closeLatch = new CountDownLatch(1);
#OnWebSocketConnect
public void onConnect(Session session) {
System.out.println("SOCKET" + GVL.socketCounter+ ":\tCONNECTED");
setConnected(true);
}
#OnWebSocketMessage
public void onMessage(String message) {
messaging.handleMsg(message); // this method uses received data to calculate some things
}
public void onError(int statusCode, String reason){
lib.error("SOCKET" + GVL.socketCounter+ ":\tError:\t" + reason + " / Code: " + statusCode);
}
#OnWebSocketClose
public void onClose(int statusCode, String reason) {
lib.error("SOCKET" + GVL.socketCounter+ ":\tClosed:\t" + reason + " / Code: " + statusCode);
setConnected(false);
}
public void awaitClose(int n) {
try {
this.closeLatch.await(n, TimeUnit.SECONDS);
} catch (Exception e) {
e.printStackTrace();
lib.error("SOCKET" + GVL.socketCounter+ ": Jetty.awaitClose():" + e.toString());
disconnect(); // useless?
}
}
}
}
Don't keep recreating the WebSocketClient object, just create 1 of those and reconnect when you want to.
Think of the WebSocketClient as the browser.
Each client.start() as you starting up that browser.
Each client.connect() as you opening a tab to a new web page.
The expensive operation, starting the browser, you are doing over and over and over again.
The cheap operation, connecting to a website, opening new tabs, closing others, you are not doing.
Instead, you are going "dang, i need to reconnect, let me stop the browser, and restart it to reconnect"
Disclaimer- I am not a Java programmer. Odds are I'll need to do my homework on any advice given, but I will gladly do so :)
That said, I wrote a complete database-backed socket server which is working just fine for my small tests, and now I'm getting ready for initial release. Since I do not know Java/Netty/BoneCP well- I have no idea if I made a fundamental mistake somewhere that will hurt my server before it even gets out the door.
For example, I have no idea what an executor group does exactly and what number I should use. Whether it's okay to implement BoneCP as a singleton, is it really necessary to have all those try/catch's for each database query? etc.
I've tried to reduce my entire server to a basic example which operates the same way as the real thing (I stripped this all in text, did not test in java itself, so excuse any syntax errors due to that).
The basic idea is that clients can connect, exchange messages with the server, disconnect other clients, and stay connected indefinitely until they choose or are forced to disconnect. (the client will send ping messages every minute to keep the connection alive)
The only major difference, besides untesting this example, is how the clientID is set (safely assume it is truly unique per connected client) and that there is some more business logic in checking of values etc.
Bottom line- can anything be done to improve this so it can handle the most concurrent users possible? Thanks!
//MAIN
public class MainServer {
public static void main(String[] args) {
EdgeController edgeController = new EdgeController();
edgeController.connect();
}
}
//EdgeController
public class EdgeController {
public void connect() throws Exception {
ServerBootstrap b = new ServerBootstrap();
ChannelFuture f;
try {
b.group(new NioEventLoopGroup(), new NioEventLoopGroup())
.channel(NioServerSocketChannel.class)
.localAddress(9100)
.childOption(ChannelOption.TCP_NODELAY, true)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new EdgeInitializer(new DefaultEventExecutorGroup(10)));
// Start the server.
f = b.bind().sync();
// Wait until the server socket is closed.
f.channel().closeFuture().sync();
} finally { //Not quite sure how to get here yet... but no matter
// Shut down all event loops to terminate all threads.
b.shutdown();
}
}
}
//EdgeInitializer
public class EdgeInitializer extends ChannelInitializer<SocketChannel> {
private EventExecutorGroup executorGroup;
public EdgeInitializer(EventExecutorGroup _executorGroup) {
executorGroup = _executorGroup;
}
#Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("idleStateHandler", new IdleStateHandler(200,0,0));
pipeline.addLast("idleStateEventHandler", new EdgeIdleHandler());
pipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.nulDelimiter()));
pipeline.addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));
pipeline.addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));
pipeline.addLast(this.executorGroup, "handler", new EdgeHandler());
}
}
//EdgeIdleHandler
public class EdgeIdleHandler extends ChannelHandlerAdapter {
private static final Logger logger = Logger.getLogger( EdgeIdleHandler.class.getName());
#Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception{
if(evt instanceof IdleStateEvent) {
ctx.close();
}
}
private void trace(String msg) {
logger.log(Level.INFO, msg);
}
}
//DBController
public enum DBController {
INSTANCE;
private BoneCP connectionPool = null;
private BoneCPConfig connectionPoolConfig = null;
public boolean setupPool() {
boolean ret = true;
try {
Class.forName("com.mysql.jdbc.Driver");
connectionPoolConfig = new BoneCPConfig();
connectionPoolConfig.setJdbcUrl("jdbc:mysql://" + DB_HOST + ":" + DB_PORT + "/" + DB_NAME);
connectionPoolConfig.setUsername(DB_USER);
connectionPoolConfig.setPassword(DB_PASS);
try {
connectionPool = new BoneCP(connectionPoolConfig);
} catch(SQLException ex) {
ret = false;
}
} catch(ClassNotFoundException ex) {
ret = false;
}
return(ret);
}
public Connection getConnection() {
Connection ret;
try {
ret = connectionPool.getConnection();
} catch(SQLException ex) {
ret = null;
}
return(ret);
}
}
//EdgeHandler
public class EdgeHandler extends ChannelInboundMessageHandlerAdapter<String> {
private final Charset CHARSET_UTF8 = Charset.forName("UTF-8");
private long clientID;
static final ChannelGroup channels = new DefaultChannelGroup();
#Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
Connection dbConnection = null;
Statement statement = null;
ResultSet resultSet = null;
String query;
Boolean okToPlay = false;
//Check if status for ID #1 is true
try {
query = "SELECT `Status` FROM `ServerTable` WHERE `ID` = 1";
dbConnection = DBController.INSTANCE.getConnection();
statement = dbConnection.createStatement();
resultSet = statement.executeQuery(query);
if (resultSet.first()) {
if (resultSet.getInt("Status") > 0) {
okToPlay = true;
}
}
} catch (SQLException ex) {
okToPlay = false;
} finally {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException logOrIgnore) {
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException logOrIgnore) {
}
}
if (dbConnection != null) {
try {
dbConnection.close();
} catch (SQLException logOrIgnore) {
}
}
}
if (okToPlay) {
//clientID = setClientID();
sendCommand(ctx, "HELLO", "WORLD");
} else {
sendErrorAndClose(ctx, "CLOSED");
}
}
#Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
channels.remove(ctx.channel());
}
#Override
public void messageReceived(ChannelHandlerContext ctx, String request) throws Exception {
// Generate and write a response.
String[] segments_whitespace;
String command, command_args;
if (request.length() > 0) {
segments_whitespace = request.split("\\s+");
if (segments_whitespace.length > 1) {
command = segments_whitespace[0];
command_args = segments_whitespace[1];
if (command.length() > 0 && command_args.length() > 0) {
switch (command) {
case "HOWDY": processHowdy(ctx, command_args); break;
default: break;
}
}
}
}
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
TraceUtils.severe("Unexpected exception from downstream - " + cause.toString());
ctx.close();
}
/* */
/* STATES - / CLIENT SETUP */
/* */
private void processHowdy(ChannelHandlerContext ctx, String howdyTo) {
Connection dbConnection = null;
Statement statement = null;
ResultSet resultSet = null;
String replyBack = null;
try {
dbConnection = DBController.INSTANCE.getConnection();
statement = dbConnection.createStatement();
resultSet = statement.executeQuery("SELECT `to` FROM `ServerTable` WHERE `To`='" + howdyTo + "'");
if (resultSet.first()) {
replyBack = "you!";
}
} catch (SQLException ex) {
} finally {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException logOrIgnore) {
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException logOrIgnore) {
}
}
if (dbConnection != null) {
try {
dbConnection.close();
} catch (SQLException logOrIgnore) {
}
}
}
if (replyBack != null) {
sendCommand(ctx, "HOWDY", replyBack);
} else {
sendErrorAndClose(ctx, "ERROR");
}
}
private boolean closePeer(ChannelHandlerContext ctx, long peerClientID) {
boolean success = false;
ChannelFuture future;
for (Channel c : channels) {
if (c != ctx.channel()) {
if (c.pipeline().get(EdgeHandler.class).receiveClose(c, peerClientID)) {
success = true;
break;
}
}
}
return (success);
}
public boolean receiveClose(Channel thisChannel, long remoteClientID) {
ChannelFuture future;
boolean didclose = false;
long thisClientID = (clientID == null ? 0 : clientID);
if (remoteClientID == thisClientID) {
future = thisChannel.write("CLOSED BY PEER" + '\n');
future.addListener(ChannelFutureListener.CLOSE);
didclose = true;
}
return (didclose);
}
private ChannelFuture sendCommand(ChannelHandlerContext ctx, String cmd, String outgoingCommandArgs) {
return (ctx.write(cmd + " " + outgoingCommandArgs + '\n'));
}
private ChannelFuture sendErrorAndClose(ChannelHandlerContext ctx, String error_args) {
ChannelFuture future = sendCommand(ctx, "ERROR", error_args);
future.addListener(ChannelFutureListener.CLOSE);
return (future);
}
}
When a network message arrive at server, it will be decoded and will release a messageReceived event.
If you look at your pipeline, last added thing to pipeline is executor. Because of that executor will receive what has been decoded and will release the messageReceived event.
Executors are processor of events, server will tell which events happening through them. So how executors are being used is an important subject. If there is only one executor and because of that, all clients using this same executor, there will be a queue for usage of this same executor.
When there are many executors, processing time of events will decrease, because there will not be any waiting for free executors.
In your code
new DefaultEventExecutorGroup(10)
means this ServerBootstrap will use only 10 executors at all its lifetime.
While initializing new channels, same executor group being used:
pipeline.addLast(this.executorGroup, "handler", new EdgeHandler());
So each new client channel will use same executor group (10 executor threads).
That is efficient and enough if 10 threads are able to process incoming events properly. But if we can see messages are being decoded/encoded but not handled as events quickly, that means there is need to increase amount of them.
We can increase number of executors from 10 to 100 like that:
new DefaultEventExecutorGroup(100)
So that will process event queue faster if there is enough CPU power.
What should not be done is creating new executor for each new channel:
pipeline.addLast(new DefaultEventExecutorGroup(10), "handler", new EdgeHandler());
Above line is creating a new executor group for each new channel, that will slow down things greatly, for example if there are 3000 clients, there will be 3000 executorgroups(threads). That is removing main advantage of NIO, ability to use with low thread amounts.
Instead of creating 1 executor for each channel, we can create 3000 executors at startup and at least they will not be deleted and created each time a client connects/disconnects.
.childHandler(new EdgeInitializer(new DefaultEventExecutorGroup(3000)));
Above line is more acceptable than creating 1 executor for each client, because all clients are connected to same ExecutorGroup and when a client disconnects Executors still there even if client data is removed.
If we must speak about database requests, some database queries can take long time to being completed, so if there are 10 executorss and there are 10 jobs being processed, 11th job will have to wait until one of others complete. This is a bottleneck if server receiving more than 10 very time consuming database job at the same time. Increasing count of executors will solve bottleneck to some degree.