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(128); // Minimal receive buffer size.
180        Socket s = new Socket();
181
182        // Set the send buffer size really small, to ensure we block.
183        int sendBufferSize = 1024;
184        s.setSendBufferSize(sendBufferSize);
185        sendBufferSize = s.getSendBufferSize(); // How big is the buffer really, Linux?
186
187        // Linux still seems to accept more than it should.
188        // How much seems to differ from device to device, but I've yet to see anything accept
189        // twice as much again.
190        sendBufferSize *= 2;
191
192        s.connect(ss.getLocalSocketAddress());
193        new Killer(s).start();
194        try {
195            System.err.println("write...");
196            // Write too much so the buffer is full and we block,
197            // waiting for the server to read (which it never will).
198            // If the asynchronous close fails, we'll see a test timeout here.
199            byte[] buf = new byte[sendBufferSize];
200            s.getOutputStream().write(buf);
201            fail();
202        } catch (SocketException expected) {
203            // We throw "Connection reset by peer", which I don't _think_ is a problem.
204            // assertEquals("Socket closed", expected.getMessage());
205        }
206        ss.close();
207    }
208
209    // This server accepts connections, but doesn't read or write anything.
210    // It holds on to the Socket connecting to the client so it won't be GCed.
211    // Call "close" to close both the server socket and its client connection.
212    static class SilentServer {
213        private final ServerSocket ss;
214        private Socket client;
215
216        public SilentServer() throws IOException {
217            this(0);
218        }
219
220        public SilentServer(int receiveBufferSize) throws IOException {
221            ss = new ServerSocket(0);
222            if (receiveBufferSize != 0) {
223                ss.setReceiveBufferSize(receiveBufferSize);
224            }
225            new Thread(new Runnable() {
226                public void run() {
227                    try {
228                        client = ss.accept();
229                    } catch (Exception ex) {
230                        ex.printStackTrace();
231                    }
232                }
233            }).start();
234        }
235
236        public SocketAddress getLocalSocketAddress() {
237            return ss.getLocalSocketAddress();
238        }
239
240        public void close() throws IOException {
241            client.close();
242            ss.close();
243        }
244    }
245
246    // This thread calls the "close" method on the supplied T after 2s.
247    static class Killer<T> extends Thread {
248        private final T s;
249
250        public Killer(T s) {
251            this.s = s;
252        }
253
254        public void run() {
255            try {
256                System.err.println("sleep...");
257                Thread.sleep(2000);
258                System.err.println("close...");
259                s.getClass().getMethod("close").invoke(s);
260            } catch (Exception ex) {
261                ex.printStackTrace();
262            }
263        }
264    }
265}
266