| 
33
 | 
     1 from __future__ import absolute_import
 | 
| 
 | 
     2 
 | 
| 
 | 
     3 import time
 | 
| 
 | 
     4 import os
 | 
| 
 | 
     5 
 | 
| 
 | 
     6 from . import (LockBase, LockFailed, NotLocked, NotMyLock, LockTimeout,
 | 
| 
 | 
     7                AlreadyLocked)
 | 
| 
 | 
     8 
 | 
| 
 | 
     9 class LinkLockFile(LockBase):
 | 
| 
 | 
    10     """Lock access to a file using atomic property of link(2).
 | 
| 
 | 
    11 
 | 
| 
 | 
    12     >>> lock = LinkLockFile('somefile')
 | 
| 
 | 
    13     >>> lock = LinkLockFile('somefile', threaded=False)
 | 
| 
 | 
    14     """
 | 
| 
 | 
    15 
 | 
| 
 | 
    16     def acquire(self, timeout=None):
 | 
| 
 | 
    17         try:
 | 
| 
 | 
    18             open(self.unique_name, "wb").close()
 | 
| 
 | 
    19         except IOError:
 | 
| 
 | 
    20             raise LockFailed("failed to create %s" % self.unique_name)
 | 
| 
 | 
    21 
 | 
| 
 | 
    22         timeout = timeout is not None and timeout or self.timeout
 | 
| 
 | 
    23         end_time = time.time()
 | 
| 
 | 
    24         if timeout is not None and timeout > 0:
 | 
| 
 | 
    25             end_time += timeout
 | 
| 
 | 
    26 
 | 
| 
 | 
    27         while True:
 | 
| 
 | 
    28             # Try and create a hard link to it.
 | 
| 
 | 
    29             try:
 | 
| 
 | 
    30                 os.link(self.unique_name, self.lock_file)
 | 
| 
 | 
    31             except OSError:
 | 
| 
 | 
    32                 # Link creation failed.  Maybe we've double-locked?
 | 
| 
 | 
    33                 nlinks = os.stat(self.unique_name).st_nlink
 | 
| 
 | 
    34                 if nlinks == 2:
 | 
| 
 | 
    35                     # The original link plus the one I created == 2.  We're
 | 
| 
 | 
    36                     # good to go.
 | 
| 
 | 
    37                     return
 | 
| 
 | 
    38                 else:
 | 
| 
 | 
    39                     # Otherwise the lock creation failed.
 | 
| 
 | 
    40                     if timeout is not None and time.time() > end_time:
 | 
| 
 | 
    41                         os.unlink(self.unique_name)
 | 
| 
 | 
    42                         if timeout > 0:
 | 
| 
 | 
    43                             raise LockTimeout("Timeout waiting to acquire"
 | 
| 
 | 
    44                                               " lock for %s" %
 | 
| 
 | 
    45                                               self.path)
 | 
| 
 | 
    46                         else:
 | 
| 
 | 
    47                             raise AlreadyLocked("%s is already locked" %
 | 
| 
 | 
    48                                                 self.path)
 | 
| 
 | 
    49                     time.sleep(timeout is not None and timeout/10 or 0.1)
 | 
| 
 | 
    50             else:
 | 
| 
 | 
    51                 # Link creation succeeded.  We're good to go.
 | 
| 
 | 
    52                 return
 | 
| 
 | 
    53 
 | 
| 
 | 
    54     def release(self):
 | 
| 
 | 
    55         if not self.is_locked():
 | 
| 
 | 
    56             raise NotLocked("%s is not locked" % self.path)
 | 
| 
 | 
    57         elif not os.path.exists(self.unique_name):
 | 
| 
 | 
    58             raise NotMyLock("%s is locked, but not by me" % self.path)
 | 
| 
 | 
    59         os.unlink(self.unique_name)
 | 
| 
 | 
    60         os.unlink(self.lock_file)
 | 
| 
 | 
    61 
 | 
| 
 | 
    62     def is_locked(self):
 | 
| 
 | 
    63         return os.path.exists(self.lock_file)
 | 
| 
 | 
    64 
 | 
| 
 | 
    65     def i_am_locking(self):
 | 
| 
 | 
    66         return (self.is_locked() and
 | 
| 
 | 
    67                 os.path.exists(self.unique_name) and
 | 
| 
 | 
    68                 os.stat(self.unique_name).st_nlink == 2)
 | 
| 
 | 
    69 
 | 
| 
 | 
    70     def break_lock(self):
 | 
| 
 | 
    71         if os.path.exists(self.lock_file):
 | 
| 
 | 
    72             os.unlink(self.lock_file)
 | 
| 
 | 
    73 
 |