How do you close a AsynchronousSocketChannel cleanly? - java

My server uses a AsynchronousServerSocketChannel that listens for client connections using a CompletionHandler. When a client connection is accepted, the AsynchronousSocketChannel is read, again using a CompletionHandler to receive the data with no timeout.
So far so good, my client connects, writes data that is read by the server, which is able to respond sending data back to the client via the same socket.
When my client terminates, it calls AsynchronousSocketChannel.close(), to close the socket. When this call is made the server is waiting to read data from the socket.
I had expected the call to AsynchronousSocketChannel.close() on the client to translate into a callback to CompletionHandler.completed with a read length of -1 on the server, indicating the socket had been closed, however the callback is to CompletionHandler.failed with the following exception:
java.io.IOException: The specified network name is no longer available.
at sun.nio.ch.Iocp.translateErrorToIOException(Iocp.java:309)
at sun.nio.ch.Iocp.access$700(Iocp.java:46)
at sun.nio.ch.Iocp$EventHandlerTask.run(Iocp.java:399)
at sun.nio.ch.AsynchronousChannelGroupImpl$1.run(AsynchronousChannelGroupImpl.java:112)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:744)
How should a client close a socket, so that it is not seen as an error on the server?

The documentation on close says that it causes
AsynchronousCloseException or ClosedChannelException on the other side.
To cause completed(-1) the client should call shutdownInput.
However, I would treat AsynchronousCloseException and ClosedChannelException as normal shutdown, along with completed(-1).

Looking at stack traces and the implementation sources, you might notice that the exception is thrown by the internal sun.nio.ch.UnixAsynchronousSocketChannelImpl#finish() method which checks for pending read/write operations. So the only way to avoid this exception is to prevent new asynchronous read() and write() calls at some point, and that should be a part of the application's logic.
I've dealt with this exception quite a lot, and in most cases the root problem was in the completion handler's unconditional "continue listening" calls:
conn.read(src, attachment, new CompletionHandler<Integer, T>() {
#Override
public void completed(Integer result, T attachment) {
// Some business logic here
// Below is the problem - unconditional read() call
conn.read(src, attachment, this);
}
#Override
public void failed(Throwable t, T attachment) {
// Exception handling
}
});
To gracefully close the connection, there should be no unconditional async read/write calls. To achieve that, one might need to send an additional message which would mean that no new async data is expected and it's safe to close the connection. And the correct pseudo-code would look something like this:
conn.read(src, attachment, new CompletionHandler<Integer, T>() {
#Override
public void completed(Integer result, T attachment) {
// Some business logic here
if(continueListening()) {
conn.read(src, attachment, this);
}
}
// ...
});

Related

How to close a channel gracefully before ssl handshake in Netty?

I'm adding a throttling feature in my application, which needs to close a channel before the SSL handshake to mitigate CPU usage when the incoming request rate exceeds the threshold. Now I use Netty SslHandler with server mode to do the handshake. My current implementation is adding a ChannelInboundHandlerAdapter before the SslHandler and rewrite the channelActive method:
#Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
if (!limiter.tryAcquire()) {
ctx.close();
return;
}
ctx.pipeline().remove(this);
ctx.fireChannelActive();
}
In this way the channel can be closed when it becomes active. However, I will receive SslHandshakeCompletionEvent with handshake failure. I read the source code of Netty SslHandler. It will set handshake failure in channelInactive method when the channelInactive event is triggered. So I wonder whether there is a better way to close the channel without firing the handshake failure event, since the handshake process has not started when my throttling close the channel.
You're attacking the problem at the wrong end. The idea is not to close channels that are being accepted too quickly, but to not accept them too quickly, which will be done in the accept handler, not the channel handler.
Your approach is almost right, however, it would be correct to override channelRegistered method instead of channelActive.
#Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
if (isGlobalLimitReached()) {
ctx.close();
} else {
ctx.pipeline().remove(this);
ctx.pipeline().addFirst(sslCtx.newHandler(ctx.alloc()));
ctx.fireChannelRegistered();
}
}
Also, you need to add SslHandler at the beginning of pipeline on the fly and only when your condition is met (no limit reached).
In that case, you'll not get SslHandshakeCompletionEvent. As handshake is started when SslHandler is added to pipeline and
channel.isActive() return true (your case in above code).
channelRegistered is most recent point where you can refuse/close connection.

How to execute code when connection finishes in Apache HttpRequestHandler

I'm using an Apache's HttpRequestHandler to serve data to HTTP clients. I'm generating content (probably a costly process) for clients, on demand.
I want to take care of two cases:
normal consumption, it ends, I want to close resources
client closes connection prematurely, I don't want to keep processing things
I'm using an InputStream (and an InputStreamEntity) that does the process, but I'd like to know if the client closes the resource prematurely (or not) and take actions at the end in both cases.
I've realized that InputStreamEntity.writeTo (which is the method used to send the content to the client) doesn't close the input stream I've declared.
What am I missing?
I've solved this by subclassing InputStreamEntity and making writeTo call close after original behaviour:
public class ClosingInputStreamEntity extends InputStreamEntity {
#Override
public void writeTo(OutputStream os) {
try {
super.writeTo(OutputStream os);
} finally {
// close resources
}
}
}

Discarding input from socket

From Socket documentation:
shutdownInput
public void shutdownInput()
throws IOException
Places the input stream for this socket at "end of stream". Any data sent to the input stream side of the socket is acknowledged and then silently discarded.
If you read from a socket input stream after invoking shutdownInput() on the socket, the stream will return EOF.
In order to test interaction between clients in a server, I've written some client bots.
These bots generate somewhat random client requests. Since these only write to the server, they have no need for the input stream, they do not need to read the updates the server sends. This is the main body of code for the bots:
private void runWriteBot(PrintWriter out) throws IOException {
//socket.shutdownInput();
String request;
System.out.println("Write bot ready.");
while (!quit) {
request = randomRequest();
out.println(request);
sleep();
}
}
If I uncomment the shutdownInput, an exception is thrown in the server's client handler:
Connection reset
I wasn't expecting an exception to be thrown on the other side of the socket. The documentation suggests (to me, at least) that anything sent by the other side will just be silently discarded, causing no interference with the other end's activity, ie without having the other side throw an exception.
Can I just ignore what the server sends, or should I drain what comes to the input stream?
Is there any automagic way of doing it, or do I need to regularly read and ignore?
The behaviour when you call shutdownInput() is platform-dependent.
BSD Unix will silently discard any further input.
Linux will keep buffering the input, which will eventually block the sender, or cause him to get EAGAIN/EWOULDBLOCK if he is in non-blocking mode.
Windows will reset the connection if any further data arrives.
This is determined by the platform, not by Java.
I don't see any need for calling shutdownInput() in most situations. The only thing it is really useful for is unblocking a read. In your situation you are going to have to read the server responses.

Socket and SwingWorker doesn't work after closing the connection

I'm running a TCP/IP Socket, that sends a SOAP message then gets a response then read it.
The problem is with that scenario: At first everything is fine, I send a message and I get the response using the swingworker. If I close the socket, and I try to connect again, I stop the swing worker by a boolean. When I connect again, I let the thread run, but then I don't get any output from the socket when I send a SOAP Message, but when I do debugging at that time, and I step down to codes, I get a response and an output!. How come that happens ?
Here is my code:
protected Object doInBackground() throws Exception {
Integer result = 1;
while (true) {
if (startReading && !this.server.isSocketClosed()) {
// send the SOAP Message based on which message is selected
SendSOAPRequestMessage();
//Thread.sleep(5);
String responseMessage = null;
try {
// get the response from the client/server
responseMessage = Utils.convertStreamToString(this.server);
System.out.println(responseMessage);
// give the message without the header + and check the content length and if the header is corrupted
fullMsg = decoder.DecodeSoapHeader(new String(responseMessage.getBytes("UTF-8")));
} catch (Exception ex) {
Logger.getLogger(MainWindow.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}
As noted here, "SwingWorker is only designed to be executed once." Moreover, your worker does not synchronize access to this.server, so external changes may not be visible to the worker's background thread. Some alternatives:
Create a new instance of the worker for each request.
Let the worker manage the socket.
Addendum: For solution number one, should I create a new socket also?
No. As noted here, "A call to start on a thread happens-before any action in the started thread." It might be clearer to pass a reference to the socket as a constructor parameter, for example.
On the other hand, socket overhead may be irrelevant. Profile to be sure.

Java - readObject() and setSoTimeout()

So, i wrote a thread on my client side , which tries to readObject() from a socket stream.
This thread runs as long the client is connected.
The connection to the server can be closed on the client's GUI.
If the client decides to disconnect(this will not exit the client program) by clicking the
"disconnect" menu option, the socket will be closed and a isConnected is set to false.
Since the clients read thread tries to readObject() from stream, while the connection can be closed via the GUI, i set a timeout to 250ms (setSoTimeout(250)).
#Override
public void run()
{
this.connection = this.connectionHandler.getSocket();
while(connectionHandler.isConnected())
{
this.readCircle();
}
this.connectionHandler.setReadTaskRunning(false);
}
private void readCircle()
{
try
{
this.connection.setSoTimeout(250);
this.connectionHandler.readData(); //this uses readObject().
}
catch(SocketTimeoutException timeout){}
catch(...){}
}
I know that readObject() will block, and to check if the client is still connected, i wraped it in a while, which checks (every timeout) if the client socket is still connected.
My question now:
In case, if the readObject() starts to get a object passed by the server, tries to read it, but while processing a timeout occurs, will the data on the stream be "damaged" in some way, because it canceled.
Or should i just let the readObject() block and catch a exception if the GUI thread wants to close the socket.
I'm not very experienced with sockets and maybe my approach is wrong at all.
Socket read timeout will cause a SocketTimeoutException to be thrown by readObject(). You may not be able to reuse that ObjectInputStream, and the stream may be damaged because its current position will stay largely undefined.
This probably can only be fixed by closing and reopening the connection.

Categories