1 /* 2 * Copyright (c) 2008, 2010, 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 25 /* @test 26 * @bug 4607272 6814948 6842687 27 * @summary Unit test for AsynchronousFileChannel#lock method 28 * @key randomness 29 */ 30 31 import java.net.*; 32 import java.nio.ByteBuffer; 33 import java.nio.charset.Charset; 34 import java.nio.file.*; 35 import static java.nio.file.StandardOpenOption.*; 36 import java.nio.channels.*; 37 import java.io.File; 38 import java.io.IOException; 39 import java.io.InputStream; 40 import java.util.Random; 41 import java.util.concurrent.*; 42 43 public class Lock { 44 45 static final Random rand = new Random(); 46 47 public static void main(String[] args) throws Exception { 48 if (args.length > 0 && args[0].equals("-lockslave")) { 49 int port = Integer.parseInt(args[1]); 50 runLockSlave(port); 51 System.exit(0); 52 } 53 54 LockSlaveMirror slave = startLockSlave(); 55 try { 56 57 // create temporary file 58 File blah = File.createTempFile("blah", null); 59 blah.deleteOnExit(); 60 61 // run tests 62 testLockProtocol(blah, slave); 63 testAsyncClose(blah, slave); 64 65 // eagerly clean-up 66 blah.delete(); 67 68 } finally { 69 slave.shutdown(); 70 } 71 } 72 73 // test locking protocol 74 static void testLockProtocol(File file, LockSlaveMirror slave) 75 throws Exception 76 { 77 FileLock fl; 78 79 // slave VM opens file and acquires exclusive lock 80 slave.open(file.getPath()).lock(); 81 82 AsynchronousFileChannel ch = AsynchronousFileChannel 83 .open(file.toPath(), READ, WRITE); 84 85 // this VM tries to acquire lock 86 // (lock should not be acquire until released by slave VM) 87 Future<FileLock> result = ch.lock(); 88 try { 89 result.get(2, TimeUnit.SECONDS); 90 throw new RuntimeException("Timeout expected"); 91 } catch (TimeoutException x) { 92 } 93 94 // slave VM releases lock 95 slave.unlock(); 96 97 // this VM should now acquire lock 98 fl = result.get(); 99 fl.release(); 100 101 // slave VM acquires lock on range 102 slave.lock(0, 10, false); 103 104 // this VM acquires lock on non-overlapping range 105 fl = ch.lock(10, 10, false).get(); 106 fl.release(); 107 108 // done 109 ch.close(); 110 slave.close(); 111 } 112 113 // test close of channel with outstanding lock operation 114 static void testAsyncClose(File file, LockSlaveMirror slave) throws Exception { 115 // slave VM opens file and acquires exclusive lock 116 slave.open(file.getPath()).lock(); 117 118 for (int i=0; i<100; i++) { 119 AsynchronousFileChannel ch = AsynchronousFileChannel 120 .open(file.toPath(), READ, WRITE); 121 122 // try to lock file (should not complete because file is locked by slave) 123 Future<FileLock> result = ch.lock(); 124 try { 125 result.get(rand.nextInt(100), TimeUnit.MILLISECONDS); 126 throw new RuntimeException("Timeout expected"); 127 } catch (TimeoutException x) { 128 } 129 130 // close channel with lock operation outstanding 131 ch.close(); 132 133 // operation should complete with AsynchronousCloseException 134 try { 135 result.get(); 136 throw new RuntimeException("ExecutionException expected"); 137 } catch (ExecutionException x) { 138 if (!(x.getCause() instanceof AsynchronousCloseException)) { 139 x.getCause().printStackTrace(); 140 throw new RuntimeException("AsynchronousCloseException expected"); 141 } 142 } 143 } 144 145 slave.close(); 146 } 147 148 // starts a "lock slave" in another process, returning a mirror object to 149 // control the slave 150 static LockSlaveMirror startLockSlave() throws Exception { 151 ServerSocketChannel ssc = ServerSocketChannel.open() 152 .bind(new InetSocketAddress(0)); 153 int port = ((InetSocketAddress)(ssc.getLocalAddress())).getPort(); 154 155 String sep = FileSystems.getDefault().getSeparator(); 156 157 String command = System.getProperty("java.home") + 158 sep + "bin" + sep + "java"; 159 String testClasses = System.getProperty("test.classes"); 160 if (testClasses != null) 161 command += " -cp " + testClasses; 162 command += " Lock -lockslave " + port; 163 164 Process p = Runtime.getRuntime().exec(command); 165 IOHandler.handle(p.getInputStream()); 166 IOHandler.handle(p.getErrorStream()); 167 168 // wait for slave to connect 169 SocketChannel sc = ssc.accept(); 170 return new LockSlaveMirror(sc); 171 } 172 173 // commands that the slave understands 174 static final String OPEN_CMD = "open"; 175 static final String CLOSE_CMD = "close"; 176 static final String LOCK_CMD = "lock"; 177 static final String UNLOCK_CMD = "unlock"; 178 static final char TERMINATOR = ';'; 179 180 // provides a proxy to a "lock slave" 181 static class LockSlaveMirror { 182 private final SocketChannel sc; 183 184 LockSlaveMirror(SocketChannel sc) { 185 this.sc = sc; 186 } 187 188 private void sendCommand(String cmd, String... params) 189 throws IOException 190 { 191 for (String s: params) { 192 cmd += " " + s; 193 } 194 cmd += TERMINATOR; 195 196 ByteBuffer buf = Charset.defaultCharset().encode(cmd); 197 while (buf.hasRemaining()) { 198 sc.write(buf); 199 } 200 201 // wait for ack 202 buf = ByteBuffer.allocate(1); 203 int n = sc.read(buf); 204 if (n != 1) 205 throw new RuntimeException("Reply expected"); 206 if (buf.get(0) != TERMINATOR) 207 throw new RuntimeException("Terminated expected"); 208 } 209 210 LockSlaveMirror open(String file) throws IOException { 211 sendCommand(OPEN_CMD, file); 212 return this; 213 } 214 215 void close() throws IOException { 216 sendCommand(CLOSE_CMD); 217 } 218 219 LockSlaveMirror lock() throws IOException { 220 sendCommand(LOCK_CMD); 221 return this; 222 } 223 224 225 LockSlaveMirror lock(long position, long size, boolean shared) 226 throws IOException 227 { 228 sendCommand(LOCK_CMD, position + "," + size + "," + shared); 229 return this; 230 } 231 232 LockSlaveMirror unlock() throws IOException { 233 sendCommand(UNLOCK_CMD); 234 return this; 235 } 236 237 void shutdown() throws IOException { 238 sc.close(); 239 } 240 } 241 242 // Helper class to direct process output to the parent System.out 243 static class IOHandler implements Runnable { 244 private final InputStream in; 245 246 IOHandler(InputStream in) { 247 this.in = in; 248 } 249 250 static void handle(InputStream in) { 251 IOHandler handler = new IOHandler(in); 252 Thread thr = new Thread(handler); 253 thr.setDaemon(true); 254 thr.start(); 255 } 256 257 public void run() { 258 try { 259 byte b[] = new byte[100]; 260 for (;;) { 261 int n = in.read(b); 262 if (n < 0) return; 263 for (int i=0; i<n; i++) { 264 System.out.print((char)b[i]); 265 } 266 } 267 } catch (IOException ioe) { } 268 } 269 } 270 271 // slave process that responds to simple commands a socket connection 272 static void runLockSlave(int port) throws Exception { 273 274 // establish connection to parent 275 SocketChannel sc = SocketChannel.open(new InetSocketAddress(port)); 276 ByteBuffer buf = ByteBuffer.allocateDirect(1024); 277 278 FileChannel fc = null; 279 FileLock fl = null; 280 try { 281 for (;;) { 282 283 // read command (ends with ";") 284 buf.clear(); 285 int n, last = 0; 286 do { 287 n = sc.read(buf); 288 if (n < 0) 289 return; 290 if (n == 0) 291 throw new AssertionError(); 292 last += n; 293 } while (buf.get(last-1) != TERMINATOR); 294 295 // decode into command and optional parameter 296 buf.flip(); 297 String s = Charset.defaultCharset().decode(buf).toString(); 298 int sp = s.indexOf(" "); 299 String cmd = (sp < 0) ? s.substring(0, s.length()-1) : 300 s.substring(0, sp); 301 String param = (sp < 0) ? "" : s.substring(sp+1, s.length()-1); 302 303 // execute 304 if (cmd.equals(OPEN_CMD)) { 305 if (fc != null) 306 throw new RuntimeException("File already open"); 307 fc = FileChannel.open(Paths.get(param),READ, WRITE); 308 } 309 if (cmd.equals(CLOSE_CMD)) { 310 if (fc == null) 311 throw new RuntimeException("No file open"); 312 fc.close(); 313 fc = null; 314 fl = null; 315 } 316 if (cmd.equals(LOCK_CMD)) { 317 if (fl != null) 318 throw new RuntimeException("Already holding lock"); 319 320 if (param.length() == 0) { 321 fl = fc.lock(); 322 } else { 323 String[] values = param.split(","); 324 if (values.length != 3) 325 throw new RuntimeException("Lock parameter invalid"); 326 long position = Long.parseLong(values[0]); 327 long size = Long.parseLong(values[1]); 328 boolean shared = Boolean.parseBoolean(values[2]); 329 fl = fc.lock(position, size, shared); 330 } 331 } 332 333 if (cmd.equals(UNLOCK_CMD)) { 334 if (fl == null) 335 throw new RuntimeException("Not holding lock"); 336 fl.release(); 337 fl = null; 338 } 339 340 // send reply 341 byte[] reply = { TERMINATOR }; 342 n = sc.write(ByteBuffer.wrap(reply)); 343 } 344 345 } finally { 346 sc.close(); 347 if (fc != null) fc.close(); 348 } 349 } 350 }