Thread-safety of NIO2 CompletionHandler
Asked Answered
H

3

7

Is the following code thread-safe? If so, what guarantees the safe publication of the ByteBuffer instance to the thread executing the CompletionHandler?

AsynchronousSocketChannel channel = ...
ByteBuffer buf = ByteBuffer.allocate(1024);
channel.read(buf, null, new CompletionHandler<Integer, Void>() {

    //"completed" can be executed by a different thread than channel.read()
    public void completed(Integer result, Void attachment) {                
        buf.flip(); //Can buf be safely accessed here? If so, why?   
        //...          
    }

    public void failed(Throwable exc, Void attachment) {
       //...
    }
});
Hebephrenia answered 26/8, 2021 at 9:32 Comment(2)
Does this compile? buf needs to be final or 'effectively final' here. In any case you shouldn't keep reallocating read buffers. Use the same one for the life of the channel, and keep it in, or via, the key attachment.Keiko
@Keiko yes, it compiles since buf is effectively final in this case. Regarding the buffer allocation - this is a sample piece of code for the purpose of the question.Hebephrenia
R
3

an authoritative reference that is valid for all JVMs and all platforms

The only such authoritative source I know of is javadocs (1, 2, 3).

Unfortunately, as you can see for yourself, they contain no explicit and clear guarantees of thread-safety.

It means the code is not thread-safe.


IMO the guarantees should be given in the javadoc for the method, the method's class, or CompletionHandler — then we can be sure they are implemented for all JVMs and all platforms (and will stay implemented in the future).

But if you really want, you can "compile" a proof for thread-safety from multiple places in different javadocs:

  • AsynchronousSocketChannel.read(...):

    The handler parameter is a completion handler that is invoked when the read operation completes (or fails).

  • java.nio.channels:

    Asynchronous channels are bound to an asynchronous channel group for the purpose of resource sharing. A group has an associated ExecutorService to which tasks are submitted to handle I/O events and dispatch to completion handlers that consume the result of asynchronous operations performed on channels in the group.

  • ExecutorService:

    Memory consistency effects: Actions in a thread prior to the submission of a Runnable or Callable task to an ExecutorService happen-before any actions taken by that task

As a result, we get that every action of the I/O read to ByteBuffer happens-before the first action of CompletionHandler => this means the code is thread-safe.

IMO "compiled proofs" like the one above are too fragile, and personally I would assume that the code is not thread-safe.

Reword answered 29/8, 2021 at 21:55 Comment(0)
S
2

I don't see in javadocs any explicit guarantees about inner state of a ByteBuffer, used in a read() operation, to be visible inside CompletionHandler called when the read() completes.

So there is no 100% guarantee.

But I would say that it is common sense to expect that to be true. Simply because:

  1. it's normal to expect CompletionHandler to see the changes made by the 'read()'
  2. the fact that CompletionHandler can be run asynchronously in a different thread — is a detail of read()'s internal implementation. As a result, it's a read()'s concern to make sure that thing are safely published in such cases.

But if you are unsure/unconvinced, and you don't develop applications with nanosecond delays — just add your own additional synchronization — and you will be 100% sure that your program works correctly.

Spannew answered 26/8, 2021 at 19:21 Comment(1)
Thanks for the input. I tend to agree that it makes sense for this to be thread safe, and I realize I can use locks to make sure that it is. However, the question isn't about that. It's about what guarantees this behavior across JVMs. OSes and CPU architectures.Hebephrenia
E
1

Just to add to the answer above, you might prefer to pass ByteBuffer to CompletionHandler via attachment argument of read(...) method (as it's done in various examples):

AsynchronousSocketChannel channel = ...
ByteBuffer buf = ByteBuffer.allocate(1024);
channel.read(buf, buf, new CompletionHandler<>() {

    public void completed(Integer result, ByteBuffer buf) {                
        buf.flip();
        //...          
    }

    public void failed(Throwable exc, ByteBuffer buf) {
       //...
    }
});

Since attachment is an explicit argument for read(...), it must be safely published to CompletionHandler's thread.
Unfortunately, I don't see in javadocs any explicit guarantees that this safe publication occurs after the read(...) completes.

Elenoraelenore answered 26/8, 2021 at 20:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.