Moving to the nio-dev list.

Registration is not an atomic operation. The channel is registered (this creates the selection key) and then the key's interest set is changed. If I read your test case correctly, it is cancelling the key before the key's interest set is changed and this leads to the CancelledKeyException.

Yes, I agree this may be surprising. Can you submit a bug for this?

-Alan

On 31/05/2022 11:38, Gillespie, Oli wrote:
Hi,

I noticed this surprising (to me) behaviour, and wonder whether it's expected or could be considered a bug. I'm not an expert in this area so apologies if this is trivial.

When registering a SocketChannel with a Selector for the first time, it's possible to get a CancelledKeyException even though this is the first register call.

Exception in thread "main" java.nio.channels.CancelledKeyException
    at java.base/sun.nio.ch.SelectionKeyImpl.ensureValid(SelectionKeyImpl.java:75)     at java.base/sun.nio.ch.SelectionKeyImpl.interestOps(SelectionKeyImpl.java:104)
    at java.base/sun.nio.ch.SelectorImpl.register(SelectorImpl.java:222)
    at java.base/java.nio.channels.spi.AbstractSelectableChannel.register(AbstractSelectableChannel.java:236)     at java.base/java.nio.channels.SelectableChannel.register(SelectableChannel.java:260)
    at KeyCancelled.main(KeyCancelled.java:20)

The javadoc states:

@throws  CancelledKeyException
    If this channel is currently registered with the given selector
      but the corresponding key has already been cancelled

However in this case the channel is _not_ registered, as shown by SocketChannel.isRegistered() returning false.

This following sequence triggers this issue:

1. Thread 1 starts SelectableChannel.register
2. A new SelectionKey becomes visible via Selector.keys()
3. Thread 2 iterates Selector.keys() and calls SelectorKey.cancel()
4. Thread 1 (still in the register() invocation) finds that the key is cancelled and throws CancelledKeyException

Below is a small reproducer which usually exhibits this issue:

import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;

public class KeyCancelled {
    public static void main(String[] args) throws Exception {
        Selector s = Selector.open();

        new Thread(() -> {
            for (int i = 0; i < 100_000; i++) {
                s.keys().forEach(SelectionKey::cancel);
            }
        }).start();

        for (int i = 0; i < 10_000; i++) {
            SocketChannel c = s.provider().openSocketChannel();
            c.configureBlocking(false);
            // Sometimes this throws CancelledKeyException, because the key is cancelled by
            // the other thread part-way through the register call.
            c.register(s, SelectionKey.OP_READ);
            // c.isRegistered() is false here after the exceptional case
        }
    }
}

So, is this expected, a bug, or a gap in documentation?

Many thanks,

Oli



Amazon Development Centre (London) Ltd. Registered in England and Wales with registration number 04543232 with its registered office at 1 Principal Place, Worship Street, London EC2A 2FA, United Kingdom.



Reply via email to