tikhomirov@486: /* tikhomirov@605: * Copyright (c) 2012-2013 TMate Software Ltd tikhomirov@486: * tikhomirov@486: * This program is free software; you can redistribute it and/or modify tikhomirov@486: * it under the terms of the GNU General Public License as published by tikhomirov@486: * the Free Software Foundation; version 2 of the License. tikhomirov@486: * tikhomirov@486: * This program is distributed in the hope that it will be useful, tikhomirov@486: * but WITHOUT ANY WARRANTY; without even the implied warranty of tikhomirov@486: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the tikhomirov@486: * GNU General Public License for more details. tikhomirov@486: * tikhomirov@486: * For information on how to redistribute this software under tikhomirov@486: * the terms of a license other than GNU General Public License tikhomirov@486: * contact TMate Software at support@hg4j.com tikhomirov@486: */ tikhomirov@487: package org.tmatesoft.hg.repo; tikhomirov@486: tikhomirov@486: import java.io.File; tikhomirov@486: import java.io.FileInputStream; tikhomirov@486: import java.io.FileOutputStream; tikhomirov@486: import java.io.IOException; tikhomirov@486: import java.lang.management.ManagementFactory; tikhomirov@486: import java.net.InetAddress; tikhomirov@486: import java.nio.ByteBuffer; tikhomirov@486: import java.nio.channels.FileChannel; tikhomirov@486: tikhomirov@505: import org.tmatesoft.hg.core.HgRepositoryLockException; tikhomirov@487: import org.tmatesoft.hg.internal.Internals; tikhomirov@486: tikhomirov@486: /** tikhomirov@489: *

Usage: tikhomirov@489: *

tikhomirov@489:  * HgRepositoryLock lock = hgRepo.getWorkingDirLock();
tikhomirov@489:  * try {
tikhomirov@489:  *     // Actually lock the repo
tikhomirov@489:  *     lock.acquire();
tikhomirov@489:  *     ///
tikhomirov@489:  *     // do whatever modifies working directory
tikhomirov@489:  *     ...
tikhomirov@489:  * } finally {
tikhomirov@489:  *     if (lock.isLocked()) {
tikhomirov@489:  *         // this check is needed not to release() 
tikhomirov@489:  *         // erroneously in case acquire() failed (e.g. due to timeout)
tikhomirov@489:  *         lock.release();
tikhomirov@489:  *     }
tikhomirov@489:  * 
tikhomirov@489:  * 
tikhomirov@489: * tikhomirov@486: * Unlike original mechanism, we don't use symlinks, rather files, as it's easier to implement tikhomirov@486: * tikhomirov@613: *

tikhomirov@613: * NOT SAFE FOR MULTITHREAD USE! tikhomirov@613: * tikhomirov@489: * @see http://code.google.com/p/hg4j/issues/detail?id=35 tikhomirov@626: * @since 1.1 tikhomirov@486: * @author Artem Tikhomirov tikhomirov@486: * @author TMate Software Ltd. tikhomirov@486: */ tikhomirov@487: public class HgRepositoryLock { tikhomirov@486: /* tikhomirov@488: * Lock .hg/ except .hg/store/ .hg/wlock (new File(hgRepo.getRepoRoot(),"wlock")) tikhomirov@488: * Lock .hg/store/ .hg/store/lock (HgRepository.repoPathHelper("lock")) tikhomirov@486: */ tikhomirov@486: tikhomirov@486: private final File lockFile; tikhomirov@486: private int use = 0; tikhomirov@486: private final int timeoutSeconds; tikhomirov@486: tikhomirov@487: HgRepositoryLock(File lock, int timeoutInSeconds) { tikhomirov@486: lockFile = lock; tikhomirov@486: timeoutSeconds = timeoutInSeconds; tikhomirov@486: } tikhomirov@486: tikhomirov@486: /** tikhomirov@486: * Tries to read lock file and supplies hostname:pid (or just pid) information from there tikhomirov@486: * @return null if no lock file available at the moment tikhomirov@486: */ tikhomirov@486: public String readLockInfo() { tikhomirov@486: if (lockFile.exists()) { tikhomirov@486: try { tikhomirov@486: byte[] bytes = read(lockFile); tikhomirov@486: if (bytes != null && bytes.length > 0) { tikhomirov@486: return new String(bytes); tikhomirov@486: } tikhomirov@486: } catch (Exception ex) { tikhomirov@486: // deliberately ignored tikhomirov@486: } tikhomirov@486: } tikhomirov@486: return null; tikhomirov@486: } tikhomirov@486: tikhomirov@489: /** tikhomirov@489: * @return true if we hold the lock tikhomirov@489: */ tikhomirov@486: public boolean isLocked() { tikhomirov@486: return use > 0; tikhomirov@486: } tikhomirov@486: tikhomirov@489: /** tikhomirov@489: * Perform actual locking. Waits for timeout (if specified at construction time) tikhomirov@605: * before throwing {@link HgRepositoryLockException} in case lock is not available tikhomirov@489: * immediately. tikhomirov@489: * tikhomirov@489: *

Multiple calls are possible, but corresponding number of {@link #release()} tikhomirov@489: * calls shall be made. tikhomirov@505: * @throws HgRepositoryLockException if failed to grab a lock tikhomirov@489: */ tikhomirov@505: public void acquire() throws HgRepositoryLockException { tikhomirov@486: if (use > 0) { tikhomirov@486: use++; tikhomirov@486: return; tikhomirov@486: } tikhomirov@486: StringBuilder lockDescription = new StringBuilder(); tikhomirov@486: lockDescription.append(getHostname()); tikhomirov@486: lockDescription.append(':'); tikhomirov@486: lockDescription.append(getPid()); tikhomirov@486: byte[] bytes = lockDescription.toString().getBytes(); tikhomirov@487: long stopTime = timeoutSeconds < 0 ? -1 : (System.currentTimeMillis() + timeoutSeconds*1000); tikhomirov@486: do { tikhomirov@486: synchronized(this) { tikhomirov@486: try { tikhomirov@631: if (lockFile.createNewFile()) { tikhomirov@486: write(lockFile, bytes); tikhomirov@486: use++; tikhomirov@486: return; tikhomirov@486: } tikhomirov@486: } catch (IOException ex) { tikhomirov@486: // deliberately ignored tikhomirov@486: } tikhomirov@486: try { tikhomirov@486: wait(1000); tikhomirov@486: } catch (InterruptedException ex) { tikhomirov@486: // deliberately ignored tikhomirov@486: } tikhomirov@486: } tikhomirov@486: tikhomirov@487: } while (stopTime == -1/*no timeout*/ || System.currentTimeMillis() <= stopTime); tikhomirov@486: String msg = String.format("Failed to aquire lock, waited for %d seconds, present owner: '%s'", timeoutSeconds, readLockInfo()); tikhomirov@505: throw new HgRepositoryLockException(msg); tikhomirov@486: } tikhomirov@486: tikhomirov@489: /** tikhomirov@489: * Release lock we own tikhomirov@505: * @throws HgRepositoryLockException if there's no evidence we do own a lock tikhomirov@489: */ tikhomirov@505: public void release() throws HgRepositoryLockException { tikhomirov@486: if (use == 0) { tikhomirov@505: throw new HgRepositoryLockException("Lock is not held!"); tikhomirov@486: } tikhomirov@486: use--; tikhomirov@486: if (use > 0) { tikhomirov@486: return; tikhomirov@486: } tikhomirov@486: // do release tikhomirov@486: lockFile.delete(); tikhomirov@486: } tikhomirov@486: tikhomirov@486: protected String getHostname() { tikhomirov@486: try { tikhomirov@486: return InetAddress.getLocalHost().getHostName(); tikhomirov@486: } catch (Exception ex) { tikhomirov@486: return "localhost"; tikhomirov@486: } tikhomirov@486: } tikhomirov@486: tikhomirov@486: protected int getPid() { tikhomirov@486: try { tikhomirov@486: // @see http://blog.igorminar.com/2007/03/how-java-application-can-discover-its.html tikhomirov@486: if (!Internals.runningOnWindows()) { tikhomirov@486: File f = new File("/proc/self"); tikhomirov@486: if (f.exists()) { tikhomirov@486: // /proc/self is a symlink to /proc/pid/ directory tikhomirov@486: return Integer.parseInt(f.getCanonicalFile().getName()); tikhomirov@486: } tikhomirov@486: } tikhomirov@486: String rtBean = ManagementFactory.getRuntimeMXBean().getName(); tikhomirov@486: int x; tikhomirov@486: if ((x = rtBean.indexOf('@')) != -1) { tikhomirov@486: return Integer.parseInt(rtBean.substring(0, x)); tikhomirov@486: } tikhomirov@486: return -1; tikhomirov@486: } catch (Exception ex) { tikhomirov@486: return -1; tikhomirov@486: } tikhomirov@486: } tikhomirov@486: tikhomirov@486: private static void write(File f, byte[] content) throws IOException { tikhomirov@486: FileOutputStream fos = new FileOutputStream(f); tikhomirov@486: fos.write(content); tikhomirov@486: fos.close(); tikhomirov@486: } tikhomirov@486: tikhomirov@486: private static byte[] read(File f) throws IOException { tikhomirov@619: FileInputStream fis = new FileInputStream(f); tikhomirov@619: FileChannel fc = fis.getChannel(); tikhomirov@486: ByteBuffer bb = ByteBuffer.allocate(Internals.ltoi(fc.size())); tikhomirov@486: fc.read(bb); tikhomirov@619: fis.close(); tikhomirov@486: return bb.array(); tikhomirov@486: } tikhomirov@486: }