/** * Makes sure that multiple threads of the same Java process cannot lock the same directory at * once. * * @throws Exception */ @Test public void multipleThreadsOfCurrentJvmSameVersion() throws Exception { final PathLocker<SrcVersion> pathLocker = new PathLocker<>(); final Path dir1 = lockerDirectory.resolve(UUID.randomUUID().toString()); final SrcVersion srcVersion = SrcVersion.parse("1.2.3-SRC-revision-deadbeef"); Future<PathLock> concurrLockFuture = null; try (PathLock lock1 = pathLocker.lockDirectory(dir1, srcVersion)) { /* locked for the current thread */ /* now try to lock from another thread which should fail with a TimeoutException * because we have locked above */ try { concurrLockFuture = lockConcurrently(pathLocker, dir1, srcVersion); concurrLockFuture.get(1, TimeUnit.SECONDS); Assert.fail("TimeoutException expected"); } catch (InterruptedException e) { throw e; } catch (ExecutionException e) { throw e; } catch (TimeoutException e) { /* expected */ } } /* unlocked again - the above attempt to lock from a concurrent thread must succeed now */ PathLock lock2 = concurrLockFuture.get(1, TimeUnit.SECONDS); Assert.assertNotNull(lock2); }
@Test public void multipleThreadsOfCurrentJvmDistinctVersion() throws Exception { final PathLocker<SrcVersion> pathLocker = new PathLocker<>(); final Path dir1 = lockerDirectory.resolve(UUID.randomUUID().toString()); final SrcVersion srcVersion1 = SrcVersion.parse("1.2.3-SRC-revision-deadbeef"); final SrcVersion srcVersion2 = SrcVersion.parse("2.3.4-SRC-revision-coffeebabe"); Future<PathLock> concurrLockFuture = null; try (PathLock lock1 = pathLocker.lockDirectory(dir1, srcVersion1)) { /* locked for the current thread */ /* * now try to lock for a distinct version from another thread which should fail with a * CannotAcquireLockException because we have locked the path above */ try { concurrLockFuture = lockConcurrently(pathLocker, dir1, srcVersion2); concurrLockFuture.get(1, TimeUnit.SECONDS); Assert.fail("CannotAcquireLockException expected"); } catch (InterruptedException e) { throw e; } catch (ExecutionException e) { Assert.assertTrue( "Should throw CannotAcquireLockException", CannotAcquireLockException.class.equals(e.getCause().getClass())); } catch (TimeoutException e) { throw e; } } /* the above concurrent attempt should still fail, even if lock1 has been released in between */ try { concurrLockFuture.get(1, TimeUnit.SECONDS); Assert.fail("CannotAcquireLockException expected"); } catch (ExecutionException e) { Assert.assertTrue( "Should throw CannotAcquireLockException", CannotAcquireLockException.class.equals(e.getCause().getClass())); } /* but a fresh attempt must succeed */ try { Future<PathLock> concurrLockFuture2 = lockConcurrently(pathLocker, dir1, srcVersion2); Assert.assertNotNull(concurrLockFuture2.get(1, TimeUnit.SECONDS)); } catch (InterruptedException e) { throw e; } catch (ExecutionException e) { throw e; } catch (TimeoutException e) { throw e; } }
/** * First spawns another process that locks some test directory and then makes sure that the * present process'es {@link PathLocker} cannot lock the same directory at the same time. * * @throws Exception */ @Test public void anotherProcess() throws Exception { final Path keepRunnigFile = targetDirectory.resolve("PathLockerProcess-keep-running-" + UUID.randomUUID().toString()); Files.write( keepRunnigFile, "PathLockerProcess will run till this file exists".getBytes("utf-8")); final Path dirToLock = lockerDirectory.resolve(UUID.randomUUID().toString()); final SrcVersion srcVersion = SrcVersion.parse("1.2.3-SRC-revision-deadbeef"); final Path lockSuccessFile = dirToLock.resolve("lock-success.txt"); /* lock dirToLock from another process running on the same machine */ final String slfApiJar = getJarPath(org.slf4j.LoggerFactory.class); final String slfSimpleJar = getJarPath(org.slf4j.impl.StaticLoggerBinder.class); final String classPath = targetDirectory.resolve("classes").toString() + File.pathSeparator + targetDirectory.resolve("test-classes").toString() + File.pathSeparator + slfApiJar + File.pathSeparator + slfSimpleJar; final ShellCommand command = ShellCommand.builder() .executable(SrcdepsCoreUtils.getCurrentJavaExecutable()) .arguments( "-cp", classPath, PathLockerProcess.class.getName(), dirToLock.toString(), srcVersion.toString(), keepRunnigFile.toString(), lockSuccessFile.toString()) .workingDirectory(lockerDirectory) .build(); final ExecutorService executor = Executors.newSingleThreadExecutor(); Future<Boolean> future = executor.submit( new Callable<Boolean>() { @Override public Boolean call() throws Exception { Shell.execute(command).assertSuccess(); return true; } }); /* with some delay, the dirToLock eventually gets locked by PathLockerProcess */ final PathLocker<SrcVersion> pathLocker = new PathLocker<>(); final long timeoutSeconds = 5; final long lockDeadline = System.currentTimeMillis() + (timeoutSeconds * 1000); while (Files.notExists(lockSuccessFile) && System.currentTimeMillis() <= lockDeadline) { log.debug(pid + " Lock success file not there yet {}", lockSuccessFile); Thread.sleep(10); } Assert.assertTrue( String.format( "PathLockerProcess has not locked [%s] within [%d] seconds", dirToLock, timeoutSeconds), Files.exists(lockSuccessFile)); log.debug(pid + " Lock success file exists {}", lockSuccessFile); try (PathLock lock1 = pathLocker.lockDirectory(dirToLock, srcVersion)) { /* locked for the current thread */ Assert.fail( String.format( "The current thread and process should not be able to lock [%s]", dirToLock)); } catch (CannotAcquireLockException e) { /* expected */ } finally { /* Signal to PathLockerProcess that it should exit */ Files.deleteIfExists(keepRunnigFile); /* Ensure the PathLockerProcess exited cleanly */ future.get(5, TimeUnit.SECONDS); } /* PathLockerProcess must have unlocked at this point * and we must succeed in locking from here now */ try (PathLock lock1 = pathLocker.lockDirectory(dirToLock, srcVersion)) { Assert.assertTrue(lock1 != null); } }