# HG changeset patch # User Artem Tikhomirov # Date 1344625383 -7200 # Node ID d740edfff563d05c7b811e7ffc5bda5078101efb # Parent cdd53e5884aea44561cebd3ce1fc23b9f4e642a1 Provisional support for Mercurial lock mechanism diff -r cdd53e5884ae -r d740edfff563 src/org/tmatesoft/hg/internal/Lock.java --- /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 null 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(); + } +} diff -r cdd53e5884ae -r d740edfff563 src/org/tmatesoft/hg/repo/HgRepository.java --- a/src/org/tmatesoft/hg/repo/HgRepository.java Thu Aug 09 15:45:18 2012 +0200 +++ b/src/org/tmatesoft/hg/repo/HgRepository.java Fri Aug 10 21:03:03 2012 +0200 @@ -35,8 +35,10 @@ import org.tmatesoft.hg.internal.ByteArrayChannel; import org.tmatesoft.hg.internal.ConfigFile; import org.tmatesoft.hg.internal.DataAccessProvider; +import org.tmatesoft.hg.internal.Experimental; import org.tmatesoft.hg.internal.Filter; import org.tmatesoft.hg.internal.Internals; +import org.tmatesoft.hg.internal.Lock; import org.tmatesoft.hg.internal.RevlogStream; import org.tmatesoft.hg.internal.SubrepoManager; import org.tmatesoft.hg.util.CancelledException; @@ -420,7 +422,44 @@ } } } - + + private Lock wdLock, storeLock; + + /** + * PROVISIONAL CODE, DO NOT USE + * + * Access repository lock that covers non-store parts of the repository (dirstate, branches, etc - + * everything that has to do with working directory state). + * + * Note, the lock object returned merely gives access to lock mechanism. NO ACTUAL LOCKING IS DONE. + * Use {@link Lock#acquire()} to actually lock the repository. + * + * @return lock object, never null + */ + @Experimental(reason="WORK IN PROGRESS") + public Lock getWorkingDirLock() { + if (wdLock == null) { + synchronized (this) { + if (wdLock == null) { + wdLock = new Lock(new File(repoPathHelper.rewrite("wlock").toString())); + } + } + } + return wdLock; + } + + @Experimental(reason="WORK IN PROGRESS") + public Lock getStoreLock() { + if (storeLock == null) { + synchronized (this) { + if (storeLock == null) { + storeLock = new Lock(new File(repoPathHelper.rewrite("store/lock").toString())); + } + } + } + return storeLock; + } + /** * Access bookmarks-related functionality * @return facility to manage bookmarks, never null