~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lockable_files.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2008-06-10 08:15:19 UTC
  • mfrom: (3489.1.2 integration)
  • Revision ID: pqm@pqm.ubuntu.com-20080610081519-95unlj6ayptlh2uv
(mbp) Bump version to 1.6b3

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, 2006, 2008 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
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
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
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
from cStringIO import StringIO
18
 
 
19
 
from bzrlib.lazy_import import lazy_import
20
 
lazy_import(globals(), """
21
18
import codecs
22
 
import warnings
23
 
 
24
 
from bzrlib import (
25
 
    counted_lock,
26
 
    errors,
27
 
    osutils,
28
 
    transactions,
29
 
    urlutils,
30
 
    )
31
 
""")
32
 
 
33
 
from bzrlib.decorators import (
34
 
    needs_read_lock,
35
 
    needs_write_lock,
36
 
    )
 
19
#import traceback
 
20
from warnings import warn
 
21
 
 
22
import bzrlib
 
23
from bzrlib.decorators import (needs_read_lock,
 
24
        needs_write_lock)
 
25
import bzrlib.errors as errors
 
26
from bzrlib.errors import BzrError
 
27
from bzrlib.osutils import file_iterator, safe_unicode
37
28
from bzrlib.symbol_versioning import (
38
29
    deprecated_in,
39
30
    deprecated_method,
40
31
    )
 
32
from bzrlib.trace import mutter, note
 
33
import bzrlib.transactions as transactions
 
34
import bzrlib.urlutils as urlutils
41
35
 
42
36
 
43
37
# XXX: The tracking here of lock counts and whether the lock is held is
44
38
# somewhat redundant with what's done in LockDir; the main difference is that
45
39
# LockableFiles permits reentrancy.
46
40
 
47
 
class _LockWarner(object):
48
 
    """Hold a counter for a lock and warn if GCed while the count is >= 1.
49
 
 
50
 
    This is separate from LockableFiles because putting a __del__ on
51
 
    LockableFiles can result in uncollectable cycles.
52
 
    """
53
 
 
54
 
    def __init__(self, repr):
55
 
        self.lock_count = 0
56
 
        self.repr = repr
57
 
 
58
 
    def __del__(self):
59
 
        if self.lock_count >= 1:
60
 
            # There should have been a try/finally to unlock this.
61
 
            warnings.warn("%r was gc'd while locked" % self.repr)
62
 
 
63
 
 
64
41
class LockableFiles(object):
65
42
    """Object representing a set of related files locked within the same scope.
66
43
 
75
52
    support this.)
76
53
 
77
54
    Instances of this class are often called control_files.
78
 
 
 
55
    
79
56
    This object builds on top of a Transport, which is used to actually write
80
57
    the files to disk, and an OSLock or LockDir, which controls how access to
81
58
    the files is controlled.  The particular type of locking used is set when
82
59
    the object is constructed.  In older formats OSLocks are used everywhere.
83
 
    in newer formats a LockDir is used for Repositories and Branches, and
 
60
    in newer formats a LockDir is used for Repositories and Branches, and 
84
61
    OSLocks for the local filesystem.
85
62
 
86
 
    This class is now deprecated; code should move to using the Transport
87
 
    directly for file operations and using the lock or CountedLock for
 
63
    This class is now deprecated; code should move to using the Transport 
 
64
    directly for file operations and using the lock or CountedLock for 
88
65
    locking.
89
 
    
90
 
    :ivar _lock: The real underlying lock (e.g. a LockDir)
91
 
    :ivar _counted_lock: A lock decorated with a semaphore, so that it 
92
 
        can be re-entered.
93
66
    """
94
67
 
95
68
    # _lock_mode: None, or 'r' or 'w'
96
69
 
97
70
    # _lock_count: If _lock_mode is true, a positive count of the number of
98
 
    # times the lock has been taken *by this process*.
99
 
 
 
71
    # times the lock has been taken *by this process*.   
 
72
    
100
73
    def __init__(self, transport, lock_name, lock_class):
101
74
        """Create a LockableFiles group
102
75
 
103
 
        :param transport: Transport pointing to the directory holding the
 
76
        :param transport: Transport pointing to the directory holding the 
104
77
            control files and lock.
105
78
        :param lock_name: Name of the lock guarding these files.
106
79
        :param lock_class: Class of lock strategy to use: typically
110
83
        self.lock_name = lock_name
111
84
        self._transaction = None
112
85
        self._lock_mode = None
113
 
        self._lock_warner = _LockWarner(repr(self))
 
86
        self._lock_count = 0
114
87
        self._find_modes()
115
88
        esc_name = self._escape(lock_name)
116
89
        self._lock = lock_class(transport, esc_name,
117
90
                                file_modebits=self._file_mode,
118
91
                                dir_modebits=self._dir_mode)
119
 
        self._counted_lock = counted_lock.CountedLock(self._lock)
120
92
 
121
93
    def create_lock(self):
122
94
        """Create the lock.
132
104
    def __str__(self):
133
105
        return 'LockableFiles(%s, %s)' % (self.lock_name, self._transport.base)
134
106
 
 
107
    def __del__(self):
 
108
        if self.is_locked():
 
109
            # do not automatically unlock; there should have been a
 
110
            # try/finally to unlock this.
 
111
            warn("%r was gc'd while locked" % self)
 
112
 
135
113
    def break_lock(self):
136
114
        """Break the lock of this lockable files group if it is held.
137
115
 
140
118
        self._lock.break_lock()
141
119
 
142
120
    def _escape(self, file_or_path):
143
 
        """DEPRECATED: Do not use outside this class"""
144
121
        if not isinstance(file_or_path, basestring):
145
122
            file_or_path = '/'.join(file_or_path)
146
123
        if file_or_path == '':
147
124
            return u''
148
 
        return urlutils.escape(osutils.safe_unicode(file_or_path))
 
125
        return urlutils.escape(safe_unicode(file_or_path))
149
126
 
150
127
    def _find_modes(self):
151
128
        """Determine the appropriate modes for files and directories.
152
 
 
 
129
        
153
130
        :deprecated: Replaced by BzrDir._find_modes.
154
131
        """
155
 
        # XXX: The properties created by this can be removed or deprecated
156
 
        # once all the _get_text_store methods etc no longer use them.
157
 
        # -- mbp 20080512
158
132
        try:
159
133
            st = self._transport.stat('.')
160
134
        except errors.TransportNotPossible:
172
146
    @deprecated_method(deprecated_in((1, 6, 0)))
173
147
    def controlfilename(self, file_or_path):
174
148
        """Return location relative to branch.
175
 
 
 
149
        
176
150
        :deprecated: Use Transport methods instead.
177
151
        """
178
152
        return self._transport.abspath(self._escape(file_or_path))
181
155
    @deprecated_method(deprecated_in((1, 5, 0)))
182
156
    def get(self, relpath):
183
157
        """Get a file as a bytestream.
184
 
 
 
158
        
185
159
        :deprecated: Use a Transport instead of LockableFiles.
186
160
        """
187
161
        relpath = self._escape(relpath)
191
165
    @deprecated_method(deprecated_in((1, 5, 0)))
192
166
    def get_utf8(self, relpath):
193
167
        """Get a file as a unicode stream.
194
 
 
 
168
        
195
169
        :deprecated: Use a Transport instead of LockableFiles.
196
170
        """
197
171
        relpath = self._escape(relpath)
202
176
    @deprecated_method(deprecated_in((1, 6, 0)))
203
177
    def put(self, path, file):
204
178
        """Write a file.
205
 
 
 
179
        
206
180
        :param path: The path to put the file, relative to the .bzr control
207
181
                     directory
208
182
        :param file: A file-like or string object whose contents should be copied.
254
228
 
255
229
    def lock_write(self, token=None):
256
230
        """Lock this group of files for writing.
257
 
 
 
231
        
258
232
        :param token: if this is already locked, then lock_write will fail
259
233
            unless the token matches the existing lock.
260
234
        :returns: a token if this instance supports tokens, otherwise None.
267
241
        some other way, and need to synchronise this object's state with that
268
242
        fact.
269
243
        """
 
244
        # mutter("lock write: %s (%s)", self, self._lock_count)
270
245
        # TODO: Upgrade locking to support using a Transport,
271
246
        # and potentially a remote locking protocol
272
247
        if self._lock_mode:
273
248
            if self._lock_mode != 'w' or not self.get_transaction().writeable():
274
249
                raise errors.ReadOnlyError(self)
275
250
            self._lock.validate_token(token)
276
 
            self._lock_warner.lock_count += 1
 
251
            self._lock_count += 1
277
252
            return self._token_from_lock
278
253
        else:
279
254
            token_from_lock = self._lock.lock_write(token=token)
 
255
            #note('write locking %s', self)
280
256
            #traceback.print_stack()
281
257
            self._lock_mode = 'w'
282
 
            self._lock_warner.lock_count = 1
283
 
            self._set_write_transaction()
 
258
            self._lock_count = 1
 
259
            self._set_transaction(transactions.WriteTransaction())
284
260
            self._token_from_lock = token_from_lock
285
261
            return token_from_lock
286
262
 
287
263
    def lock_read(self):
 
264
        # mutter("lock read: %s (%s)", self, self._lock_count)
288
265
        if self._lock_mode:
289
266
            if self._lock_mode not in ('r', 'w'):
290
267
                raise ValueError("invalid lock mode %r" % (self._lock_mode,))
291
 
            self._lock_warner.lock_count += 1
 
268
            self._lock_count += 1
292
269
        else:
293
270
            self._lock.lock_read()
 
271
            #note('read locking %s', self)
294
272
            #traceback.print_stack()
295
273
            self._lock_mode = 'r'
296
 
            self._lock_warner.lock_count = 1
297
 
            self._set_read_transaction()
298
 
 
299
 
    def _set_read_transaction(self):
300
 
        """Setup a read transaction."""
301
 
        self._set_transaction(transactions.ReadOnlyTransaction())
302
 
        # 5K may be excessive, but hey, its a knob.
303
 
        self.get_transaction().set_cache_size(5000)
304
 
 
305
 
    def _set_write_transaction(self):
306
 
        """Setup a write transaction."""
307
 
        self._set_transaction(transactions.WriteTransaction())
308
 
 
 
274
            self._lock_count = 1
 
275
            self._set_transaction(transactions.ReadOnlyTransaction())
 
276
            # 5K may be excessive, but hey, its a knob.
 
277
            self.get_transaction().set_cache_size(5000)
 
278
                        
309
279
    def unlock(self):
 
280
        # mutter("unlock: %s (%s)", self, self._lock_count)
310
281
        if not self._lock_mode:
311
282
            raise errors.LockNotHeld(self)
312
 
        if self._lock_warner.lock_count > 1:
313
 
            self._lock_warner.lock_count -= 1
 
283
        if self._lock_count > 1:
 
284
            self._lock_count -= 1
314
285
        else:
 
286
            #note('unlocking %s', self)
315
287
            #traceback.print_stack()
316
288
            self._finish_transaction()
317
289
            try:
318
290
                self._lock.unlock()
319
291
            finally:
320
 
                self._lock_mode = self._lock_warner.lock_count = None
321
 
 
322
 
    @property
323
 
    def _lock_count(self):
324
 
        return self._lock_warner.lock_count
 
292
                self._lock_mode = self._lock_count = None
325
293
 
326
294
    def is_locked(self):
327
295
        """Return true if this LockableFiles group is locked"""
328
 
        return self._lock_warner.lock_count >= 1
 
296
        return self._lock_count >= 1
329
297
 
330
298
    def get_physical_lock_status(self):
331
299
        """Return physical lock status.
332
 
 
 
300
        
333
301
        Returns true if a lock is held on the transport. If no lock is held, or
334
302
        the underlying locking mechanism does not support querying lock
335
303
        status, false is returned.
417
385
    def validate_token(self, token):
418
386
        if token is not None:
419
387
            raise errors.TokenLockingNotSupported(self)
420
 
 
 
388