aboutsummaryrefslogtreecommitdiffstats
path: root/library/python/filelock/__init__.py
blob: aea171410de2d845ee4d625be825361e9db3b8a2 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import errno
import logging
import os
import sys

import library.python.windows

logger = logging.getLogger(__name__) 

 
def set_close_on_exec(stream):
    if library.python.windows.on_win():
        library.python.windows.set_handle_information(stream, inherit=False)
    else:
        import fcntl
        fcntl.fcntl(stream, fcntl.F_SETFD, fcntl.FD_CLOEXEC)


class AbstractFileLock(object): 
 
    def __init__(self, path):
        self.path = path

    def acquire(self, blocking=True):
        raise NotImplementedError 

    def release(self):
        raise NotImplementedError 

    def __enter__(self):
        self.acquire()
        return self

    def __exit__(self, type, value, traceback):
        self.release()

 
class _NixFileLock(AbstractFileLock): 
 
    def __init__(self, path): 
        super(_NixFileLock, self).__init__(path) 
        from fcntl import flock, LOCK_EX, LOCK_UN, LOCK_NB
        self._locker = lambda lock, blocking: flock(lock, LOCK_EX if blocking else LOCK_EX | LOCK_NB)
        self._unlocker = lambda lock: flock(lock, LOCK_UN)
        self._lock = open(self.path, 'a')
        set_close_on_exec(self._lock)
 
    def acquire(self, blocking=True):
        import errno
        try:
            self._locker(self._lock, blocking)
        except IOError as e:
            if e.errno in (errno.EAGAIN, errno.EACCES) and not blocking:
                return False
            raise
        return True
 
    def release(self): 
        self._unlocker(self._lock)
 
    def __del__(self):
        if hasattr(self, "_lock"):
            self._lock.close()
 

class _WinFileLock(AbstractFileLock): 
    """ 
    Based on LockFile / UnlockFile from win32 API 
    https://msdn.microsoft.com/en-us/library/windows/desktop/aa365202(v=vs.85).aspx 
    """ 
 
    _LOCKED_BYTES_NUM = 1 
 
    def __init__(self, path): 
        super(_WinFileLock, self).__init__(path) 
        self._lock = None 
        try:
            with file(path, 'w') as lock_file:
                lock_file.write(" " * self._LOCKED_BYTES_NUM)
        except IOError as e:
            if e.errno != errno.EACCES or not os.path.isfile(path):
                raise
 
    def acquire(self, blocking=True):
        self._lock = open(self.path) 
        set_close_on_exec(self._lock)

        import time 
        locked = False
        while not locked: 
            locked = library.python.windows.lock_file(self._lock, 0, self._LOCKED_BYTES_NUM, raises=False)
            if locked:
                return True
            if blocking:
                time.sleep(.5) 
            else:
                return False
 
    def release(self): 
        if self._lock:
            library.python.windows.unlock_file(self._lock, 0, self._LOCKED_BYTES_NUM, raises=False)
            self._lock.close()
            self._lock = None
 
 
class FileLock(AbstractFileLock): 
 
    def __init__(self, path): 
        super(FileLock, self).__init__(path) 
 
        if sys.platform.startswith('win'): 
            self._lock = _WinFileLock(path) 
        else: 
            self._lock = _NixFileLock(path) 
 
    def acquire(self, blocking=True):
        logger.debug('Acquiring filelock (blocking=%s): %s', blocking, self.path)
        return self._lock.acquire(blocking)
 
    def release(self): 
        logger.debug('Ensuring filelock released: %s', self.path) 
        return self._lock.release()