root/trunk/twisted/python/lockfile.py

Revision 33074, 6.3 KB (checked in by exarkun, 7 months ago)

On new Python/Windows combos, avoid trying to make symlinks in twisted.python.lockfile.

Author: antoine
Reviewer: exarkun
Fixes: #5362

Replace the feature test for os.symlink with a platform check. This makes
the code use the special Windows implementation even when Python exposes an
os.symlink API, which newer versions do on recent versions of Windows (but
which requires additional privileges to actually use).

Line 
1# -*- test-case-name: twisted.test.test_lockfile -*-
2# Copyright (c) 2005 Divmod, Inc.
3# Copyright (c) Twisted Matrix Laboratories.
4# See LICENSE for details.
5
6"""
7Filesystem-based interprocess mutex.
8"""
9
10__metaclass__ = type
11
12import errno, os
13
14from time import time as _uniquefloat
15
16from twisted.python.runtime import platform
17
18def unique():
19    return str(long(_uniquefloat() * 1000))
20
21from os import rename
22if not platform.isWindows():
23    from os import kill
24    from os import symlink
25    from os import readlink
26    from os import remove as rmlink
27    _windows = False
28else:
29    _windows = True
30
31    try:
32        from win32api import OpenProcess
33        import pywintypes
34    except ImportError:
35        kill = None
36    else:
37        ERROR_ACCESS_DENIED = 5
38        ERROR_INVALID_PARAMETER = 87
39
40        def kill(pid, signal):
41            try:
42                OpenProcess(0, 0, pid)
43            except pywintypes.error, e:
44                if e.args[0] == ERROR_ACCESS_DENIED:
45                    return
46                elif e.args[0] == ERROR_INVALID_PARAMETER:
47                    raise OSError(errno.ESRCH, None)
48                raise
49            else:
50                raise RuntimeError("OpenProcess is required to fail.")
51
52    _open = file
53
54    # XXX Implement an atomic thingamajig for win32
55    def symlink(value, filename):
56        newlinkname = filename+"."+unique()+'.newlink'
57        newvalname = os.path.join(newlinkname,"symlink")
58        os.mkdir(newlinkname)
59        f = _open(newvalname,'wcb')
60        f.write(value)
61        f.flush()
62        f.close()
63        try:
64            rename(newlinkname, filename)
65        except:
66            os.remove(newvalname)
67            os.rmdir(newlinkname)
68            raise
69
70    def readlink(filename):
71        try:
72            fObj = _open(os.path.join(filename,'symlink'), 'rb')
73        except IOError, e:
74            if e.errno == errno.ENOENT or e.errno == errno.EIO:
75                raise OSError(e.errno, None)
76            raise
77        else:
78            result = fObj.read()
79            fObj.close()
80            return result
81
82    def rmlink(filename):
83        os.remove(os.path.join(filename, 'symlink'))
84        os.rmdir(filename)
85
86
87
88class FilesystemLock:
89    """
90    A mutex.
91
92    This relies on the filesystem property that creating
93    a symlink is an atomic operation and that it will
94    fail if the symlink already exists.  Deleting the
95    symlink will release the lock.
96
97    @ivar name: The name of the file associated with this lock.
98
99    @ivar clean: Indicates whether this lock was released cleanly by its
100        last owner.  Only meaningful after C{lock} has been called and
101        returns True.
102
103    @ivar locked: Indicates whether the lock is currently held by this
104        object.
105    """
106
107    clean = None
108    locked = False
109
110    def __init__(self, name):
111        self.name = name
112
113
114    def lock(self):
115        """
116        Acquire this lock.
117
118        @rtype: C{bool}
119        @return: True if the lock is acquired, false otherwise.
120
121        @raise: Any exception os.symlink() may raise, other than
122        EEXIST.
123        """
124        clean = True
125        while True:
126            try:
127                symlink(str(os.getpid()), self.name)
128            except OSError, e:
129                if _windows and e.errno in (errno.EACCES, errno.EIO):
130                    # The lock is in the middle of being deleted because we're
131                    # on Windows where lock removal isn't atomic.  Give up, we
132                    # don't know how long this is going to take.
133                    return False
134                if e.errno == errno.EEXIST:
135                    try:
136                        pid = readlink(self.name)
137                    except OSError, e:
138                        if e.errno == errno.ENOENT:
139                            # The lock has vanished, try to claim it in the
140                            # next iteration through the loop.
141                            continue
142                        raise
143                    except IOError, e:
144                        if _windows and e.errno == errno.EACCES:
145                            # The lock is in the middle of being
146                            # deleted because we're on Windows where
147                            # lock removal isn't atomic.  Give up, we
148                            # don't know how long this is going to
149                            # take.
150                            return False
151                        raise
152                    try:
153                        if kill is not None:
154                            kill(int(pid), 0)
155                    except OSError, e:
156                        if e.errno == errno.ESRCH:
157                            # The owner has vanished, try to claim it in the next
158                            # iteration through the loop.
159                            try:
160                                rmlink(self.name)
161                            except OSError, e:
162                                if e.errno == errno.ENOENT:
163                                    # Another process cleaned up the lock.
164                                    # Race them to acquire it in the next
165                                    # iteration through the loop.
166                                    continue
167                                raise
168                            clean = False
169                            continue
170                        raise
171                    return False
172                raise
173            self.locked = True
174            self.clean = clean
175            return True
176
177
178    def unlock(self):
179        """
180        Release this lock.
181
182        This deletes the directory with the given name.
183
184        @raise: Any exception os.readlink() may raise, or
185        ValueError if the lock is not owned by this process.
186        """
187        pid = readlink(self.name)
188        if int(pid) != os.getpid():
189            raise ValueError("Lock %r not owned by this process" % (self.name,))
190        rmlink(self.name)
191        self.locked = False
192
193
194def isLocked(name):
195    """Determine if the lock of the given name is held or not.
196
197    @type name: C{str}
198    @param name: The filesystem path to the lock to test
199
200    @rtype: C{bool}
201    @return: True if the lock is held, False otherwise.
202    """
203    l = FilesystemLock(name)
204    result = None
205    try:
206        result = l.lock()
207    finally:
208        if result:
209            l.unlock()
210    return not result
211
212
213__all__ = ['FilesystemLock', 'isLocked']
Note: See TracBrowser for help on using the browser.