# HG changeset patch # User alanb # Date 1528359519 -3600 # Node ID c5c827f3bf72c26e71190ad455f07edd76c9d631 # Parent 66d0ded78cced8df5a221d615d059563e20f68f0 6350055: (se) SelectionKey.interestOps variants to atomically update interest ops Reviewed-by: bpb Contributed-by: david.lloyd@redhat.com, alan.bateman@oracle.com diff -r 66d0ded78cce -r c5c827f3bf72 src/java.base/share/classes/java/nio/channels/SelectionKey.java --- 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)}. + * + *

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)}. + * + *

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. * *

It is guaranteed that the returned set will only contain operation diff -r 66d0ded78cce -r c5c827f3bf72 src/java.base/share/classes/sun/nio/ch/SelectionKeyImpl.java --- 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 diff -r 66d0ded78cce -r c5c827f3bf72 test/jdk/java/nio/channels/SelectionKey/AtomicUpdates.java --- /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); + } + } +} +