aboutsummaryrefslogtreecommitdiffstats
path: root/library/python/filelock/__init__.py
blob: fae89713d36cebb7adcd5dec3b1c0e51be0542e1 (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 open(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()