001package co.codewizards.cloudstore.core.io; 002 003import static co.codewizards.cloudstore.core.util.Util.*; 004 005import java.io.File; 006import java.io.IOException; 007import java.nio.channels.FileLock; 008import java.util.HashMap; 009import java.util.Map; 010 011/** 012 * Factory creating {@link LockFile} instances. 013 * <p> 014 * All methods of this class are thread-safe. 015 * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co 016 */ 017public class LockFileFactory { 018 019 private static class LockFileFactoryHolder { 020 public static final LockFileFactory instance = new LockFileFactory(); 021 } 022 023 protected final Object mutex = new Object(); 024 025 protected LockFileFactory() { } 026 027 public static LockFileFactory getInstance() { 028 return LockFileFactoryHolder.instance; 029 } 030 031 private Map<File, LockFileImpl> file2LockFileImpl = new HashMap<File, LockFileImpl>(); 032 033 /** 034 * Acquire an exclusive lock on the specified file. 035 * <p> 036 * <b>Important:</b> You <i>must</i> invoke {@link LockFile#release()} on the returned object! Use a try-finally-block 037 * to ensure it: 038 * <pre> LockFile lockFile = LockFileFactory.acquire(theFile, theTimeout); 039 * try { 040 * // do something 041 * } finally { 042 * lockFile.release(); 043 * }</pre> 044 * If the JVM is interrupted or shut down before {@code release()}, the file-lock is released by the 045 * operating system, but a missing {@code release()} causes the file to be locked for the entire remaining runtime 046 * of the JVM! 047 * <p> 048 * <b>Important:</b> This is <i>not</i> usable for the synchronization of multiple threads within the same Java virtual machine! 049 * Multiple {@link LockFile}s on the same {@link File} are possible within the same JVM! This locking mechanism 050 * only locks against separate processes! Since this implementation is based on {@link FileLock}, please consult 051 * its Javadoc for further information. 052 * <p> 053 * Multiple invocations of this method on the same given {@code file} return multiple different {@code LockFile} instances. 054 * The actual lock is held until the last {@code LockFile} instance was {@linkplain LockFile#release() released}. 055 * <p> 056 * This method is thread-safe. 057 * @param file the file to be locked. Must not be <code>null</code>. If this file does not exist in the file system, 058 * it is created by this method. 059 * @param timeoutMillis the timeout to wait for the lock to be acquired in milliseconds. The value 0 means to 060 * wait forever. 061 * @return the {@code LockFile}. Never <code>null</code>. This <i>must</i> be {@linkplain FileLock#release() released} 062 * (use a try-finally-block)! 063 * @throws TimeoutException if the {@code LockFile} could not be acquired within the timeout specified by {@code timeoutMillis}. 064 * @see LockFile#release() 065 */ 066 public LockFile acquire(File file, long timeoutMillis) throws TimeoutException { 067 assertNotNull("file", file); 068 try { 069 file = file.getCanonicalFile(); 070 } catch (IOException e) { 071 throw new RuntimeException(e); 072 } 073 074 LockFileImpl lockFileImpl; 075 synchronized (this) { 076 lockFileImpl = file2LockFileImpl.get(file); 077 if (lockFileImpl == null) { 078 lockFileImpl = new LockFileImpl(this, file); 079 file2LockFileImpl.put(file, lockFileImpl); 080 } 081 lockFileImpl.acquireRunningCounter.incrementAndGet(); 082 } 083 try { 084 // The following must NOT be synchronized! Otherwise we might wait here longer than the current timeout 085 // (as long as the longest timeout of all acquire methods running concurrently). 086 lockFileImpl.acquire(timeoutMillis); 087 } finally { 088 synchronized (this) { 089 int lockCounter = lockFileImpl.getLockCounter(); 090 int acquireRunningCounter = lockFileImpl.acquireRunningCounter.decrementAndGet(); 091 092 if (lockCounter < 1 && acquireRunningCounter < 1) 093 file2LockFileImpl.remove(file); 094 } 095 } 096 return new LockFileProxy(lockFileImpl); 097 } 098 099 protected synchronized void postRelease(LockFileImpl lockFileImpl) { 100 LockFileImpl lockFileImpl2 = file2LockFileImpl.get(lockFileImpl.getFile()); 101 if (lockFileImpl != lockFileImpl2) 102 throw new IllegalArgumentException("Unknown lockFileImpl instance (not managed by this registry): " + lockFileImpl); 103 104 int lockCounter = lockFileImpl.getLockCounter(); 105 int acquireRunningCounter = lockFileImpl.acquireRunningCounter.decrementAndGet(); 106 107 if (lockCounter < 1 && acquireRunningCounter < 1) 108 file2LockFileImpl.remove(lockFileImpl.getFile()); 109 } 110 111}