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