1/*
2 * Copyright (c) 2012, 2017, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24/* @test
25 * @bug 7200742
26 * @key intermittent
27 * @summary Test that Selector doesn't spin when changing interest ops
28 */
29
30import java.net.*;
31import java.nio.ByteBuffer;
32import java.nio.channels.*;
33import static java.nio.channels.SelectionKey.*;
34import java.io.IOException;
35
36public class ChangingInterests {
37
38    static int OPS[] = { 0, OP_WRITE, OP_READ, (OP_WRITE|OP_READ) };
39
40    static String toOpsString(int ops) {
41        String s = "";
42        if ((ops & OP_READ) > 0)
43            s += "POLLIN";
44        if ((ops & OP_WRITE) > 0) {
45            if (s.length() > 0)
46                s += "|";
47            s += "POLLOUT";
48        }
49        if (s.length() == 0)
50            s = "0";
51        return "(" + s + ")";
52    }
53
54    /**
55     * Writes two bytes to 'out' and reads one byte from 'in' so
56     * as to make 'in' readable.
57     */
58    static void makeReadable(SocketChannel out, SocketChannel in) throws IOException {
59        out.write(ByteBuffer.wrap(new byte[2]));
60        ByteBuffer oneByte = ByteBuffer.wrap(new byte[1]);
61        do {
62            int n = in.read(oneByte);
63            if (n == 1) {
64                break;
65            } else if (n == 0) {
66                try {
67                    Thread.sleep(50);
68                } catch (InterruptedException ignore) { }
69            } else {
70                throw new RuntimeException
71                    ("Expected to read 0 or 1 byte; actual number was " + n);
72            }
73        } while (true);
74    }
75
76    static void drain(SocketChannel sc) throws IOException {
77        ByteBuffer buf = ByteBuffer.allocate(100);
78        int n;
79        while ((n = sc.read(buf)) > 0) {
80            buf.rewind();
81        }
82    }
83
84    /**
85     * Changes the given key's interest set from one set to another and then
86     * checks the selected key set and the key's channel.
87     */
88    static void testChange(SelectionKey key, int from, int to) throws IOException {
89        Selector sel = key.selector();
90        assertTrue(sel.keys().size() == 1, "Only one channel should be registered");
91
92        // ensure that channel is registered with the "from" interest set
93        key.interestOps(from);
94        sel.selectNow();
95        sel.selectedKeys().clear();
96
97        // change to the "to" interest set
98        key.interestOps(to);
99        System.out.println("select...");
100        int selected = sel.selectNow();
101        System.out.println("" + selected + " channel(s) selected");
102
103        int expected = (to == 0) ? 0 : 1;
104        assertTrue(selected == expected, "Expected " + expected);
105
106        // check selected keys
107        for (SelectionKey k: sel.selectedKeys()) {
108            assertTrue(k == key, "Unexpected key selected");
109
110            boolean readable = k.isReadable();
111            boolean writable = k.isWritable();
112
113            System.out.println("key readable: " + readable);
114            System.out.println("key writable: " + writable);
115
116            if ((to & OP_READ) == 0) {
117                assertTrue(!readable, "Not expected to be readable");
118            } else {
119                assertTrue(readable, "Expected to be readable");
120            }
121
122            if ((to & OP_WRITE) == 0) {
123                assertTrue(!writable, "Not expected to be writable");
124            } else {
125                assertTrue(writable, "Expected to be writable");
126            }
127
128            sel.selectedKeys().clear();
129        }
130    }
131
132    /**
133     * Tests that given Selector's select method blocks.
134     */
135    static void testForSpin(Selector sel) throws IOException {
136        System.out.println("Test for spin...");
137        long start = System.currentTimeMillis();
138        int count = 3;
139        while (count-- > 0) {
140            int selected = sel.select(1000);
141            System.out.println("" + selected + " channel(s) selected");
142            assertTrue(selected == 0, "Channel should not be selected");
143        }
144        long dur = System.currentTimeMillis() - start;
145        assertTrue(dur > 1000, "select was too short");
146    }
147
148    public static void main(String[] args) throws IOException {
149        InetAddress lh = InetAddress.getLocalHost();
150
151        // create loopback connection
152        ServerSocketChannel ssc =
153            ServerSocketChannel.open().bind(new InetSocketAddress(0));
154
155        final SocketChannel sc = SocketChannel.open();
156        sc.setOption(StandardSocketOptions.TCP_NODELAY, true);
157        sc.connect(new InetSocketAddress(lh, ssc.socket().getLocalPort()));
158        SocketChannel peer = ssc.accept();
159        peer.setOption(StandardSocketOptions.TCP_NODELAY, true);
160
161        sc.configureBlocking(false);
162
163        // ensure that channel "sc" is readable
164        makeReadable(peer, sc);
165
166        try (Selector sel = Selector.open()) {
167            SelectionKey key = sc.register(sel, 0);
168            sel.selectNow();
169
170            // test all transitions
171            for (int from: OPS) {
172                for (int to: OPS) {
173
174                    System.out.println(toOpsString(from) + " -> " + toOpsString(to));
175
176                    testChange(key, from, to);
177
178                    // if the interst ops is now 0 then Selector should not spin
179                    if (to == 0)
180                        testForSpin(sel);
181
182                    // if interest ops is now OP_READ then make non-readable
183                    // and test that Selector does not spin.
184                    if (to == OP_READ) {
185                        System.out.println("Drain channel...");
186                        drain(sc);
187                        testForSpin(sel);
188                        System.out.println("Make channel readable again");
189                        makeReadable(peer, sc);
190                    }
191
192                    System.out.println();
193                }
194            }
195
196        } finally {
197            sc.close();
198            peer.close();
199            ssc.close();
200        }
201    }
202
203    static void assertTrue(boolean v, String msg) {
204        if (!v) throw new RuntimeException(msg);
205    }
206
207}
208