~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lockable_files.py

  • Committer: Vincent Ladeuil
  • Date: 2012-01-18 14:09:19 UTC
  • mto: This revision was merged to the branch mainline in revision 6468.
  • Revision ID: v.ladeuil+lp@free.fr-20120118140919-rlvdrhpc0nq1lbwi
Change set/remove to require a lock for the branch config files.

This means that tests (or any plugin for that matter) do not requires an
explicit lock on the branch anymore to change a single option. This also
means the optimisation becomes "opt-in" and as such won't be as
spectacular as it may be and/or harder to get right (nothing fails
anymore).

This reduces the diff by ~300 lines.

Code/tests that were updating more than one config option is still taking
a lock to at least avoid some IOs and demonstrate the benefits through
the decreased number of hpss calls.

The duplication between BranchStack and BranchOnlyStack will be removed
once the same sharing is in place for local config files, at which point
the Stack class itself may be able to host the changes.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2008, 2009 Canonical Ltd
 
1
# Copyright (C) 2005-2011 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 __future__ import absolute_import
 
18
 
17
19
from bzrlib.lazy_import import lazy_import
18
20
lazy_import(globals(), """
19
 
import codecs
20
21
import warnings
21
22
 
22
23
from bzrlib import (
34
35
    )
35
36
 
36
37
 
37
 
# XXX: The tracking here of lock counts and whether the lock is held is
38
 
# somewhat redundant with what's done in LockDir; the main difference is that
39
 
# LockableFiles permits reentrancy.
40
 
 
41
 
class _LockWarner(object):
42
 
    """Hold a counter for a lock and warn if GCed while the count is >= 1.
43
 
 
44
 
    This is separate from LockableFiles because putting a __del__ on
45
 
    LockableFiles can result in uncollectable cycles.
46
 
    """
47
 
 
48
 
    def __init__(self, repr):
49
 
        self.lock_count = 0
50
 
        self.repr = repr
51
 
 
52
 
    def __del__(self):
53
 
        if self.lock_count >= 1:
54
 
            # There should have been a try/finally to unlock this.
55
 
            warnings.warn("%r was gc'd while locked" % self.repr)
56
 
 
57
 
 
58
38
class LockableFiles(object):
59
39
    """Object representing a set of related files locked within the same scope.
60
40
 
69
49
    This class is now deprecated; code should move to using the Transport
70
50
    directly for file operations and using the lock or CountedLock for
71
51
    locking.
72
 
    
 
52
 
73
53
    :ivar _lock: The real underlying lock (e.g. a LockDir)
74
 
    :ivar _counted_lock: A lock decorated with a semaphore, so that it 
75
 
        can be re-entered.
 
54
    :ivar _lock_count: If _lock_mode is true, a positive count of the number
 
55
        of times the lock has been taken (and not yet released) *by this
 
56
        process*, through this particular object instance.
 
57
    :ivar _lock_mode: None, or 'r' or 'w'
76
58
    """
77
59
 
78
 
    # _lock_mode: None, or 'r' or 'w'
79
 
 
80
 
    # _lock_count: If _lock_mode is true, a positive count of the number of
81
 
    # times the lock has been taken *by this process*.
82
 
 
83
60
    def __init__(self, transport, lock_name, lock_class):
84
61
        """Create a LockableFiles group
85
62
 
93
70
        self.lock_name = lock_name
94
71
        self._transaction = None
95
72
        self._lock_mode = None
96
 
        self._lock_warner = _LockWarner(repr(self))
 
73
        self._lock_count = 0
97
74
        self._find_modes()
98
75
        esc_name = self._escape(lock_name)
99
76
        self._lock = lock_class(transport, esc_name,
112
89
    def __repr__(self):
113
90
        return '%s(%r)' % (self.__class__.__name__,
114
91
                           self._transport)
 
92
 
115
93
    def __str__(self):
116
94
        return 'LockableFiles(%s, %s)' % (self.lock_name, self._transport.base)
117
95
 
175
153
        some other way, and need to synchronise this object's state with that
176
154
        fact.
177
155
        """
178
 
        # TODO: Upgrade locking to support using a Transport,
179
 
        # and potentially a remote locking protocol
180
156
        if self._lock_mode:
181
 
            if self._lock_mode != 'w' or not self.get_transaction().writeable():
 
157
            if (self._lock_mode != 'w'
 
158
                or not self.get_transaction().writeable()):
182
159
                raise errors.ReadOnlyError(self)
183
160
            self._lock.validate_token(token)
184
 
            self._lock_warner.lock_count += 1
 
161
            self._lock_count += 1
185
162
            return self._token_from_lock
186
163
        else:
187
164
            token_from_lock = self._lock.lock_write(token=token)
188
165
            #traceback.print_stack()
189
166
            self._lock_mode = 'w'
190
 
            self._lock_warner.lock_count = 1
 
167
            self._lock_count = 1
191
168
            self._set_write_transaction()
192
169
            self._token_from_lock = token_from_lock
193
170
            return token_from_lock
196
173
        if self._lock_mode:
197
174
            if self._lock_mode not in ('r', 'w'):
198
175
                raise ValueError("invalid lock mode %r" % (self._lock_mode,))
199
 
            self._lock_warner.lock_count += 1
 
176
            self._lock_count += 1
200
177
        else:
201
178
            self._lock.lock_read()
202
179
            #traceback.print_stack()
203
180
            self._lock_mode = 'r'
204
 
            self._lock_warner.lock_count = 1
 
181
            self._lock_count = 1
205
182
            self._set_read_transaction()
206
183
 
207
184
    def _set_read_transaction(self):
218
195
    def unlock(self):
219
196
        if not self._lock_mode:
220
197
            return lock.cant_unlock_not_held(self)
221
 
        if self._lock_warner.lock_count > 1:
222
 
            self._lock_warner.lock_count -= 1
 
198
        if self._lock_count > 1:
 
199
            self._lock_count -= 1
223
200
        else:
224
201
            #traceback.print_stack()
225
202
            self._finish_transaction()
226
203
            try:
227
204
                self._lock.unlock()
228
205
            finally:
229
 
                self._lock_mode = self._lock_warner.lock_count = None
230
 
 
231
 
    @property
232
 
    def _lock_count(self):
233
 
        return self._lock_warner.lock_count
 
206
                self._lock_mode = self._lock_count = None
234
207
 
235
208
    def is_locked(self):
236
209
        """Return true if this LockableFiles group is locked"""
237
 
        return self._lock_warner.lock_count >= 1
 
210
        return self._lock_count >= 1
238
211
 
239
212
    def get_physical_lock_status(self):
240
213
        """Return physical lock status.
326
299
    def validate_token(self, token):
327
300
        if token is not None:
328
301
            raise errors.TokenLockingNotSupported(self)
329