How correctly close SocketChannel in Java NIO? - java

I have a simple non-blocking server with main loop:
try {
while (selector.select() > -1) {
// Wait for an event one of the registered channels
// Iterate over the set of keys for which events are available
Iterator selectedKeys = selector.selectedKeys().iterator();
while (selectedKeys.hasNext()) {
SelectionKey key = (SelectionKey) selectedKeys.next();
selectedKeys.remove();
try {
if (!key.isValid()) {
continue;
}
if (key.isConnectable()) {
connect(key);
}
// Check what event is available and deal with it
if (key.isAcceptable()) {
accept(key);
}
if (key.isReadable()) {
read(key);
}
if (key.isWritable()) {
write(key);
}
} catch (Exception e) {
e.printStackTrace();
close(key);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
In read/write section I check if there is something to read/write if not - then I try to close channel:
if (channel.read(attachment.buffer) < 1)
close(key);
Close method:
private void close(SelectionKey key) throws IOException {
key.cancel();
key.channel().close();
}
But during processing this code I get exception in main loop (it is catched but I supposed something wrong) I get this stacktrace:
java.nio.channels.CancelledKeyException
at sun.nio.ch.SelectionKeyImpl.ensureValid(Unknown Source)
at sun.nio.ch.SelectionKeyImpl.readyOps(Unknown Source)
at java.nio.channels.SelectionKey.isWritable(Unknown Source)
So it fails on main loop when enter write section, close channel and came back to main loop in 'writable' if section and fails with such exception. Any suggestions?

The error is very simple.
if (!key.isValid()) {
continue;
}
if (key.isConnectable()) {
connect(key);
}
// Check what event is available and deal with it
if (key.isAcceptable()) {
accept(key);
}
if (key.isReadable()) {
read(key);
}
if (key.isWritable()) {
write(key);
}
Your read method is the one which cancels the SelectionKey. However, after returning from read, you again test the key for whether the channel is writable -- potentially after just cancelling that very same key! Your initial check cannot help here.
One solution would be to check for whether the key is valid wherever it might've just been cancelled:
...
if (key.isValid() && key.isWritable()) {
write(key);
}
...
Alternatively, you could also try only registering one interest at a time as you need to on any particular channel, and thus all readiness events are mutually exclusive:
if (!key.isValid()) {
continue;
}
if (key.isConnectable()) {
connect(key);
} else if (key.isAcceptable()) {
accept(key);
} else if (key.isReadable()) {
read(key);
} else if (key.isWritable()) {
write(key);
}
This might be beneficial in situations; as generally a channel will almost always be write-ready, keeping an interest in write-readiness along side read-readiness might keep the Selector loop spinning, which is more than likely not desirable. For the most part, generally register interest in write-readiness only when the underlying socket output buffer is full.
As a side note, know that SocketChannel.read can return a value < 1 without it being an error.
A read operation might not fill the buffer, and in fact it might not read any bytes at all. Whether or not it does so depends upon the nature and state of the channel. A socket channel in non-blocking mode, for example, cannot read any more bytes than are immediately available from the socket's input buffer;
Additionally, Selector.select does not state anything about returning < -1 to indicate it is closed.
Returns: The number of keys, possibly zero, whose ready-operation sets were updated

Related

SocketChannel high CPU load on read

I have SocketChannel configured for read only SelectionKey.OP_CONNECT | SelectionKey.OP_READ
Profiler shows runChannel is the most CPU consuming method and actually it is reasonable because it's infinite loop which calls method selector.select() all the time, but on the other hand I have dozens of such connections and it kills CPU.
Is there a possibility to decrease CPU load and in the same time do not miss any incoming message?
public void runChannel() {
while (session.isConnectionAlive()) {
try {
// Wait for an event
int num = selector.select();
// If you don't have any activity, loop around and wait
// again.
if (num == 0) {
continue;
}
} catch (IOException e) {
log.error("Selector error: {}", e.toString());
log.debug("Stacktrace: ", e);
session.closeConnection();
break;
}
handleSelectorkeys(selector.selectedKeys());
}
}
Unsunscribe from OP_CONNECT - select() won't block if you're subscribed to OP_CONNECT and connected.

Using 1 SocketChannel for 2-way "real-time communictation"

I'm receiving a continuous stream of data that I'm saving to a ByteBuffer.
Sometimes I need to write to the channel, however, it's important not to lose any data. Is it possible to use the selector to solve this issue?
If I'm constantly checking the selector for the channel state, it always says that the channel is currently reading and it's like there is no opportunity to perform writing. I can't use multiple connections because the server doesn't support it.
this.socketChannel = SocketChannel.open();
this.socketChannel.configureBlocking(false);
this.socketChannel.connect(new InetSocketAddress(IP, this.port));
try {
this.selector = Selector.open();
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
SelectionKey selectionKey = this.socketChannel.register(selector, interestSet);
while (selector.select() > -1) {
// Wait for an event one of the registered channels
// Iterate over the set of keys for which events are available
Iterator selectedKeys = selector.selectedKeys().iterator();
while (selectedKeys.hasNext()) {
SelectionKey key = (SelectionKey) selectedKeys.next();
selectedKeys.remove();
try {
if (!key.isValid()) {
continue;
} else if (key.isReadable()) {
System.out.println("readable");
} else if (key.isWritable()) {
System.out.println("writable");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
Edit:
Sorry I didn't add more info. This is an important bit of my code. It always prints "readable" to the console and I was hoping that isWritable block also gets executed.
Thanks in advance, Honza
You are using else if operator so if your key is readable checking for if it is writeable will not be performed, but it doesn't mean that the channel is not writeable.
Actually it could be readable and writeable in the same time. But in your program if it is readable you just don't check for writeable.
replace else-if with if and see the result.

NIO thread pool per event

I am using a java NIO based which accept connections from clients(configured non blocking) and only reads data sent by clients. Clients once connected will stick to server for a long time, so i used a single thread for "selector.select" and "Accept", but the clients connected will send messages every 15 sec and number of clients are 5000, each message is of size 150 Bytes.
Instead of creating a new thread for each read from clients i decided to have a thread pool of 100 threads, but server is not able to read data from all clients it simply hangs. When a new thread is created each time it is able to read data from all clients.
Here is my ReadEvent thread
class ReadEvent implements Runnable {
private static Logger logger = Logger.getLogger(ReadEvent.class.getName());
private SelectionKey key;
/**
* Constructor to initialize the thread with the key having read event
* pending.
*
* #param key
* SelectionKey having read event.
**/
public ReadEvent(SelectionKey key) {
this.key = key;
}
/**
* Method to read the data from the key.
**/
#Override
public void run() {
SocketChannel socketChannel = (SocketChannel) key.channel();
synchronized (socketChannel) {
if (socketChannel.isOpen()) {
try {
ByteBuffer readBuffer = ByteBuffer.allocate(150);
int numRead = 0;
try {
/* ".read" is nonblocking */
numRead = socketChannel.read(readBuffer);
/*
* Some other IO error on reading socketChannel.
*/
}
catch (IOException e) {
logger.debug(
"[run] Connection abruptly terminated from client",
e);
key.channel().close();
return;
}
if (numRead == -1) {// socket closed cleanly
key.channel().close();
return;
}
String data = null;
data = new String(readBuffer.array(),
Charset.forName("ASCII"));
/* Send the read data to the DataDispatcher Actor */
Main.getDataDispatcher().tell(data, ActorRef.noSender());
}
catch (IOException e) {
logger.debug("[run] ", e);
return;
}
}
else {// socketChannel is closed
try {
key.channel().close();// Sanitary close operation
return;
}
catch (IOException e) {
}
}
}
}
}
I can't figure out the overload on thread pool, any suggestions on implementation of ReadThread will help me.
UPDATE 1 : java.lang.OutOfMemoryError on fixed thread pool
Snippet of calling read event :
Thread per read:
try {
if (!key.isValid()) {
continue;
}
if (key.isAcceptable()) {
this.accept(key);
}
else if (key.isReadable()) {
new Thread(new ReadEvent(key)).start();
}
}
catch (CancelledKeyException e) {// key has been canceled
}
The above snippet works fine for few thousands of clients.
Using Thread pool
ExecutorService executor = Executors.newFixedThreadPool(100);
try {
if (!key.isValid()) {
continue;
}
if (key.isAcceptable()) {
this.accept(key);
}
else if (key.isReadable()) {
executor.execute(new ReadEvent(key));
}
}
catch (CancelledKeyException e) {// key has been canceled
}
The above snippet doesn't serves all clients and found the heap size is increasing gradually and most(almost 100%) of the CPU is used for GC and finally got java.lang.OutOfMemoryError: GC overhead limit exceeded exception
6.7 years later but hey I had a similar issue. The problem is until the socket is actually read [socketChannel.read(...)] the operation is deemed as valid.
By submitting the SelectionKey to the thread pool, remember that the thread will execute independently from the selecting thread meaning the operation is still ready as per the selecting thread and will keep submitting the operation to your thread pool until one of the submitted threads actually reads [socketChannel.read(...)] the channel and the operation is deemed as invalid and removed from the ready set.
Solution as mentioned by others already is read the bytes within the selecting thread and then pass the channel and the read contents into the thread pool for post processing.
case SelectionKey.OP_READ: {
SocketChannel channel = (SocketChannel) key.channel();
try {
ByteBuffer buffer = ByteBuffer.allocate(1024 * 8);
int read = channel.read(buffer);// read in selecting thread
if (read <= -1) {
key.cancel();//Signals the channel is closed
} else {
threadPool.execute(() -> {
//pass the channel & contents into the thread pool
});
}
} catch (IOException e) {
e.printStackTrace();
key.cancel();
}
}
break;

How destroy SelectionKey attachment? attach(null) doesn't work

My java application has memory leaks - when my resources clearing code is executing task manager shows that memory usage wasn't changed. My code
while (isRunning) {
try
{
selector.select();
long sum=0;
Set keys = selector.selectedKeys();
Iterator it = keys.iterator();
while(it.hasNext())
{
SelectionKey key = (SelectionKey)it.next();
if (key.isReadable())
{
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer bb;
if(key.attachment()==null)
{
bb = ByteBuffer.allocate(1024*1024);
key.attach(bb);
}
else
{
bb = (ByteBuffer)key.attachment();
bb.clear();
}
int x = sc.read(bb);
System.out.println(x +" bytes were read");
if(x==-1)
{
key.attach(null); //doesn't work
sc.close();
//bb = null; // also doesn't work
}
}
}
keys.clear();
}
catch (Exception ex)
{
ex.printStackTrace(new PrintStream(System.out));
}
finally
{
//stopServer();
}
}
Testing logic - I wrote simple TCP client java programm sending 100 messages to server. I intentionally allocated large buffer - 1MB for each connection. When client finishes his job int x = sc.read(bb); returns -1 and the following code is executed:
if(x==-1)
{
key.attach(null); //doesn't work
sc.close();
//bb = null; // also doesn't work
}
I checked it with debug output, this code was really executed but task manager still showes large memory usage. where is the problem?
Certainly key.attach(null) works. If it didn't, attaching a non-null object wouldn't work either. Same code.
But, in any case, closing the SocketChannel cancels the key, which removes it from all key sets of all Selectors it was registered with, so you will never see the key again anyway, so it becomes eligible for GC, and so does the attachment, regardless of whether you call key.attach(null) or not, which is therefore redundant. Either you have another reference to your attachment somewhere else, or your memory usage problem is elsewhere.

java.nio.channels.ClosedChannelException

How can i solve this problem. I got following error:
java.nio.channels.ClosedChannelException
This is coding:
public void run() {
try {
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(512);
int i1 = socketChannel.read(buffer);
if (buffer.limit() == 0 || i1 == -1) {
Socket s = null;
try {
s = socketChannel.socket();
s.close();
key.cancel();
} catch (IOException ie) {
if (UnitDataServer.isLog) {
log.error("Error closing socket " + s + ": " + ie);
}
}
} else {
buffer.flip();
if (UnitDataServer.isLog) {
log.info(" Recvd Message from Unit : " + buffer.array());
}
byte byteArray[] = buffer.array();
log.info("Byte Array length :" + byteArray.length);
hexString = new StringBuffer();
for (int i = 0; i < i1 /* byteArray.length */; i++) {
String hex = Integer.toHexString(0xFF & byteArray[i]);
if (hex.length() == 1) {
// could use a for loop, but we're only dealing with a
// single byte
hexString.append('0');
}
hexString.append(hex);
}
hexString.trimToSize();
log.info("Hex String :" + hexString);
Communicator.dataReceive(new DataReceive(
socketChannel, hexString.toString(), dst));
}
} catch (Exception e) {
if (UnitDataServer.isLog) {
// log.error(e);
}
try {
socketChannel.socket().close();
key.cancel();
} catch (IOException ex) {
if (UnitDataServer.isLog) {
log.error(ex);
}
}
}
}
You have closed the channel and are still trying to use it.
There are several issues with your code.
First, your test for EOS is faulty. Remove the limit() == 0 test. That doesn't indicate EOS, it just indicates a zero length read, which can happen in non-blocking mode at any time. It doesn't mean the peer has closed his end of the connection, and it doesn't mean you should close your end.
Second, closing a channel closes the socket as well. You should close the channel only, not the socket.
Third, closing a channel cancels the key. You don't need to follow every close with a cancel.
You may also have failed to check whether a ready key is valid in the select loop before using it, e.g. for reading.
I continue to be amazed, and amused, and bemused, by the claim elsewhere in this thread that 'source code is untrue' under some circumstances.
You need to fix/secure code that is throwing this exception. ClosedChannelException is ...
... thrown when an
attempt is made to invoke or complete
an I/O operation upon channel that is
closed, or at least closed to that
operation. That this exception is
thrown does not necessarily imply that
the channel is completely closed. A
socket channel whose write half has
been shut down, for example, may still
be open for reading
(as described in Java 6 API)
But really, you would need to provide us code snipped and stack trace in order to get more detailed help.

Categories

Resources