mercurial/lock.py
changeset 43076 2372284d9457
parent 38239 ead71b15efd5
child 43077 687b865b95ad
equal deleted inserted replaced
43075:57875cf423c9 43076:2372284d9457
    22     error,
    22     error,
    23     pycompat,
    23     pycompat,
    24     util,
    24     util,
    25 )
    25 )
    26 
    26 
    27 from .utils import (
    27 from .utils import procutil
    28     procutil,
    28 
    29 )
       
    30 
    29 
    31 def _getlockprefix():
    30 def _getlockprefix():
    32     """Return a string which is used to differentiate pid namespaces
    31     """Return a string which is used to differentiate pid namespaces
    33 
    32 
    34     It's useful to detect "dead" processes and remove stale locks with
    33     It's useful to detect "dead" processes and remove stale locks with
    41             result += '/%x' % os.stat('/proc/self/ns/pid').st_ino
    40             result += '/%x' % os.stat('/proc/self/ns/pid').st_ino
    42         except OSError as ex:
    41         except OSError as ex:
    43             if ex.errno not in (errno.ENOENT, errno.EACCES, errno.ENOTDIR):
    42             if ex.errno not in (errno.ENOENT, errno.EACCES, errno.ENOTDIR):
    44                 raise
    43                 raise
    45     return result
    44     return result
       
    45 
    46 
    46 
    47 @contextlib.contextmanager
    47 @contextlib.contextmanager
    48 def _delayedinterrupt():
    48 def _delayedinterrupt():
    49     """Block signal interrupt while doing something critical
    49     """Block signal interrupt while doing something critical
    50 
    50 
    58     assertedsigs = []
    58     assertedsigs = []
    59     blocked = False
    59     blocked = False
    60     orighandlers = {}
    60     orighandlers = {}
    61 
    61 
    62     def raiseinterrupt(num):
    62     def raiseinterrupt(num):
    63         if (num == getattr(signal, 'SIGINT', None) or
    63         if num == getattr(signal, 'SIGINT', None) or num == getattr(
    64             num == getattr(signal, 'CTRL_C_EVENT', None)):
    64             signal, 'CTRL_C_EVENT', None
       
    65         ):
    65             raise KeyboardInterrupt
    66             raise KeyboardInterrupt
    66         else:
    67         else:
    67             raise error.SignalInterrupt
    68             raise error.SignalInterrupt
       
    69 
    68     def catchterm(num, frame):
    70     def catchterm(num, frame):
    69         if blocked:
    71         if blocked:
    70             assertedsigs.append(num)
    72             assertedsigs.append(num)
    71         else:
    73         else:
    72             raiseinterrupt(num)
    74             raiseinterrupt(num)
    80                 orighandlers[num] = signal.getsignal(num)
    82                 orighandlers[num] = signal.getsignal(num)
    81         try:
    83         try:
    82             for num in orighandlers:
    84             for num in orighandlers:
    83                 signal.signal(num, catchterm)
    85                 signal.signal(num, catchterm)
    84         except ValueError:
    86         except ValueError:
    85             pass # in a thread? no luck
    87             pass  # in a thread? no luck
    86 
    88 
    87         blocked = True
    89         blocked = True
    88         yield
    90         yield
    89     finally:
    91     finally:
    90         # no simple way to reliably restore all signal handlers because
    92         # no simple way to reliably restore all signal handlers because
    93         blocked = False
    95         blocked = False
    94         try:
    96         try:
    95             for num, handler in orighandlers.items():
    97             for num, handler in orighandlers.items():
    96                 signal.signal(num, handler)
    98                 signal.signal(num, handler)
    97         except ValueError:
    99         except ValueError:
    98             pass # in a thread?
   100             pass  # in a thread?
    99 
   101 
   100     # re-raise interrupt exception if any, which may be shadowed by a new
   102     # re-raise interrupt exception if any, which may be shadowed by a new
   101     # interrupt occurred while re-raising the first one
   103     # interrupt occurred while re-raising the first one
   102     if assertedsigs:
   104     if assertedsigs:
   103         raiseinterrupt(assertedsigs[0])
   105         raiseinterrupt(assertedsigs[0])
       
   106 
   104 
   107 
   105 def trylock(ui, vfs, lockname, timeout, warntimeout, *args, **kwargs):
   108 def trylock(ui, vfs, lockname, timeout, warntimeout, *args, **kwargs):
   106     """return an acquired lock or raise an a LockHeld exception
   109     """return an acquired lock or raise an a LockHeld exception
   107 
   110 
   108     This function is responsible to issue warnings and or debug messages about
   111     This function is responsible to issue warnings and or debug messages about
   111     def printwarning(printer, locker):
   114     def printwarning(printer, locker):
   112         """issue the usual "waiting on lock" message through any channel"""
   115         """issue the usual "waiting on lock" message through any channel"""
   113         # show more details for new-style locks
   116         # show more details for new-style locks
   114         if ':' in locker:
   117         if ':' in locker:
   115             host, pid = locker.split(":", 1)
   118             host, pid = locker.split(":", 1)
   116             msg = (_("waiting for lock on %s held by process %r on host %r\n")
   119             msg = _(
   117                    % (pycompat.bytestr(l.desc), pycompat.bytestr(pid),
   120                 "waiting for lock on %s held by process %r on host %r\n"
   118                       pycompat.bytestr(host)))
   121             ) % (
   119         else:
   122                 pycompat.bytestr(l.desc),
   120             msg = (_("waiting for lock on %s held by %r\n")
   123                 pycompat.bytestr(pid),
   121                    % (l.desc, pycompat.bytestr(locker)))
   124                 pycompat.bytestr(host),
       
   125             )
       
   126         else:
       
   127             msg = _("waiting for lock on %s held by %r\n") % (
       
   128                 l.desc,
       
   129                 pycompat.bytestr(locker),
       
   130             )
   122         printer(msg)
   131         printer(msg)
   123 
   132 
   124     l = lock(vfs, lockname, 0, *args, dolock=False, **kwargs)
   133     l = lock(vfs, lockname, 0, *args, dolock=False, **kwargs)
   125 
   134 
   126     debugidx = 0 if (warntimeout and timeout) else -1
   135     debugidx = 0 if (warntimeout and timeout) else -1
   139             if delay == debugidx:
   148             if delay == debugidx:
   140                 printwarning(ui.debug, inst.locker)
   149                 printwarning(ui.debug, inst.locker)
   141             if delay == warningidx:
   150             if delay == warningidx:
   142                 printwarning(ui.warn, inst.locker)
   151                 printwarning(ui.warn, inst.locker)
   143             if timeout <= delay:
   152             if timeout <= delay:
   144                 raise error.LockHeld(errno.ETIMEDOUT, inst.filename,
   153                 raise error.LockHeld(
   145                                      l.desc, inst.locker)
   154                     errno.ETIMEDOUT, inst.filename, l.desc, inst.locker
       
   155                 )
   146             time.sleep(1)
   156             time.sleep(1)
   147             delay += 1
   157             delay += 1
   148 
   158 
   149     l.delay = delay
   159     l.delay = delay
   150     if l.delay:
   160     if l.delay:
   153         else:
   163         else:
   154             ui.debug("got lock after %d seconds\n" % l.delay)
   164             ui.debug("got lock after %d seconds\n" % l.delay)
   155     if l.acquirefn:
   165     if l.acquirefn:
   156         l.acquirefn()
   166         l.acquirefn()
   157     return l
   167     return l
       
   168 
   158 
   169 
   159 class lock(object):
   170 class lock(object):
   160     '''An advisory lock held by one process to control access to a set
   171     '''An advisory lock held by one process to control access to a set
   161     of files.  Non-cooperating processes or incorrectly written scripts
   172     of files.  Non-cooperating processes or incorrectly written scripts
   162     can ignore Mercurial's locking scheme and stomp all over the
   173     can ignore Mercurial's locking scheme and stomp all over the
   174     # old-style lock: symlink to pid
   185     # old-style lock: symlink to pid
   175     # new-style lock: symlink to hostname:pid
   186     # new-style lock: symlink to hostname:pid
   176 
   187 
   177     _host = None
   188     _host = None
   178 
   189 
   179     def __init__(self, vfs, fname, timeout=-1, releasefn=None, acquirefn=None,
   190     def __init__(
   180                  desc=None, inheritchecker=None, parentlock=None,
   191         self,
   181                  signalsafe=True, dolock=True):
   192         vfs,
       
   193         fname,
       
   194         timeout=-1,
       
   195         releasefn=None,
       
   196         acquirefn=None,
       
   197         desc=None,
       
   198         inheritchecker=None,
       
   199         parentlock=None,
       
   200         signalsafe=True,
       
   201         dolock=True,
       
   202     ):
   182         self.vfs = vfs
   203         self.vfs = vfs
   183         self.f = fname
   204         self.f = fname
   184         self.held = 0
   205         self.held = 0
   185         self.timeout = timeout
   206         self.timeout = timeout
   186         self.releasefn = releasefn
   207         self.releasefn = releasefn
   192         self._inherited = False
   213         self._inherited = False
   193         if signalsafe:
   214         if signalsafe:
   194             self._maybedelayedinterrupt = _delayedinterrupt
   215             self._maybedelayedinterrupt = _delayedinterrupt
   195         else:
   216         else:
   196             self._maybedelayedinterrupt = util.nullcontextmanager
   217             self._maybedelayedinterrupt = util.nullcontextmanager
   197         self.postrelease  = []
   218         self.postrelease = []
   198         self.pid = self._getpid()
   219         self.pid = self._getpid()
   199         if dolock:
   220         if dolock:
   200             self.delay = self.lock()
   221             self.delay = self.lock()
   201             if self.acquirefn:
   222             if self.acquirefn:
   202                 self.acquirefn()
   223                 self.acquirefn()
   207     def __exit__(self, exc_type, exc_value, exc_tb):
   228     def __exit__(self, exc_type, exc_value, exc_tb):
   208         self.release()
   229         self.release()
   209 
   230 
   210     def __del__(self):
   231     def __del__(self):
   211         if self.held:
   232         if self.held:
   212             warnings.warn(r"use lock.release instead of del lock",
   233             warnings.warn(
   213                     category=DeprecationWarning,
   234                 r"use lock.release instead of del lock",
   214                     stacklevel=2)
   235                 category=DeprecationWarning,
       
   236                 stacklevel=2,
       
   237             )
   215 
   238 
   216             # ensure the lock will be removed
   239             # ensure the lock will be removed
   217             # even if recursive locking did occur
   240             # even if recursive locking did occur
   218             self.held = 1
   241             self.held = 1
   219 
   242 
   233                 if timeout != 0:
   256                 if timeout != 0:
   234                     time.sleep(1)
   257                     time.sleep(1)
   235                     if timeout > 0:
   258                     if timeout > 0:
   236                         timeout -= 1
   259                         timeout -= 1
   237                     continue
   260                     continue
   238                 raise error.LockHeld(errno.ETIMEDOUT, inst.filename, self.desc,
   261                 raise error.LockHeld(
   239                                      inst.locker)
   262                     errno.ETIMEDOUT, inst.filename, self.desc, inst.locker
       
   263                 )
   240 
   264 
   241     def _trylock(self):
   265     def _trylock(self):
   242         if self.held:
   266         if self.held:
   243             self.held += 1
   267             self.held += 1
   244             return
   268             return
   266                         self._parentheld = True
   290                         self._parentheld = True
   267                         self.held = 1
   291                         self.held = 1
   268                         return
   292                         return
   269                     locker = self._testlock(locker)
   293                     locker = self._testlock(locker)
   270                     if locker is not None:
   294                     if locker is not None:
   271                         raise error.LockHeld(errno.EAGAIN,
   295                         raise error.LockHeld(
   272                                              self.vfs.join(self.f), self.desc,
   296                             errno.EAGAIN,
   273                                              locker)
   297                             self.vfs.join(self.f),
       
   298                             self.desc,
       
   299                             locker,
       
   300                         )
   274                 else:
   301                 else:
   275                     raise error.LockUnavailable(why.errno, why.strerror,
   302                     raise error.LockUnavailable(
   276                                                 why.filename, self.desc)
   303                         why.errno, why.strerror, why.filename, self.desc
       
   304                     )
   277 
   305 
   278         if not self.held:
   306         if not self.held:
   279             # use empty locker to mean "busy for frequent lock/unlock
   307             # use empty locker to mean "busy for frequent lock/unlock
   280             # by many processes"
   308             # by many processes"
   281             raise error.LockHeld(errno.EAGAIN,
   309             raise error.LockHeld(
   282                                  self.vfs.join(self.f), self.desc, "")
   310                 errno.EAGAIN, self.vfs.join(self.f), self.desc, ""
       
   311             )
   283 
   312 
   284     def _readlock(self):
   313     def _readlock(self):
   285         """read lock and return its value
   314         """read lock and return its value
   286 
   315 
   287         Returns None if no lock exists, pid for old-style locks, and host:pid
   316         Returns None if no lock exists, pid for old-style locks, and host:pid
   340         Communicating this string to the subprocess needs to be done separately
   369         Communicating this string to the subprocess needs to be done separately
   341         -- typically by an environment variable.
   370         -- typically by an environment variable.
   342         """
   371         """
   343         if not self.held:
   372         if not self.held:
   344             raise error.LockInheritanceContractViolation(
   373             raise error.LockInheritanceContractViolation(
   345                 'inherit can only be called while lock is held')
   374                 'inherit can only be called while lock is held'
       
   375             )
   346         if self._inherited:
   376         if self._inherited:
   347             raise error.LockInheritanceContractViolation(
   377             raise error.LockInheritanceContractViolation(
   348                 'inherit cannot be called while lock is already inherited')
   378                 'inherit cannot be called while lock is already inherited'
       
   379             )
   349         if self._inheritchecker is not None:
   380         if self._inheritchecker is not None:
   350             self._inheritchecker()
   381             self._inheritchecker()
   351         if self.releasefn:
   382         if self.releasefn:
   352             self.releasefn()
   383             self.releasefn()
   353         if self._parentheld:
   384         if self._parentheld:
   389                 for callback in self.postrelease:
   420                 for callback in self.postrelease:
   390                     callback()
   421                     callback()
   391                 # Prevent double usage and help clear cycles.
   422                 # Prevent double usage and help clear cycles.
   392                 self.postrelease = None
   423                 self.postrelease = None
   393 
   424 
       
   425 
   394 def release(*locks):
   426 def release(*locks):
   395     for lock in locks:
   427     for lock in locks:
   396         if lock is not None:
   428         if lock is not None:
   397             lock.release()
   429             lock.release()