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