001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package demo; // CHECKSTYLE_OFF: RegexpHeader
020
021import java.io.IOException;
022import java.lang.management.ManagementFactory;
023import java.nio.channels.FileChannel;
024import java.nio.channels.FileLock;
025import java.nio.file.FileStore;
026import java.nio.file.Files;
027import java.nio.file.Path;
028import java.nio.file.Paths;
029import java.nio.file.StandardOpenOption;
030import java.util.concurrent.CountDownLatch;
031import java.util.concurrent.atomic.AtomicInteger;
032
033/**
034 * A simple tool to check file locking on your OS/FS/Java combo. To use this tool, just copy it to basedir on
035 * the volume you plan to use as local repository and compile and run it:
036 * <ul>
037 *   <li><pre>javac demo.TestNioLock.java</pre></li>
038 *   <li><pre>java demo.TestNioLock test someFile 1000</pre></li>
039 * </ul>
040 */
041public class TestNioLock {
042    private static final int EC_WON = 10;
043
044    private static final int EC_LOST = 20;
045
046    private static final int EC_FAILED = 30;
047
048    private static final int EC_ERROR = 100;
049
050    public static void main(String[] args) throws IOException, InterruptedException {
051        if (args.length != 3) {
052            System.out.println("demo.TestNioLock <test|perform> <file> <sleepMs>");
053            System.exit(EC_ERROR);
054        }
055
056        String mode = args[0];
057        Path path = Paths.get(args[1]).toAbsolutePath();
058        Path latchFile = path.getParent().resolve(TestNioLock.class.getName() + ".latchFile");
059
060        if (Files.isDirectory(path)) {
061            System.out.println("The <file> cannot be directory.");
062            System.exit(EC_ERROR);
063        }
064        if (!Files.isRegularFile(latchFile)) {
065            Files.createFile(latchFile);
066        }
067
068        if ("test".equals(mode)) {
069            System.out.println("Testing file locking on");
070            System.out.println(
071                    "  Java " + System.getProperty("java.version") + ", " + System.getProperty("java.vendor"));
072            System.out.println("  OS " + System.getProperty("os.name") + " " + System.getProperty("os.version") + " "
073                    + System.getProperty("os.arch"));
074
075            FileStore fileStore = Files.getFileStore(path.getParent());
076            System.out.println("  FS " + fileStore.name() + " " + fileStore.type());
077            System.out.println();
078
079            AtomicInteger oneResult = new AtomicInteger(-1);
080            AtomicInteger twoResult = new AtomicInteger(-1);
081            CountDownLatch latch = new CountDownLatch(2);
082            String javaCmd = System.getProperty("java.home") + "/bin/java";
083
084            try (FileChannel latchChannel =
085                    FileChannel.open(latchFile, StandardOpenOption.READ, StandardOpenOption.WRITE)) {
086                try (FileLock latchLock = latchChannel.lock(0L, 1L, false)) {
087                    new Thread(() -> {
088                                try {
089                                    oneResult.set(new ProcessBuilder(
090                                                    javaCmd, TestNioLock.class.getName(), "perform", args[1], args[2])
091                                            .inheritIO()
092                                            .start()
093                                            .waitFor());
094                                } catch (Exception e) {
095                                    oneResult.set(EC_FAILED);
096                                } finally {
097                                    latch.countDown();
098                                }
099                            })
100                            .start();
101                    new Thread(() -> {
102                                try {
103                                    twoResult.set(new ProcessBuilder(
104                                                    javaCmd, TestNioLock.class.getName(), "perform", args[1], args[2])
105                                            .inheritIO()
106                                            .start()
107                                            .waitFor());
108                                } catch (Exception e) {
109                                    twoResult.set(EC_FAILED);
110                                } finally {
111                                    latch.countDown();
112                                }
113                            })
114                            .start();
115
116                    Thread.sleep(1000); // give them a bit of time (to both block)
117                    latchLock.release();
118                    latch.await();
119                }
120            }
121
122            int oneExit = oneResult.get();
123            int twoExit = twoResult.get();
124            if ((oneExit == EC_WON && twoExit == EC_LOST) || (oneExit == EC_LOST && twoExit == EC_WON)) {
125                System.out.println("OK");
126                System.exit(0);
127            } else {
128                System.out.println("FAILED: one=" + oneExit + " two=" + twoExit);
129                System.exit(EC_FAILED);
130            }
131        } else if ("perform".equals(mode)) {
132            String processName = ManagementFactory.getRuntimeMXBean().getName();
133            System.out.println(processName + " > started");
134            boolean won = false;
135            long sleepMs = Long.parseLong(args[2]);
136            try (FileChannel latchChannel = FileChannel.open(latchFile, StandardOpenOption.READ)) {
137                try (FileLock latchLock = latchChannel.lock(0L, 1L, true)) {
138                    System.out.println(processName + " > latchLock acquired");
139                    try (FileChannel channel = FileChannel.open(
140                            path, StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE)) {
141                        try (FileLock lock = channel.tryLock(0L, 1L, false)) {
142                            if (lock != null && lock.isValid() && !lock.isShared()) {
143                                System.out.println(processName + " > WON");
144                                won = true;
145                                Thread.sleep(sleepMs);
146                            } else {
147                                System.out.println(processName + " > LOST");
148                            }
149                        }
150                    }
151                }
152            }
153            System.out.println(processName + " > ended");
154            if (won) {
155                System.exit(EC_WON);
156            } else {
157                System.exit(EC_LOST);
158            }
159        } else {
160            System.err.println("Unknown mode: " + mode);
161        }
162        System.exit(EC_ERROR);
163    }
164}