Lock.java 10.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340
/*
 * Copyright 2008-2009 Sun Microsystems, Inc.  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.
 *
 * 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */


/* @test
 * @bug 4607272
 * @summary Unit test for AsynchronousFileChannel#lock method
 */

import java.net.*;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.file.*;
import static java.nio.file.StandardOpenOption.*;
import java.nio.channels.*;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Random;
import java.util.concurrent.*;

public class Lock {

    static final Random rand = new Random();

    public static void main(String[] args) throws Exception {
        if (args.length > 0 && args[0].equals("-lockslave")) {
            int port = Integer.parseInt(args[1]);
            runLockSlave(port);
            System.exit(0);
        }

        LockSlaveMirror slave = startLockSlave();
        try {

             // create temporary file
            File blah = File.createTempFile("blah", null);
            blah.deleteOnExit();

            testLockProtocol(blah, slave);
            testAsyncClose(blah, slave);

        } finally {
            slave.shutdown();
        }
    }

    // test locking protocol
    static void testLockProtocol(File file, LockSlaveMirror slave)
        throws Exception
    {
        FileLock fl;

        // slave VM opens file and acquires exclusive lock
        slave.open(file.getPath()).lock();

        AsynchronousFileChannel ch = AsynchronousFileChannel
            .open(file.toPath(), READ, WRITE);

        // this VM tries to acquire lock
        // (lock should not be acquire until released by slave VM)
        Future<FileLock> result = ch.lock();
        try {
            result.get(2, TimeUnit.SECONDS);
            throw new RuntimeException("Timeout expected");
        } catch (TimeoutException x) {
        }

        // slave VM releases lock
        slave.unlock();

        // this VM should now acquire lock
        fl = result.get();
        fl.release();

        // slave VM acquires lock on range
        slave.lock(0, 10, false);

        // this VM acquires lock on non-overlapping range
        fl = ch.lock(10, 10, false, null, null).get();
        fl.release();

        // done
        ch.close();
        slave.close();
    }

    // test close of channel with outstanding lock operation
    static void testAsyncClose(File file, LockSlaveMirror slave) throws Exception {
        // slave VM opens file and acquires exclusive lock
        slave.open(file.getPath()).lock();

        for (int i=0; i<100; i++) {
            AsynchronousFileChannel ch = AsynchronousFileChannel
                .open(file.toPath(), READ, WRITE);

            // try to lock file (should not complete because file is locked by slave)
            Future<FileLock> result = ch.lock();
            try {
                result.get(rand.nextInt(100), TimeUnit.MILLISECONDS);
                throw new RuntimeException("Timeout expected");
            } catch (TimeoutException x) {
            }

            // close channel with lock operation outstanding
            ch.close();

            // operation should complete with AsynchronousCloseException
            try {
                result.get();
                throw new RuntimeException("ExecutionException expected");
            } catch (ExecutionException x) {
                if (!(x.getCause() instanceof AsynchronousCloseException)) {
                    x.getCause().printStackTrace();
                    throw new RuntimeException("AsynchronousCloseException expected");
                }
            }
        }

        slave.close();
    }

    // starts a "lock slave" in another process, returning a mirror object to
    // control the slave
    static LockSlaveMirror startLockSlave() throws Exception {
        ServerSocketChannel ssc = ServerSocketChannel.open()
            .bind(new InetSocketAddress(0));
        int port = ((InetSocketAddress)(ssc.getLocalAddress())).getPort();

        String sep = FileSystems.getDefault().getSeparator();

        String command = System.getProperty("java.home") +
            sep + "bin" + sep + "java Lock -lockslave " + port;
        Process p = Runtime.getRuntime().exec(command);
        IOHandler.handle(p.getInputStream());
        IOHandler.handle(p.getErrorStream());

        // wait for slave to connect
        SocketChannel sc = ssc.accept();
        return new LockSlaveMirror(sc);
    }

    // commands that the slave understands
    static final String OPEN_CMD    = "open";
    static final String CLOSE_CMD   = "close";
    static final String LOCK_CMD    = "lock";
    static final String UNLOCK_CMD  = "unlock";
    static final char TERMINATOR    = ';';

    // provides a proxy to a "lock slave"
    static class LockSlaveMirror {
        private final SocketChannel sc;

        LockSlaveMirror(SocketChannel sc) {
            this.sc = sc;
        }

        private void sendCommand(String cmd, String... params)
            throws IOException
        {
            for (String s: params) {
                cmd += " " + s;
            }
            cmd += TERMINATOR;

            ByteBuffer buf = Charset.defaultCharset().encode(cmd);
            while (buf.hasRemaining()) {
                sc.write(buf);
            }

            // wait for ack
            buf = ByteBuffer.allocate(1);
            int n = sc.read(buf);
            if (n != 1)
                throw new RuntimeException("Reply expected");
            if (buf.get(0) != TERMINATOR)
                throw new RuntimeException("Terminated expected");
        }

        LockSlaveMirror open(String file) throws IOException {
            sendCommand(OPEN_CMD, file);
            return this;
        }

        void close() throws IOException {
            sendCommand(CLOSE_CMD);
        }

        LockSlaveMirror lock() throws IOException {
            sendCommand(LOCK_CMD);
            return this;
        }


        LockSlaveMirror lock(long position, long size, boolean shared)
            throws IOException
        {
            sendCommand(LOCK_CMD, position + "," + size + "," + shared);
            return this;
        }

        LockSlaveMirror unlock() throws IOException {
            sendCommand(UNLOCK_CMD);
            return this;
        }

        void shutdown() throws IOException {
            sc.close();
        }
    }

    // Helper class to direct process output to the parent System.out
    static class IOHandler implements Runnable {
        private final InputStream in;

        IOHandler(InputStream in) {
            this.in = in;
        }

        static void handle(InputStream in) {
            IOHandler handler = new IOHandler(in);
            Thread thr = new Thread(handler);
            thr.setDaemon(true);
            thr.start();
        }

        public void run() {
            try {
                byte b[] = new byte[100];
                for (;;) {
                    int n = in.read(b);
                    if (n < 0) return;
                    for (int i=0; i<n; i++) {
                        System.out.print((char)b[i]);
                    }
                }
            } catch (IOException ioe) { }
        }
    }

    // slave process that responds to simple commands a socket connection
    static void runLockSlave(int port) throws Exception {

        // establish connection to parent
        SocketChannel sc = SocketChannel.open(new InetSocketAddress(port));
        ByteBuffer buf = ByteBuffer.allocateDirect(1024);

        FileChannel fc = null;
        FileLock fl = null;
        try {
            for (;;) {

                // read command (ends with ";")
                buf.clear();
                int n, last = 0;
                do {
                    n = sc.read(buf);
                    if (n < 0)
                        return;
                    if (n == 0)
                        throw new AssertionError();
                    last += n;
                } while (buf.get(last-1) != TERMINATOR);

                // decode into command and optional parameter
                buf.flip();
                String s = Charset.defaultCharset().decode(buf).toString();
                int sp = s.indexOf(" ");
                String cmd = (sp < 0) ? s.substring(0, s.length()-1) :
                    s.substring(0, sp);
                String param = (sp < 0) ? "" : s.substring(sp+1, s.length()-1);

                // execute
                if (cmd.equals(OPEN_CMD)) {
                    if (fc != null)
                        throw new RuntimeException("File already open");
                    fc = FileChannel.open(Paths.get(param),READ, WRITE);
                }
                if (cmd.equals(CLOSE_CMD)) {
                    if (fc == null)
                        throw new RuntimeException("No file open");
                    fc.close();
                    fc = null;
                    fl = null;
                }
                if (cmd.equals(LOCK_CMD)) {
                    if (fl != null)
                        throw new RuntimeException("Already holding lock");

                    if (param.length() == 0) {
                        fl = fc.lock();
                    } else {
                        String[] values = param.split(",");
                        if (values.length != 3)
                            throw new RuntimeException("Lock parameter invalid");
                        long position = Long.parseLong(values[0]);
                        long size = Long.parseLong(values[1]);
                        boolean shared = Boolean.parseBoolean(values[2]);
                        fl = fc.lock(position, size, shared);
                    }
                }

                if (cmd.equals(UNLOCK_CMD)) {
                    if (fl == null)
                        throw new RuntimeException("Not holding lock");
                    fl.release();
                    fl = null;
                }

                // send reply
                byte[] reply = { TERMINATOR };
                n = sc.write(ByteBuffer.wrap(reply));
            }

        } finally {
            sc.close();
            if (fc != null) fc.close();
        }
    }
}