package com.zillabyte.motherbrain.utils; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.channels.ClosedByInterruptException; import java.nio.channels.FileLock; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Semaphore; import com.google.common.collect.Maps; public class FileLockUtil { static final ConcurrentMap<String, Semaphore> _jvmLocks = Maps.newConcurrentMap(); public static class MultiLock implements AutoCloseable { FileLock _fileLock; FileOutputStream _stream; Semaphore _jvmLock; MultiLock() {} public void release() throws IOException, InterruptedException { try { close(); } catch (IOException e) { throw Utils.handleInterruptible(e); } } @Override public void close() throws IOException { InterruptedException interrupt = null; while(true) { try { _fileLock.release(); break; } catch (IOException e) { try { throw Utils.handleInterruptible(e); } catch (InterruptedException e1) { // Should not throw InterruptedException in AutoCloseable if (interrupt == null) { interrupt = e1; } else { interrupt.addSuppressed(e1); } } } } try { while(true) { try { _stream.close(); break; } catch (IOException e) { try { throw Utils.handleInterruptible(e); } catch (InterruptedException e1) { // Should not throw InterruptedException in AutoCloseable if (interrupt == null) { interrupt = e1; } else { interrupt.addSuppressed(e1); } } } } } finally { // Even if we can't close (possibly leak) the stream, it should be // safe to release the JVM lock. _jvmLock.release(); } if (interrupt != null) { Thread.currentThread().interrupt(); throw (ClosedByInterruptException) new ClosedByInterruptException().initCause(interrupt); } } } public static MultiLock lock(String lockFile) throws InterruptedException, IOException { return lock(new File(lockFile)); } public static MultiLock lock(File lockFile) throws InterruptedException, IOException { // Init the jvm lock _jvmLocks.putIfAbsent(lockFile.getAbsolutePath(), new Semaphore(1)); final Semaphore jvmLock = _jvmLocks.get(lockFile.getAbsolutePath()); // Acquire the JVM lock. jvmLock.acquire(); // Success, we have a jvm lock. Now can we get a file lock? lockFile.getParentFile().mkdirs(); // Create the file.. if (lockFile.exists() == false) { try { lockFile.createNewFile(); lockFile.deleteOnExit(); } catch (IOException e) { // Error creating the file, release the lock and rethrow. jvmLock.release(); throw Utils.handleInterruptible(e); } } final MultiLock mlock = new MultiLock(); try { mlock._stream = new FileOutputStream(lockFile); } catch (FileNotFoundException e) { // We could try creating the file again, but perhaps better to fail // here to avoid potential DOS. Instead, release the lock and rethrow. jvmLock.release(); throw e; } try { try { mlock._fileLock = mlock._stream.getChannel().lock(); } catch(java.nio.channels.FileLockInterruptionException e) { throw (InterruptedException) new InterruptedException().initCause(e); } } catch(java.nio.channels.OverlappingFileLockException e) { // The JVM has a lock that we don't know about. Clean up // and rethrow. InterruptedException interrupt = null; try { while (true) { try { // Try to close the stream. mlock._stream.close(); break; } catch (IOException e1) { try { throw Utils.handleInterruptible(e1); } catch (InterruptedException e2) { // If we were interrupted, retry the close. interrupt = e2; } } } } finally { // Release the lock. jvmLock.release(); if (interrupt != null) { throw interrupt; } } // Rethrow throw e; } catch (IOException e) { InterruptedException interrupt = null; try { while (true) { try { // Try to close the stream. mlock._stream.close(); break; } catch (IOException e1) { try { throw Utils.handleInterruptible(e1); } catch (InterruptedException e2) { // If we were interrupted, retry the close. interrupt = e2; } catch (IOException e2) { mlock._fileLock.close(); } } } } finally { // Release the lock. jvmLock.release(); if (interrupt != null) { throw interrupt; } } // Rethrow. throw Utils.handleInterruptible(e); } // Success! mlock._jvmLock = jvmLock; return mlock; } }