~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lockable_files.py

  • Committer: Andrew Bennetts
  • Date: 2009-08-07 04:17:51 UTC
  • mto: This revision was merged to the branch mainline in revision 4608.
  • Revision ID: andrew.bennetts@canonical.com-20090807041751-0vhb0y0g7k49hr45
Review comments from John.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2011 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2008, 2009 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
 
17
from cStringIO import StringIO
 
18
 
17
19
from bzrlib.lazy_import import lazy_import
18
20
lazy_import(globals(), """
 
21
import codecs
19
22
import warnings
20
23
 
21
24
from bzrlib import (
29
32
""")
30
33
 
31
34
from bzrlib.decorators import (
32
 
    only_raises,
33
 
    )
 
35
    needs_read_lock,
 
36
    needs_write_lock,
 
37
    )
 
38
from bzrlib.symbol_versioning import (
 
39
    deprecated_in,
 
40
    deprecated_method,
 
41
    )
 
42
 
 
43
 
 
44
# XXX: The tracking here of lock counts and whether the lock is held is
 
45
# somewhat redundant with what's done in LockDir; the main difference is that
 
46
# LockableFiles permits reentrancy.
 
47
 
 
48
class _LockWarner(object):
 
49
    """Hold a counter for a lock and warn if GCed while the count is >= 1.
 
50
 
 
51
    This is separate from LockableFiles because putting a __del__ on
 
52
    LockableFiles can result in uncollectable cycles.
 
53
    """
 
54
 
 
55
    def __init__(self, repr):
 
56
        self.lock_count = 0
 
57
        self.repr = repr
 
58
 
 
59
    def __del__(self):
 
60
        if self.lock_count >= 1:
 
61
            # There should have been a try/finally to unlock this.
 
62
            warnings.warn("%r was gc'd while locked" % self.repr)
34
63
 
35
64
 
36
65
class LockableFiles(object):
47
76
    This class is now deprecated; code should move to using the Transport
48
77
    directly for file operations and using the lock or CountedLock for
49
78
    locking.
50
 
 
 
79
    
51
80
    :ivar _lock: The real underlying lock (e.g. a LockDir)
52
 
    :ivar _lock_count: If _lock_mode is true, a positive count of the number
53
 
        of times the lock has been taken (and not yet released) *by this
54
 
        process*, through this particular object instance.
55
 
    :ivar _lock_mode: None, or 'r' or 'w'
 
81
    :ivar _counted_lock: A lock decorated with a semaphore, so that it 
 
82
        can be re-entered.
56
83
    """
57
84
 
 
85
    # _lock_mode: None, or 'r' or 'w'
 
86
 
 
87
    # _lock_count: If _lock_mode is true, a positive count of the number of
 
88
    # times the lock has been taken *by this process*.
 
89
 
58
90
    def __init__(self, transport, lock_name, lock_class):
59
91
        """Create a LockableFiles group
60
92
 
68
100
        self.lock_name = lock_name
69
101
        self._transaction = None
70
102
        self._lock_mode = None
71
 
        self._lock_count = 0
 
103
        self._lock_warner = _LockWarner(repr(self))
72
104
        self._find_modes()
73
105
        esc_name = self._escape(lock_name)
74
106
        self._lock = lock_class(transport, esc_name,
87
119
    def __repr__(self):
88
120
        return '%s(%r)' % (self.__class__.__name__,
89
121
                           self._transport)
90
 
 
91
122
    def __str__(self):
92
123
        return 'LockableFiles(%s, %s)' % (self.lock_name, self._transport.base)
93
124
 
151
182
        some other way, and need to synchronise this object's state with that
152
183
        fact.
153
184
        """
 
185
        # TODO: Upgrade locking to support using a Transport,
 
186
        # and potentially a remote locking protocol
154
187
        if self._lock_mode:
155
 
            if (self._lock_mode != 'w'
156
 
                or not self.get_transaction().writeable()):
 
188
            if self._lock_mode != 'w' or not self.get_transaction().writeable():
157
189
                raise errors.ReadOnlyError(self)
158
190
            self._lock.validate_token(token)
159
 
            self._lock_count += 1
 
191
            self._lock_warner.lock_count += 1
160
192
            return self._token_from_lock
161
193
        else:
162
194
            token_from_lock = self._lock.lock_write(token=token)
163
195
            #traceback.print_stack()
164
196
            self._lock_mode = 'w'
165
 
            self._lock_count = 1
 
197
            self._lock_warner.lock_count = 1
166
198
            self._set_write_transaction()
167
199
            self._token_from_lock = token_from_lock
168
200
            return token_from_lock
171
203
        if self._lock_mode:
172
204
            if self._lock_mode not in ('r', 'w'):
173
205
                raise ValueError("invalid lock mode %r" % (self._lock_mode,))
174
 
            self._lock_count += 1
 
206
            self._lock_warner.lock_count += 1
175
207
        else:
176
208
            self._lock.lock_read()
177
209
            #traceback.print_stack()
178
210
            self._lock_mode = 'r'
179
 
            self._lock_count = 1
 
211
            self._lock_warner.lock_count = 1
180
212
            self._set_read_transaction()
181
213
 
182
214
    def _set_read_transaction(self):
189
221
        """Setup a write transaction."""
190
222
        self._set_transaction(transactions.WriteTransaction())
191
223
 
192
 
    @only_raises(errors.LockNotHeld, errors.LockBroken)
193
224
    def unlock(self):
194
225
        if not self._lock_mode:
195
226
            return lock.cant_unlock_not_held(self)
196
 
        if self._lock_count > 1:
197
 
            self._lock_count -= 1
 
227
        if self._lock_warner.lock_count > 1:
 
228
            self._lock_warner.lock_count -= 1
198
229
        else:
199
230
            #traceback.print_stack()
200
231
            self._finish_transaction()
201
232
            try:
202
233
                self._lock.unlock()
203
234
            finally:
204
 
                self._lock_mode = self._lock_count = None
 
235
                self._lock_mode = self._lock_warner.lock_count = None
 
236
 
 
237
    @property
 
238
    def _lock_count(self):
 
239
        return self._lock_warner.lock_count
205
240
 
206
241
    def is_locked(self):
207
242
        """Return true if this LockableFiles group is locked"""
208
 
        return self._lock_count >= 1
 
243
        return self._lock_warner.lock_count >= 1
209
244
 
210
245
    def get_physical_lock_status(self):
211
246
        """Return physical lock status.
297
332
    def validate_token(self, token):
298
333
        if token is not None:
299
334
            raise errors.TokenLockingNotSupported(self)
 
335