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@487: import org.tmatesoft.hg.internal.Internals; tikhomirov@486: tikhomirov@486: /** tikhomirov@486: * NOT SAFE FOR MULTITHREAD USE! tikhomirov@486: * tikhomirov@486: * Unlike original mechanism, we don't use symlinks, rather files, as it's easier to implement tikhomirov@486: * 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@486: public boolean isLocked() { tikhomirov@486: return use > 0; tikhomirov@486: } tikhomirov@486: 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@486: public void release() { tikhomirov@486: if (use == 0) { tikhomirov@486: throw new HgInvalidStateException(""); 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: }