6350055: (se) SelectionKey.interestOps variants to atomically update interest ops
Reviewed-by: bpb
Contributed-by: david.lloyd@redhat.com, alan.bateman@oracle.com
--- a/src/java.base/share/classes/java/nio/channels/SelectionKey.java Thu Jun 07 09:02:03 2018 +0200
+++ b/src/java.base/share/classes/java/nio/channels/SelectionKey.java Thu Jun 07 09:18:39 2018 +0100
@@ -190,6 +190,83 @@
public abstract SelectionKey interestOps(int ops);
/**
+ * Atomically sets this key's interest set to the bitwise union ("or") of
+ * the existing interest set and the given value. This method is guaranteed
+ * to be atomic with respect to other concurrent calls to this method or to
+ * {@link #interestOpsAnd(int)}.
+ *
+ * <p> This method may be invoked at any time. If this method is invoked
+ * while a selection operation is in progress then it has no effect upon
+ * that operation; the change to the key's interest set will be seen by the
+ * next selection operation.
+ *
+ * @implSpec The default implementation synchronizes on this key and invokes
+ * {@code interestOps()} and {@code interestOps(int)} to retrieve and set
+ * this key's interest set.
+ *
+ * @param ops The interest set to apply
+ *
+ * @return The previous interest set
+ *
+ * @throws IllegalArgumentException
+ * If a bit in the set does not correspond to an operation that
+ * is supported by this key's channel, that is, if
+ * {@code (ops & ~channel().validOps()) != 0}
+ *
+ * @throws CancelledKeyException
+ * If this key has been cancelled
+ *
+ * @since 11
+ */
+ public int interestOpsOr(int ops) {
+ synchronized (this) {
+ int oldVal = interestOps();
+ interestOps(oldVal | ops);
+ return oldVal;
+ }
+ }
+
+ /**
+ * Atomically sets this key's interest set to the bitwise intersection ("and")
+ * of the existing interest set and the given value. This method is guaranteed
+ * to be atomic with respect to other concurrent calls to this method or to
+ * {@link #interestOpsOr(int)}.
+ *
+ * <p> This method may be invoked at any time. If this method is invoked
+ * while a selection operation is in progress then it has no effect upon
+ * that operation; the change to the key's interest set will be seen by the
+ * next selection operation.
+ *
+ * @apiNote Unlike the {@code interestOps(int)} and {@code interestOpsOr(int)}
+ * methods, this method does not throw {@code IllegalArgumentException} when
+ * invoked with bits in the interest set that do not correspond to an
+ * operation that is supported by this key's channel. This is to allow
+ * operation bits in the interest set to be cleared using bitwise complement
+ * values, e.g., {@code interestOpsAnd(~SelectionKey.OP_READ)} will remove
+ * the {@code OP_READ} from the interest set without affecting other bits.
+ *
+ * @implSpec The default implementation synchronizes on this key and invokes
+ * {@code interestOps()} and {@code interestOps(int)} to retrieve and set
+ * this key's interest set.
+ *
+ * @param ops The interest set to apply
+ *
+ * @return The previous interest set
+ *
+ * @throws CancelledKeyException
+ * If this key has been cancelled
+ *
+ * @since 11
+ */
+ public int interestOpsAnd(int ops) {
+ synchronized (this) {
+ int oldVal = interestOps();
+ interestOps(oldVal & ops);
+ return oldVal;
+ }
+ }
+
+ /**
* Retrieves this key's ready-operation set.
*
* <p> It is guaranteed that the returned set will only contain operation
--- a/src/java.base/share/classes/sun/nio/ch/SelectionKeyImpl.java Thu Jun 07 09:02:03 2018 +0200
+++ b/src/java.base/share/classes/sun/nio/ch/SelectionKeyImpl.java Thu Jun 07 09:18:39 2018 +0100
@@ -25,6 +25,9 @@
package sun.nio.ch;
+import java.lang.invoke.ConstantBootstraps;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
@@ -39,6 +42,13 @@
public final class SelectionKeyImpl
extends AbstractSelectionKey
{
+ private static final VarHandle INTERESTOPS =
+ ConstantBootstraps.fieldVarHandle(
+ MethodHandles.lookup(),
+ "interestOps",
+ VarHandle.class,
+ SelectionKeyImpl.class, int.class);
+
private final SelChImpl channel;
private final SelectorImpl selector;
@@ -84,7 +94,35 @@
@Override
public SelectionKey interestOps(int ops) {
ensureValid();
- return nioInterestOps(ops);
+ if ((ops & ~channel().validOps()) != 0)
+ throw new IllegalArgumentException();
+ int oldOps = (int) INTERESTOPS.getAndSet(this, ops);
+ if (ops != oldOps) {
+ selector.setEventOps(this);
+ }
+ return this;
+ }
+
+ @Override
+ public int interestOpsOr(int ops) {
+ ensureValid();
+ if ((ops & ~channel().validOps()) != 0)
+ throw new IllegalArgumentException();
+ int oldVal = (int) INTERESTOPS.getAndBitwiseOr(this, ops);
+ if (oldVal != (oldVal | ops)) {
+ selector.setEventOps(this);
+ }
+ return oldVal;
+ }
+
+ @Override
+ public int interestOpsAnd(int ops) {
+ ensureValid();
+ int oldVal = (int) INTERESTOPS.getAndBitwiseAnd(this, ops);
+ if (oldVal != (oldVal & ops)) {
+ selector.setEventOps(this);
+ }
+ return oldVal;
}
@Override
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/nio/channels/SelectionKey/AtomicUpdates.java Thu Jun 07 09:18:39 2018 +0100
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/* @test
+ * @bug 6350055
+ * @run testng AtomicUpdates
+ * @summary Unit test for SelectionKey interestOpsOr and interestOpsAnd
+ */
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.nio.channels.CancelledKeyException;
+import java.nio.channels.SelectableChannel;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import org.testng.annotations.Test;
+
+import static java.nio.channels.SelectionKey.OP_READ;
+import static java.nio.channels.SelectionKey.OP_WRITE;
+import static java.nio.channels.SelectionKey.OP_CONNECT;
+import static java.nio.channels.SelectionKey.OP_ACCEPT;
+import static org.testng.Assert.*;
+
+@Test
+public class AtomicUpdates {
+
+ private SelectionKey keyFor(SocketChannel sc) {
+ return new SelectionKey() {
+ private int ops;
+ private boolean invalid;
+ private void ensureValid() {
+ if (!isValid())
+ throw new CancelledKeyException();
+ }
+ @Override
+ public SelectableChannel channel() {
+ return sc;
+ }
+ @Override
+ public Selector selector() {
+ throw new RuntimeException();
+ }
+ @Override
+ public boolean isValid() {
+ return !invalid;
+ }
+ @Override
+ public void cancel() {
+ invalid = true;
+ }
+ @Override
+ public int interestOps() {
+ ensureValid();
+ return ops;
+ }
+ @Override
+ public SelectionKey interestOps(int ops) {
+ ensureValid();
+ if ((ops & ~channel().validOps()) != 0)
+ throw new IllegalArgumentException();
+ this.ops = ops;
+ return this;
+ }
+ @Override
+ public int readyOps() {
+ ensureValid();
+ return 0;
+ }
+ };
+ }
+
+ private void test(SelectionKey key) {
+ assertTrue(key.channel() instanceof SocketChannel);
+ key.interestOps(0);
+
+ // 0 -> 0
+ int previous = key.interestOpsOr(0);
+ assertTrue(previous == 0);
+ assertTrue(key.interestOps() == 0);
+
+ // 0 -> OP_CONNECT
+ previous = key.interestOpsOr(OP_CONNECT);
+ assertTrue(previous == 0);
+ assertTrue(key.interestOps() == OP_CONNECT);
+
+ // OP_CONNECT -> OP_CONNECT
+ previous = key.interestOpsOr(0);
+ assertTrue(previous == OP_CONNECT);
+ assertTrue(key.interestOps() == OP_CONNECT);
+
+ // OP_CONNECT -> OP_CONNECT | OP_READ | OP_WRITE
+ previous = key.interestOpsOr(OP_READ | OP_WRITE);
+ assertTrue(previous == OP_CONNECT);
+ assertTrue(key.interestOps() == (OP_CONNECT | OP_READ | OP_WRITE));
+
+ // OP_CONNECT | OP_READ | OP_WRITE -> OP_CONNECT
+ previous = key.interestOpsAnd(~(OP_READ | OP_WRITE));
+ assertTrue(previous == (OP_CONNECT | OP_READ | OP_WRITE));
+ assertTrue(key.interestOps() == OP_CONNECT);
+
+ // OP_CONNECT -> 0
+ previous = key.interestOpsAnd(~OP_CONNECT);
+ assertTrue(previous == OP_CONNECT);
+ assertTrue(key.interestOps() == 0);
+
+ // OP_READ | OP_WRITE -> OP_READ | OP_WRITE
+ key.interestOps(OP_READ | OP_WRITE);
+ previous = key.interestOpsAnd(~OP_ACCEPT);
+ assertTrue(previous == (OP_READ | OP_WRITE));
+ assertTrue(key.interestOps() == (OP_READ | OP_WRITE));
+
+ // OP_READ | OP_WRITE -> 0
+ previous = key.interestOpsAnd(0);
+ assertTrue(previous == (OP_READ | OP_WRITE));
+ assertTrue(key.interestOps() == 0);
+
+ // 0 -> 0
+ previous = key.interestOpsAnd(0);
+ assertTrue(previous == 0);
+ assertTrue(key.interestOps() == 0);
+
+ try {
+ key.interestOpsOr(OP_ACCEPT);
+ fail("IllegalArgumentException expected");
+ } catch (IllegalArgumentException expected) { }
+
+ key.cancel();
+ try {
+ key.interestOpsOr(OP_READ);
+ fail("CancelledKeyException expected");
+ } catch (CancelledKeyException expected) { }
+ try {
+ key.interestOpsAnd(~OP_READ);
+ fail("CancelledKeyException expected");
+ } catch (CancelledKeyException expected) { }
+ }
+
+ /**
+ * Test default implementation of interestOpsOr/interestOpsAnd
+ */
+ public void testDefaultImplementation() throws Exception {
+ try (SocketChannel sc = SocketChannel.open()) {
+ SelectionKey key = keyFor(sc);
+ test(key);
+ }
+ }
+
+ /**
+ * Test the default provider implementation of SelectionKey.
+ */
+ public void testNioImplementation() throws Exception {
+ try (SocketChannel sc = SocketChannel.open();
+ Selector sel = Selector.open()) {
+ sc.configureBlocking(false);
+ SelectionKey key = sc.register(sel, 0);
+ test(key);
+ }
+ }
+}
+