PageRenderTime 41ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/mercurial/lock.py

https://bitbucket.org/mirror/mercurial/
Python | 154 lines | 144 code | 2 blank | 8 comment | 1 complexity | aea6ea01c033fca134b3b2c1efb5c988 MD5 | raw file
Possible License(s): GPL-2.0
  1. # lock.py - simple advisory locking scheme for mercurial
  2. #
  3. # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
  4. #
  5. # This software may be used and distributed according to the terms of the
  6. # GNU General Public License version 2 or any later version.
  7. import util, error
  8. import errno, os, socket, time
  9. import warnings
  10. class lock(object):
  11. '''An advisory lock held by one process to control access to a set
  12. of files. Non-cooperating processes or incorrectly written scripts
  13. can ignore Mercurial's locking scheme and stomp all over the
  14. repository, so don't do that.
  15. Typically used via localrepository.lock() to lock the repository
  16. store (.hg/store/) or localrepository.wlock() to lock everything
  17. else under .hg/.'''
  18. # lock is symlink on platforms that support it, file on others.
  19. # symlink is used because create of directory entry and contents
  20. # are atomic even over nfs.
  21. # old-style lock: symlink to pid
  22. # new-style lock: symlink to hostname:pid
  23. _host = None
  24. def __init__(self, vfs, file, timeout=-1, releasefn=None, desc=None):
  25. self.vfs = vfs
  26. self.f = file
  27. self.held = 0
  28. self.timeout = timeout
  29. self.releasefn = releasefn
  30. self.desc = desc
  31. self.postrelease = []
  32. self.pid = os.getpid()
  33. self.delay = self.lock()
  34. def __del__(self):
  35. if self.held:
  36. warnings.warn("use lock.release instead of del lock",
  37. category=DeprecationWarning,
  38. stacklevel=2)
  39. # ensure the lock will be removed
  40. # even if recursive locking did occur
  41. self.held = 1
  42. self.release()
  43. def lock(self):
  44. timeout = self.timeout
  45. while True:
  46. try:
  47. self.trylock()
  48. return self.timeout - timeout
  49. except error.LockHeld, inst:
  50. if timeout != 0:
  51. time.sleep(1)
  52. if timeout > 0:
  53. timeout -= 1
  54. continue
  55. raise error.LockHeld(errno.ETIMEDOUT, inst.filename, self.desc,
  56. inst.locker)
  57. def trylock(self):
  58. if self.held:
  59. self.held += 1
  60. return
  61. if lock._host is None:
  62. lock._host = socket.gethostname()
  63. lockname = '%s:%s' % (lock._host, self.pid)
  64. while not self.held:
  65. try:
  66. self.vfs.makelock(lockname, self.f)
  67. self.held = 1
  68. except (OSError, IOError), why:
  69. if why.errno == errno.EEXIST:
  70. locker = self.testlock()
  71. if locker is not None:
  72. raise error.LockHeld(errno.EAGAIN,
  73. self.vfs.join(self.f), self.desc,
  74. locker)
  75. else:
  76. raise error.LockUnavailable(why.errno, why.strerror,
  77. why.filename, self.desc)
  78. def testlock(self):
  79. """return id of locker if lock is valid, else None.
  80. If old-style lock, we cannot tell what machine locker is on.
  81. with new-style lock, if locker is on this machine, we can
  82. see if locker is alive. If locker is on this machine but
  83. not alive, we can safely break lock.
  84. The lock file is only deleted when None is returned.
  85. """
  86. try:
  87. locker = self.vfs.readlock(self.f)
  88. except (OSError, IOError), why:
  89. if why.errno == errno.ENOENT:
  90. return None
  91. raise
  92. try:
  93. host, pid = locker.split(":", 1)
  94. except ValueError:
  95. return locker
  96. if host != lock._host:
  97. return locker
  98. try:
  99. pid = int(pid)
  100. except ValueError:
  101. return locker
  102. if util.testpid(pid):
  103. return locker
  104. # if locker dead, break lock. must do this with another lock
  105. # held, or can race and break valid lock.
  106. try:
  107. l = lock(self.vfs, self.f + '.break', timeout=0)
  108. self.vfs.unlink(self.f)
  109. l.release()
  110. except error.LockError:
  111. return locker
  112. def release(self):
  113. """release the lock and execute callback function if any
  114. If the lock has been acquired multiple times, the actual release is
  115. delayed to the last release call."""
  116. if self.held > 1:
  117. self.held -= 1
  118. elif self.held == 1:
  119. self.held = 0
  120. if os.getpid() != self.pid:
  121. # we forked, and are not the parent
  122. return
  123. if self.releasefn:
  124. self.releasefn()
  125. try:
  126. self.vfs.unlink(self.f)
  127. except OSError:
  128. pass
  129. for callback in self.postrelease:
  130. callback()
  131. def release(*locks):
  132. for lock in locks:
  133. if lock is not None:
  134. lock.release()