tikhomirov@486: /* tikhomirov@486: * Copyright (c) 2012 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@489: import org.tmatesoft.hg.internal.Experimental; tikhomirov@487: import org.tmatesoft.hg.internal.Internals; tikhomirov@486: tikhomirov@486: /** tikhomirov@486: * NOT SAFE FOR MULTITHREAD USE! 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@489: * @see http://code.google.com/p/hg4j/issues/detail?id=35 tikhomirov@486: * @author Artem Tikhomirov tikhomirov@486: * @author TMate Software Ltd. tikhomirov@486: */ tikhomirov@489: @Experimental(reason="Work in progress") 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@489: * before throwing {@link HgInvalidStateException} 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@489: */ tikhomirov@486: public void acquire() { 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@486: if (!lockFile.exists()) { 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@486: throw new HgInvalidStateException(msg); tikhomirov@486: } tikhomirov@486: tikhomirov@489: /** tikhomirov@489: * Release lock we own tikhomirov@489: */ tikhomirov@486: public void release() { tikhomirov@486: if (use == 0) { tikhomirov@489: throw new HgInvalidStateException("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@486: FileChannel fc = new FileInputStream(f).getChannel(); tikhomirov@486: ByteBuffer bb = ByteBuffer.allocate(Internals.ltoi(fc.size())); tikhomirov@486: fc.read(bb); tikhomirov@486: fc.close(); tikhomirov@486: return bb.array(); tikhomirov@486: } tikhomirov@486: }