~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lockdir.py

Merge from bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
87
87
>>> # typically will be obtained from a BzrDir, Branch, etc
88
88
>>> t = MemoryTransport()
89
89
>>> l = LockDir(t, 'sample-lock')
 
90
>>> l.create()
90
91
>>> l.wait_lock()
91
92
>>> # do something here
92
93
>>> l.unlock()
95
96
 
96
97
import os
97
98
import time
 
99
from warnings import warn
98
100
from StringIO import StringIO
99
101
 
100
102
import bzrlib.config
128
130
# TODO: Some kind of callback run while polling a lock to show progress
129
131
# indicators.
130
132
 
 
133
# TODO: Make sure to pass the right file and directory mode bits to all
 
134
# files/dirs created.
 
135
 
131
136
_DEFAULT_TIMEOUT_SECONDS = 300
132
137
_DEFAULT_POLL_SECONDS = 0.5
133
138
 
136
141
 
137
142
    __INFO_NAME = '/info'
138
143
 
139
 
    def __init__(self, transport, path):
 
144
    def __init__(self, transport, path, file_modebits=0644, dir_modebits=0755):
140
145
        """Create a new LockDir object.
141
146
 
142
147
        The LockDir is initially unlocked - this just creates the object.
151
156
        self.transport = transport
152
157
        self.path = path
153
158
        self._lock_held = False
154
 
        self._info_path = path + self.__INFO_NAME
 
159
        self._fake_read_lock = False
 
160
        self._held_dir = path + '/held'
 
161
        self._held_info_path = self._held_dir + self.__INFO_NAME
 
162
        self._file_modebits = file_modebits
 
163
        self._dir_modebits = dir_modebits
155
164
        self.nonce = rand_chars(20)
156
165
 
157
166
    def __repr__(self):
161
170
 
162
171
    is_held = property(lambda self: self._lock_held)
163
172
 
 
173
    def create(self):
 
174
        """Create the on-disk lock.
 
175
 
 
176
        This is typically only called when the object/directory containing the 
 
177
        directory is first created.  The lock is not held when it's created.
 
178
        """
 
179
        if self.transport.is_readonly():
 
180
            raise UnlockableTransport(self.transport)
 
181
        self.transport.mkdir(self.path)
 
182
 
164
183
    def attempt_lock(self):
165
184
        """Take the lock; fail if it's already held.
166
185
        
167
186
        If you wish to block until the lock can be obtained, call wait_lock()
168
187
        instead.
169
188
        """
 
189
        if self._fake_read_lock:
 
190
            raise LockContention(self)
170
191
        if self.transport.is_readonly():
171
192
            raise UnlockableTransport(self.transport)
172
193
        try:
173
 
            tmpname = '%s.pending.%s.tmp' % (self.path, rand_chars(20))
 
194
            tmpname = '%s/pending.%s.tmp' % (self.path, rand_chars(20))
174
195
            self.transport.mkdir(tmpname)
175
196
            sio = StringIO()
176
197
            self._prepare_info(sio)
177
198
            sio.seek(0)
178
199
            self.transport.put(tmpname + self.__INFO_NAME, sio)
179
 
            # FIXME: this turns into os.rename on posix, but into a fancy rename 
180
 
            # on Windows that may overwrite existing directory trees.  
181
 
            # NB: posix rename will overwrite empty directories, but not 
182
 
            # non-empty directories.
183
 
            self.transport.move(tmpname, self.path)
 
200
            self.transport.rename(tmpname, self._held_dir)
184
201
            self._lock_held = True
185
202
            self.confirm()
186
203
            return
192
209
    def unlock(self):
193
210
        """Release a held lock
194
211
        """
 
212
        if self._fake_read_lock:
 
213
            self._fake_read_lock = False
 
214
            return
195
215
        if not self._lock_held:
196
216
            raise LockNotHeld(self)
197
217
        # rename before deleting, because we can't atomically remove the whole
198
218
        # tree
199
 
        tmpname = '%s.releasing.%s.tmp' % (self.path, rand_chars(20))
200
 
        self.transport.rename(self.path, tmpname)
 
219
        tmpname = '%s/releasing.%s.tmp' % (self.path, rand_chars(20))
 
220
        self.transport.rename(self._held_dir, tmpname)
201
221
        self._lock_held = False
202
222
        self.transport.delete(tmpname + self.__INFO_NAME)
203
223
        self.transport.rmdir(tmpname)
228
248
            return
229
249
        if current_info != dead_holder_info:
230
250
            raise LockBreakMismatch(self, current_info, dead_holder_info)
231
 
        tmpname = '%s.broken.%s.tmp' % (self.path, rand_chars(20))
232
 
        self.transport.rename(self.path, tmpname)
 
251
        tmpname = '%s/broken.%s.tmp' % (self.path, rand_chars(20))
 
252
        self.transport.rename(self._held_dir, tmpname)
233
253
        # check that we actually broke the right lock, not someone else;
234
254
        # there's a small race window between checking it and doing the 
235
255
        # rename.
261
281
            raise LockBroken(self)
262
282
        
263
283
    def _read_info_file(self, path):
 
284
        """Read one given info file.
 
285
 
 
286
        peek() reads the info file of the lock holder, if any.
 
287
        """
264
288
        return self._parse_info(self.transport.get(path))
265
289
 
266
290
    def peek(self):
271
295
        Otherwise returns None.
272
296
        """
273
297
        try:
274
 
            info = self._read_info_file(self._info_path)
 
298
            info = self._read_info_file(self._held_info_path)
275
299
            assert isinstance(info, dict), \
276
300
                    "bad parse result %r" % info
277
301
            return info
319
343
            else:
320
344
                raise LockContention(self)
321
345
 
 
346
    def lock_write(self):
 
347
        """Wait for and acquire the lock."""
 
348
        self.attempt_lock()
 
349
 
 
350
    def lock_read(self):
 
351
        """Compatability-mode shared lock.
 
352
 
 
353
        LockDir doesn't support shared read-only locks, so this 
 
354
        just pretends that the lock is taken but really does nothing.
 
355
        """
 
356
        # At the moment Branches are commonly locked for read, but 
 
357
        # we can't rely on that remotely.  Once this is cleaned up,
 
358
        # reenable this warning to prevent it coming back in 
 
359
        # -- mbp 20060303
 
360
        ## warn("LockDir.lock_read falls back to write lock")
 
361
        if self._lock_held or self._fake_read_lock:
 
362
            raise LockContention(self)
 
363
        self._fake_read_lock = True
 
364
 
322
365
    def wait(self, timeout=20, poll=0.5):
323
366
        """Wait a certain period for a lock to be released."""
324
367
        # XXX: the transport interface doesn't let us guard