/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/lockable_files.py

  • Committer: Robert Collins
  • Date: 2010-05-06 11:08:10 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100506110810-h3j07fh5gmw54s25
Cleaner matcher matching revised unlocking protocol.

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 __future__ import absolute_import
18
 
 
19
 
from .lazy_import import lazy_import
 
17
from bzrlib.lazy_import import lazy_import
20
18
lazy_import(globals(), """
21
 
from breezy import (
 
19
import codecs
 
20
import warnings
 
21
 
 
22
from bzrlib import (
22
23
    counted_lock,
23
24
    errors,
24
25
    lock,
 
26
    osutils,
25
27
    transactions,
26
28
    urlutils,
27
29
    )
28
30
""")
29
31
 
30
 
from .decorators import (
 
32
from bzrlib.decorators import (
31
33
    only_raises,
32
34
    )
33
35
 
34
36
 
 
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
 
35
58
class LockableFiles(object):
36
59
    """Object representing a set of related files locked within the same scope.
37
60
 
46
69
    This class is now deprecated; code should move to using the Transport
47
70
    directly for file operations and using the lock or CountedLock for
48
71
    locking.
49
 
 
 
72
    
50
73
    :ivar _lock: The real underlying lock (e.g. a LockDir)
51
 
    :ivar _lock_count: If _lock_mode is true, a positive count of the number
52
 
        of times the lock has been taken (and not yet released) *by this
53
 
        process*, through this particular object instance.
54
 
    :ivar _lock_mode: None, or 'r' or 'w'
 
74
    :ivar _counted_lock: A lock decorated with a semaphore, so that it 
 
75
        can be re-entered.
55
76
    """
56
77
 
 
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
 
57
83
    def __init__(self, transport, lock_name, lock_class):
58
84
        """Create a LockableFiles group
59
85
 
67
93
        self.lock_name = lock_name
68
94
        self._transaction = None
69
95
        self._lock_mode = None
70
 
        self._lock_count = 0
 
96
        self._lock_warner = _LockWarner(repr(self))
71
97
        self._find_modes()
72
98
        esc_name = self._escape(lock_name)
73
99
        self._lock = lock_class(transport, esc_name,
86
112
    def __repr__(self):
87
113
        return '%s(%r)' % (self.__class__.__name__,
88
114
                           self._transport)
89
 
 
90
115
    def __str__(self):
91
116
        return 'LockableFiles(%s, %s)' % (self.lock_name, self._transport.base)
92
117
 
99
124
 
100
125
    def _escape(self, file_or_path):
101
126
        """DEPRECATED: Do not use outside this class"""
 
127
        if not isinstance(file_or_path, basestring):
 
128
            file_or_path = '/'.join(file_or_path)
102
129
        if file_or_path == '':
103
130
            return u''
104
 
        return urlutils.escape(file_or_path)
 
131
        return urlutils.escape(osutils.safe_unicode(file_or_path))
105
132
 
106
133
    def _find_modes(self):
107
134
        """Determine the appropriate modes for files and directories.
114
141
        try:
115
142
            st = self._transport.stat('.')
116
143
        except errors.TransportNotPossible:
117
 
            self._dir_mode = 0o755
118
 
            self._file_mode = 0o644
 
144
            self._dir_mode = 0755
 
145
            self._file_mode = 0644
119
146
        else:
120
147
            # Check the directory mode, but also make sure the created
121
148
            # directories and files are read-write for this user. This is
122
149
            # mostly a workaround for filesystems which lie about being able to
123
150
            # write to a directory (cygwin & win32)
124
 
            self._dir_mode = (st.st_mode & 0o7777) | 0o0700
 
151
            self._dir_mode = (st.st_mode & 07777) | 00700
125
152
            # Remove the sticky and execute bits for files
126
 
            self._file_mode = self._dir_mode & ~0o7111
 
153
            self._file_mode = self._dir_mode & ~07111
127
154
 
128
155
    def leave_in_place(self):
129
156
        """Set this LockableFiles to not clear the physical lock on unlock."""
148
175
        some other way, and need to synchronise this object's state with that
149
176
        fact.
150
177
        """
 
178
        # TODO: Upgrade locking to support using a Transport,
 
179
        # and potentially a remote locking protocol
151
180
        if self._lock_mode:
152
 
            if (self._lock_mode != 'w'
153
 
                    or not self.get_transaction().writeable()):
 
181
            if self._lock_mode != 'w' or not self.get_transaction().writeable():
154
182
                raise errors.ReadOnlyError(self)
155
183
            self._lock.validate_token(token)
156
 
            self._lock_count += 1
 
184
            self._lock_warner.lock_count += 1
157
185
            return self._token_from_lock
158
186
        else:
159
187
            token_from_lock = self._lock.lock_write(token=token)
160
 
            # traceback.print_stack()
 
188
            #traceback.print_stack()
161
189
            self._lock_mode = 'w'
162
 
            self._lock_count = 1
 
190
            self._lock_warner.lock_count = 1
163
191
            self._set_write_transaction()
164
192
            self._token_from_lock = token_from_lock
165
193
            return token_from_lock
168
196
        if self._lock_mode:
169
197
            if self._lock_mode not in ('r', 'w'):
170
198
                raise ValueError("invalid lock mode %r" % (self._lock_mode,))
171
 
            self._lock_count += 1
 
199
            self._lock_warner.lock_count += 1
172
200
        else:
173
201
            self._lock.lock_read()
174
 
            # traceback.print_stack()
 
202
            #traceback.print_stack()
175
203
            self._lock_mode = 'r'
176
 
            self._lock_count = 1
 
204
            self._lock_warner.lock_count = 1
177
205
            self._set_read_transaction()
178
206
 
179
207
    def _set_read_transaction(self):
190
218
    def unlock(self):
191
219
        if not self._lock_mode:
192
220
            return lock.cant_unlock_not_held(self)
193
 
        if self._lock_count > 1:
194
 
            self._lock_count -= 1
 
221
        if self._lock_warner.lock_count > 1:
 
222
            self._lock_warner.lock_count -= 1
195
223
        else:
196
 
            # traceback.print_stack()
 
224
            #traceback.print_stack()
197
225
            self._finish_transaction()
198
226
            try:
199
227
                self._lock.unlock()
200
228
            finally:
201
 
                self._lock_count = 0
202
 
                self._lock_mode = None
 
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
203
234
 
204
235
    def is_locked(self):
205
236
        """Return true if this LockableFiles group is locked"""
206
 
        return self._lock_count >= 1
 
237
        return self._lock_warner.lock_count >= 1
207
238
 
208
239
    def get_physical_lock_status(self):
209
240
        """Return physical lock status.
256
287
    This is suitable for use only in WorkingTrees (which are at present
257
288
    always local).
258
289
    """
259
 
 
260
290
    def __init__(self, transport, escaped_name, file_modebits, dir_modebits):
261
291
        self._transport = transport
262
292
        self._escaped_name = escaped_name
290
320
    def create(self, mode=None):
291
321
        """Create lock mechanism"""
292
322
        # for old-style locks, create the file now
293
 
        self._transport.put_bytes(self._escaped_name, b'',
294
 
                                  mode=self._file_modebits)
 
323
        self._transport.put_bytes(self._escaped_name, '',
 
324
                            mode=self._file_modebits)
295
325
 
296
326
    def validate_token(self, token):
297
327
        if token is not None:
298
328
            raise errors.TokenLockingNotSupported(self)
 
329