Mercurial > hg4j
diff src/org/tmatesoft/hg/internal/Lock.java @ 486:d740edfff563
Provisional support for Mercurial lock mechanism
author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
---|---|
date | Fri, 10 Aug 2012 21:03:03 +0200 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/internal/Lock.java Fri Aug 10 21:03:03 2012 +0200 @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2012 TMate Software Ltd + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For information on how to redistribute this software under + * the terms of a license other than GNU General Public License + * contact TMate Software at support@hg4j.com + */ +package org.tmatesoft.hg.internal; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; + +import org.tmatesoft.hg.repo.HgInvalidStateException; + +/** + * NOT SAFE FOR MULTITHREAD USE! + * + * Unlike original mechanism, we don't use symlinks, rather files, as it's easier to implement + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +public class Lock { + /* + * Lock .hg/ except .hg/store/ .hg/wlock (HgRepository.repoPathHelper("wlock")) + * Lock .hg/store/ .hg/store/lock (HgRepository.repoPathHelper("store/lock") ???) + */ + + private final File lockFile; + private int use = 0; + private final int timeoutSeconds; + + public Lock(File lock) { + this(lock, 10); + } + + public Lock(File lock, int timeoutInSeconds) { + lockFile = lock; + timeoutSeconds = timeoutInSeconds; + } + + /** + * Tries to read lock file and supplies hostname:pid (or just pid) information from there + * @return <code>null</code> if no lock file available at the moment + */ + public String readLockInfo() { + if (lockFile.exists()) { + try { + byte[] bytes = read(lockFile); + if (bytes != null && bytes.length > 0) { + return new String(bytes); + } + } catch (Exception ex) { + // deliberately ignored + } + } + return null; + } + + public boolean isLocked() { + return use > 0; + } + + public void acquire() { + if (use > 0) { + use++; + return; + } + StringBuilder lockDescription = new StringBuilder(); + lockDescription.append(getHostname()); + lockDescription.append(':'); + lockDescription.append(getPid()); + byte[] bytes = lockDescription.toString().getBytes(); + long stopTime = System.currentTimeMillis() + timeoutSeconds*1000; + do { + synchronized(this) { + try { + if (!lockFile.exists()) { + write(lockFile, bytes); + use++; + return; + } + } catch (IOException ex) { + // deliberately ignored + } + try { + wait(1000); + } catch (InterruptedException ex) { + // deliberately ignored + } + } + + } while (System.currentTimeMillis() <= stopTime); + String msg = String.format("Failed to aquire lock, waited for %d seconds, present owner: '%s'", timeoutSeconds, readLockInfo()); + throw new HgInvalidStateException(msg); + } + + public void release() { + if (use == 0) { + throw new HgInvalidStateException(""); + } + use--; + if (use > 0) { + return; + } + // do release + lockFile.delete(); + } + + protected String getHostname() { + try { + return InetAddress.getLocalHost().getHostName(); + } catch (Exception ex) { + return "localhost"; + } + } + + protected int getPid() { + try { + // @see http://blog.igorminar.com/2007/03/how-java-application-can-discover-its.html + if (!Internals.runningOnWindows()) { + File f = new File("/proc/self"); + if (f.exists()) { + // /proc/self is a symlink to /proc/pid/ directory + return Integer.parseInt(f.getCanonicalFile().getName()); + } + } + String rtBean = ManagementFactory.getRuntimeMXBean().getName(); + int x; + if ((x = rtBean.indexOf('@')) != -1) { + return Integer.parseInt(rtBean.substring(0, x)); + } + return -1; + } catch (Exception ex) { + return -1; + } + } + + private static void write(File f, byte[] content) throws IOException { + FileOutputStream fos = new FileOutputStream(f); + fos.write(content); + fos.close(); + } + + private static byte[] read(File f) throws IOException { + FileChannel fc = new FileInputStream(f).getChannel(); + ByteBuffer bb = ByteBuffer.allocate(Internals.ltoi(fc.size())); + fc.read(bb); + fc.close(); + return bb.array(); + } +}