ConcurrentCloseTest.java revision babccbf9e429c4c78aca24c205825ceaaf7d3f37
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package libcore.java.net;
18
19import java.io.IOException;
20import java.net.DatagramPacket;
21import java.net.DatagramSocket;
22import java.net.InetSocketAddress;
23import java.net.ServerSocket;
24import java.net.Socket;
25import java.net.SocketAddress;
26import java.net.SocketException;
27import java.nio.channels.AsynchronousCloseException;
28import java.nio.channels.ClosedChannelException;
29import java.nio.channels.SocketChannel;
30import java.util.ArrayList;
31import java.util.List;
32import java.util.concurrent.CopyOnWriteArrayList;
33import tests.net.StuckServer;
34
35/**
36 * Test that Socket.close called on another thread interrupts a thread that's blocked doing
37 * network I/O.
38 */
39public class ConcurrentCloseTest extends junit.framework.TestCase {
40    public void test_accept() throws Exception {
41        ServerSocket ss = new ServerSocket(0);
42        new Killer(ss).start();
43        try {
44            System.err.println("accept...");
45            Socket s = ss.accept();
46            fail("accept returned " + s + "!");
47        } catch (SocketException expected) {
48            assertEquals("Socket closed", expected.getMessage());
49        }
50    }
51
52    public void test_connect() throws Exception {
53        StuckServer ss = new StuckServer(false);
54        Socket s = new Socket();
55        new Killer(s).start();
56        try {
57            System.err.println("connect...");
58            s.connect(ss.getLocalSocketAddress());
59            fail("connect returned: " + s + "!");
60        } catch (SocketException expected) {
61            assertEquals("Socket closed", expected.getMessage());
62        } finally {
63            ss.close();
64        }
65    }
66
67    public void test_connect_timeout() throws Exception {
68        StuckServer ss = new StuckServer(false);
69        Socket s = new Socket();
70        new Killer(s).start();
71        try {
72            System.err.println("connect (with timeout)...");
73            s.connect(ss.getLocalSocketAddress(), 3600 * 1000);
74            fail("connect returned: " + s + "!");
75        } catch (SocketException expected) {
76            assertEquals("Socket closed", expected.getMessage());
77        } finally {
78            ss.close();
79        }
80    }
81
82    public void test_connect_nonBlocking() throws Exception {
83        StuckServer ss = new StuckServer(false);
84        SocketChannel s = SocketChannel.open();
85        new Killer(s.socket()).start();
86        try {
87            System.err.println("connect (non-blocking)...");
88            s.configureBlocking(false);
89            s.connect(ss.getLocalSocketAddress());
90            while (!s.finishConnect()) {
91                // Spin like a mad thing!
92            }
93            fail("connect returned: " + s + "!");
94        } catch (SocketException expected) {
95            assertEquals("Socket closed", expected.getMessage());
96        } catch (AsynchronousCloseException alsoOkay) {
97            // See below.
98        } catch (ClosedChannelException alsoOkay) {
99            // For now, I'm assuming that we're happy as long as we get any reasonable exception.
100            // It may be that we're supposed to guarantee only one or the other.
101        } finally {
102            ss.close();
103        }
104    }
105
106    public void test_read() throws Exception {
107        SilentServer ss = new SilentServer();
108        Socket s = new Socket();
109        s.connect(ss.getLocalSocketAddress());
110        new Killer(s).start();
111        try {
112            System.err.println("read...");
113            int i = s.getInputStream().read();
114            fail("read returned: " + i);
115        } catch (SocketException expected) {
116            assertEquals("Socket closed", expected.getMessage());
117        }
118        ss.close();
119    }
120
121    public void test_read_multiple() throws Throwable {
122        SilentServer ss = new SilentServer();
123        final Socket s = new Socket();
124        s.connect(ss.getLocalSocketAddress());
125
126        // We want to test that we unblock *all* the threads blocked on a socket, not just one.
127        // We know the implementation uses the same mechanism for all blocking calls, so we just
128        // test read(2) because it's the easiest to test. (recv(2), for example, is only accessible
129        // from Java via a synchronized method.)
130        final ArrayList<Thread> threads = new ArrayList<Thread>();
131        final List<Throwable> thrownExceptions = new CopyOnWriteArrayList<Throwable>();
132        for (int i = 0; i < 10; ++i) {
133            Thread t = new Thread(new Runnable() {
134                public void run() {
135                    try {
136                        try {
137                            System.err.println("read...");
138                            int i = s.getInputStream().read();
139                            fail("read returned: " + i);
140                        } catch (SocketException expected) {
141                            assertEquals("Socket closed", expected.getMessage());
142                        }
143                    } catch (Throwable ex) {
144                        thrownExceptions.add(ex);
145                    }
146                }
147            });
148            threads.add(t);
149        }
150        for (Thread t : threads) {
151            t.start();
152        }
153        new Killer(s).start();
154        for (Thread t : threads) {
155            t.join();
156        }
157        for (Throwable exception : thrownExceptions) {
158            throw exception;
159        }
160
161        ss.close();
162    }
163
164    public void test_recv() throws Exception {
165        DatagramSocket s = new DatagramSocket();
166        byte[] buf = new byte[200];
167        DatagramPacket p = new DatagramPacket(buf, 200);
168        new Killer(s).start();
169        try {
170            System.err.println("receive...");
171            s.receive(p);
172            fail("receive returned!");
173        } catch (SocketException expected) {
174            assertEquals("Socket closed", expected.getMessage());
175        }
176    }
177
178    public void test_write() throws Exception {
179        final SilentServer ss = new SilentServer();
180        Socket s = new Socket();
181        s.connect(ss.getLocalSocketAddress());
182        new Killer(s).start();
183        try {
184            System.err.println("write...");
185            // We just keep writing here until all the buffers are full and we block,
186            // waiting for the server to read (which it never will). If the asynchronous close
187            // fails, we'll see a test timeout here.
188            while (true) {
189                byte[] buf = new byte[256*1024];
190                s.getOutputStream().write(buf);
191            }
192        } catch (SocketException expected) {
193            // We throw "Connection reset by peer", which I don't _think_ is a problem.
194            // assertEquals("Socket closed", expected.getMessage());
195        }
196        ss.close();
197    }
198
199    // This server accepts connections, but doesn't read or write anything.
200    // It holds on to the Socket connecting to the client so it won't be GCed.
201    // Call "close" to close both the server socket and its client connection.
202    static class SilentServer {
203        private final ServerSocket ss;
204        private Socket client;
205
206        public SilentServer() throws IOException {
207            ss = new ServerSocket(0);
208            new Thread(new Runnable() {
209                public void run() {
210                    try {
211                        client = ss.accept();
212                    } catch (Exception ex) {
213                        ex.printStackTrace();
214                    }
215                }
216            }).start();
217        }
218
219        public SocketAddress getLocalSocketAddress() {
220            return ss.getLocalSocketAddress();
221        }
222
223        public void close() throws IOException {
224            client.close();
225            ss.close();
226        }
227    }
228
229    // This thread calls the "close" method on the supplied T after 2s.
230    static class Killer<T> extends Thread {
231        private final T s;
232
233        public Killer(T s) {
234            this.s = s;
235        }
236
237        public void run() {
238            try {
239                System.err.println("sleep...");
240                Thread.sleep(2000);
241                System.err.println("close...");
242                s.getClass().getMethod("close").invoke(s);
243            } catch (Exception ex) {
244                ex.printStackTrace();
245            }
246        }
247    }
248}
249