~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lockable_files.py

Merge bzr.dev to resolve conflicts

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005-2011 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
from bzrlib.lazy_import import lazy_import
 
18
lazy_import(globals(), """
 
19
import warnings
 
20
 
 
21
from bzrlib import (
 
22
    counted_lock,
 
23
    errors,
 
24
    lock,
 
25
    osutils,
 
26
    transactions,
 
27
    urlutils,
 
28
    )
 
29
""")
 
30
 
 
31
from bzrlib.decorators import (
 
32
    only_raises,
 
33
    )
 
34
 
 
35
 
 
36
# XXX: The tracking here of lock counts and whether the lock is held is
 
37
# somewhat redundant with what's done in LockDir; the main difference is that
 
38
# LockableFiles permits reentrancy.
 
39
 
 
40
class _LockWarner(object):
 
41
    """Hold a counter for a lock and warn if GCed while the count is >= 1.
 
42
 
 
43
    This is separate from LockableFiles because putting a __del__ on
 
44
    LockableFiles can result in uncollectable cycles.
 
45
    """
 
46
 
 
47
    def __init__(self, repr):
 
48
        self.lock_count = 0
 
49
        self.repr = repr
 
50
 
 
51
    def __del__(self):
 
52
        if self.lock_count >= 1:
 
53
            # There should have been a try/finally to unlock this.
 
54
            warnings.warn("%r was gc'd while locked" % self.repr)
 
55
 
 
56
 
 
57
class LockableFiles(object):
 
58
    """Object representing a set of related files locked within the same scope.
 
59
 
 
60
    This coordinates access to the lock along with providing a transaction.
 
61
 
 
62
    LockableFiles manage a lock count and can be locked repeatedly by
 
63
    a single caller.  (The underlying lock implementation generally does not
 
64
    support this.)
 
65
 
 
66
    Instances of this class are often called control_files.
 
67
 
 
68
    This class is now deprecated; code should move to using the Transport
 
69
    directly for file operations and using the lock or CountedLock for
 
70
    locking.
 
71
    
 
72
    :ivar _lock: The real underlying lock (e.g. a LockDir)
 
73
    :ivar _counted_lock: A lock decorated with a semaphore, so that it 
 
74
        can be re-entered.
 
75
    """
 
76
 
 
77
    # _lock_mode: None, or 'r' or 'w'
 
78
 
 
79
    # _lock_count: If _lock_mode is true, a positive count of the number of
 
80
    # times the lock has been taken *by this process*.
 
81
 
 
82
    def __init__(self, transport, lock_name, lock_class):
 
83
        """Create a LockableFiles group
 
84
 
 
85
        :param transport: Transport pointing to the directory holding the
 
86
            control files and lock.
 
87
        :param lock_name: Name of the lock guarding these files.
 
88
        :param lock_class: Class of lock strategy to use: typically
 
89
            either LockDir or TransportLock.
 
90
        """
 
91
        self._transport = transport
 
92
        self.lock_name = lock_name
 
93
        self._transaction = None
 
94
        self._lock_mode = None
 
95
        self._lock_warner = _LockWarner(repr(self))
 
96
        self._find_modes()
 
97
        esc_name = self._escape(lock_name)
 
98
        self._lock = lock_class(transport, esc_name,
 
99
                                file_modebits=self._file_mode,
 
100
                                dir_modebits=self._dir_mode)
 
101
        self._counted_lock = counted_lock.CountedLock(self._lock)
 
102
 
 
103
    def create_lock(self):
 
104
        """Create the lock.
 
105
 
 
106
        This should normally be called only when the LockableFiles directory
 
107
        is first created on disk.
 
108
        """
 
109
        self._lock.create(mode=self._dir_mode)
 
110
 
 
111
    def __repr__(self):
 
112
        return '%s(%r)' % (self.__class__.__name__,
 
113
                           self._transport)
 
114
    def __str__(self):
 
115
        return 'LockableFiles(%s, %s)' % (self.lock_name, self._transport.base)
 
116
 
 
117
    def break_lock(self):
 
118
        """Break the lock of this lockable files group if it is held.
 
119
 
 
120
        The current ui factory will be used to prompt for user conformation.
 
121
        """
 
122
        self._lock.break_lock()
 
123
 
 
124
    def _escape(self, file_or_path):
 
125
        """DEPRECATED: Do not use outside this class"""
 
126
        if not isinstance(file_or_path, basestring):
 
127
            file_or_path = '/'.join(file_or_path)
 
128
        if file_or_path == '':
 
129
            return u''
 
130
        return urlutils.escape(osutils.safe_unicode(file_or_path))
 
131
 
 
132
    def _find_modes(self):
 
133
        """Determine the appropriate modes for files and directories.
 
134
 
 
135
        :deprecated: Replaced by BzrDir._find_creation_modes.
 
136
        """
 
137
        # XXX: The properties created by this can be removed or deprecated
 
138
        # once all the _get_text_store methods etc no longer use them.
 
139
        # -- mbp 20080512
 
140
        try:
 
141
            st = self._transport.stat('.')
 
142
        except errors.TransportNotPossible:
 
143
            self._dir_mode = 0755
 
144
            self._file_mode = 0644
 
145
        else:
 
146
            # Check the directory mode, but also make sure the created
 
147
            # directories and files are read-write for this user. This is
 
148
            # mostly a workaround for filesystems which lie about being able to
 
149
            # write to a directory (cygwin & win32)
 
150
            self._dir_mode = (st.st_mode & 07777) | 00700
 
151
            # Remove the sticky and execute bits for files
 
152
            self._file_mode = self._dir_mode & ~07111
 
153
 
 
154
    def leave_in_place(self):
 
155
        """Set this LockableFiles to not clear the physical lock on unlock."""
 
156
        self._lock.leave_in_place()
 
157
 
 
158
    def dont_leave_in_place(self):
 
159
        """Set this LockableFiles to clear the physical lock on unlock."""
 
160
        self._lock.dont_leave_in_place()
 
161
 
 
162
    def lock_write(self, token=None):
 
163
        """Lock this group of files for writing.
 
164
 
 
165
        :param token: if this is already locked, then lock_write will fail
 
166
            unless the token matches the existing lock.
 
167
        :returns: a token if this instance supports tokens, otherwise None.
 
168
        :raises TokenLockingNotSupported: when a token is given but this
 
169
            instance doesn't support using token locks.
 
170
        :raises MismatchedToken: if the specified token doesn't match the token
 
171
            of the existing lock.
 
172
 
 
173
        A token should be passed in if you know that you have locked the object
 
174
        some other way, and need to synchronise this object's state with that
 
175
        fact.
 
176
        """
 
177
        # TODO: Upgrade locking to support using a Transport,
 
178
        # and potentially a remote locking protocol
 
179
        if self._lock_mode:
 
180
            if self._lock_mode != 'w' or not self.get_transaction().writeable():
 
181
                raise errors.ReadOnlyError(self)
 
182
            self._lock.validate_token(token)
 
183
            self._lock_warner.lock_count += 1
 
184
            return self._token_from_lock
 
185
        else:
 
186
            token_from_lock = self._lock.lock_write(token=token)
 
187
            #traceback.print_stack()
 
188
            self._lock_mode = 'w'
 
189
            self._lock_warner.lock_count = 1
 
190
            self._set_write_transaction()
 
191
            self._token_from_lock = token_from_lock
 
192
            return token_from_lock
 
193
 
 
194
    def lock_read(self):
 
195
        if self._lock_mode:
 
196
            if self._lock_mode not in ('r', 'w'):
 
197
                raise ValueError("invalid lock mode %r" % (self._lock_mode,))
 
198
            self._lock_warner.lock_count += 1
 
199
        else:
 
200
            self._lock.lock_read()
 
201
            #traceback.print_stack()
 
202
            self._lock_mode = 'r'
 
203
            self._lock_warner.lock_count = 1
 
204
            self._set_read_transaction()
 
205
 
 
206
    def _set_read_transaction(self):
 
207
        """Setup a read transaction."""
 
208
        self._set_transaction(transactions.ReadOnlyTransaction())
 
209
        # 5K may be excessive, but hey, its a knob.
 
210
        self.get_transaction().set_cache_size(5000)
 
211
 
 
212
    def _set_write_transaction(self):
 
213
        """Setup a write transaction."""
 
214
        self._set_transaction(transactions.WriteTransaction())
 
215
 
 
216
    @only_raises(errors.LockNotHeld, errors.LockBroken)
 
217
    def unlock(self):
 
218
        if not self._lock_mode:
 
219
            return lock.cant_unlock_not_held(self)
 
220
        if self._lock_warner.lock_count > 1:
 
221
            self._lock_warner.lock_count -= 1
 
222
        else:
 
223
            #traceback.print_stack()
 
224
            self._finish_transaction()
 
225
            try:
 
226
                self._lock.unlock()
 
227
            finally:
 
228
                self._lock_mode = self._lock_warner.lock_count = None
 
229
 
 
230
    @property
 
231
    def _lock_count(self):
 
232
        return self._lock_warner.lock_count
 
233
 
 
234
    def is_locked(self):
 
235
        """Return true if this LockableFiles group is locked"""
 
236
        return self._lock_warner.lock_count >= 1
 
237
 
 
238
    def get_physical_lock_status(self):
 
239
        """Return physical lock status.
 
240
 
 
241
        Returns true if a lock is held on the transport. If no lock is held, or
 
242
        the underlying locking mechanism does not support querying lock
 
243
        status, false is returned.
 
244
        """
 
245
        try:
 
246
            return self._lock.peek() is not None
 
247
        except NotImplementedError:
 
248
            return False
 
249
 
 
250
    def get_transaction(self):
 
251
        """Return the current active transaction.
 
252
 
 
253
        If no transaction is active, this returns a passthrough object
 
254
        for which all data is immediately flushed and no caching happens.
 
255
        """
 
256
        if self._transaction is None:
 
257
            return transactions.PassThroughTransaction()
 
258
        else:
 
259
            return self._transaction
 
260
 
 
261
    def _set_transaction(self, new_transaction):
 
262
        """Set a new active transaction."""
 
263
        if self._transaction is not None:
 
264
            raise errors.LockError('Branch %s is in a transaction already.' %
 
265
                                   self)
 
266
        self._transaction = new_transaction
 
267
 
 
268
    def _finish_transaction(self):
 
269
        """Exit the current transaction."""
 
270
        if self._transaction is None:
 
271
            raise errors.LockError('Branch %s is not in a transaction' %
 
272
                                   self)
 
273
        transaction = self._transaction
 
274
        self._transaction = None
 
275
        transaction.finish()
 
276
 
 
277
 
 
278
class TransportLock(object):
 
279
    """Locking method which uses transport-dependent locks.
 
280
 
 
281
    On the local filesystem these transform into OS-managed locks.
 
282
 
 
283
    These do not guard against concurrent access via different
 
284
    transports.
 
285
 
 
286
    This is suitable for use only in WorkingTrees (which are at present
 
287
    always local).
 
288
    """
 
289
    def __init__(self, transport, escaped_name, file_modebits, dir_modebits):
 
290
        self._transport = transport
 
291
        self._escaped_name = escaped_name
 
292
        self._file_modebits = file_modebits
 
293
        self._dir_modebits = dir_modebits
 
294
 
 
295
    def break_lock(self):
 
296
        raise NotImplementedError(self.break_lock)
 
297
 
 
298
    def leave_in_place(self):
 
299
        raise NotImplementedError(self.leave_in_place)
 
300
 
 
301
    def dont_leave_in_place(self):
 
302
        raise NotImplementedError(self.dont_leave_in_place)
 
303
 
 
304
    def lock_write(self, token=None):
 
305
        if token is not None:
 
306
            raise errors.TokenLockingNotSupported(self)
 
307
        self._lock = self._transport.lock_write(self._escaped_name)
 
308
 
 
309
    def lock_read(self):
 
310
        self._lock = self._transport.lock_read(self._escaped_name)
 
311
 
 
312
    def unlock(self):
 
313
        self._lock.unlock()
 
314
        self._lock = None
 
315
 
 
316
    def peek(self):
 
317
        raise NotImplementedError()
 
318
 
 
319
    def create(self, mode=None):
 
320
        """Create lock mechanism"""
 
321
        # for old-style locks, create the file now
 
322
        self._transport.put_bytes(self._escaped_name, '',
 
323
                            mode=self._file_modebits)
 
324
 
 
325
    def validate_token(self, token):
 
326
        if token is not None:
 
327
            raise errors.TokenLockingNotSupported(self)
 
328