~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lockdir.py

  • Committer: Ian Clatworthy
  • Date: 2009-09-09 11:43:10 UTC
  • mto: (4634.37.2 prepare-2.0)
  • mto: This revision was merged to the branch mainline in revision 4689.
  • Revision ID: ian.clatworthy@canonical.com-20090909114310-glw7tv76i5gnx9pt
put rules back in Makefile supporting plain-style docs

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2006, 2007, 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
"""On-disk mutex protecting a resource
18
18
 
21
21
internal locks (such as flock etc) because they can be seen across all
22
22
transports, including http.
23
23
 
24
 
Objects can be read if there is only physical read access; therefore 
 
24
Objects can be read if there is only physical read access; therefore
25
25
readers can never be required to create a lock, though they will
26
26
check whether a writer is using the lock.  Writers can't detect
27
27
whether anyone else is reading from the resource as they write.
56
56
 
57
57
The desired characteristics are:
58
58
 
59
 
* Locks are not reentrant.  (That is, a client that tries to take a 
 
59
* Locks are not reentrant.  (That is, a client that tries to take a
60
60
  lock it already holds may deadlock or fail.)
61
61
* Stale locks can be guessed at by a heuristic
62
62
* Lost locks can be broken by any client
78
78
and deadlocks will likely occur if the locks are aliased.
79
79
 
80
80
In the future we may add a "freshen" method which can be called
81
 
by a lock holder to check that their lock has not been broken, and to 
 
81
by a lock holder to check that their lock has not been broken, and to
82
82
update the timestamp within it.
83
83
 
84
84
Example usage:
105
105
 
106
106
import os
107
107
import time
108
 
from cStringIO import StringIO
109
108
 
110
109
from bzrlib import (
111
110
    debug,
112
111
    errors,
 
112
    lock,
113
113
    )
114
114
import bzrlib.config
115
115
from bzrlib.errors import (
118
118
        LockBreakMismatch,
119
119
        LockBroken,
120
120
        LockContention,
 
121
        LockFailed,
121
122
        LockNotHeld,
122
123
        NoSuchFile,
123
124
        PathError,
124
125
        ResourceBusy,
125
 
        UnlockableTransport,
 
126
        TransportError,
126
127
        )
127
128
from bzrlib.trace import mutter, note
128
 
from bzrlib.transport import Transport
129
 
from bzrlib.osutils import rand_chars, format_delta
130
 
from bzrlib.rio import read_stanza, Stanza
 
129
from bzrlib.osutils import format_delta, rand_chars, get_host_name
131
130
import bzrlib.ui
132
131
 
 
132
from bzrlib.lazy_import import lazy_import
 
133
lazy_import(globals(), """
 
134
from bzrlib import rio
 
135
""")
133
136
 
134
137
# XXX: At the moment there is no consideration of thread safety on LockDir
135
138
# objects.  This should perhaps be updated - e.g. if two threads try to take a
150
153
_DEFAULT_POLL_SECONDS = 1.0
151
154
 
152
155
 
153
 
class LockDir(object):
154
 
    """Write-lock guarding access to data."""
 
156
class LockDir(lock.Lock):
 
157
    """Write-lock guarding access to data.
 
158
    """
155
159
 
156
160
    __INFO_NAME = '/info'
157
161
 
162
166
 
163
167
        :param transport: Transport which will contain the lock
164
168
 
165
 
        :param path: Path to the lock within the base directory of the 
 
169
        :param path: Path to the lock within the base directory of the
166
170
            transport.
167
171
        """
168
 
        assert isinstance(transport, Transport), \
169
 
            ("not a transport: %r" % transport)
170
172
        self.transport = transport
171
173
        self.path = path
172
174
        self._lock_held = False
189
191
    def create(self, mode=None):
190
192
        """Create the on-disk lock.
191
193
 
192
 
        This is typically only called when the object/directory containing the 
 
194
        This is typically only called when the object/directory containing the
193
195
        directory is first created.  The lock is not held when it's created.
194
196
        """
195
 
        if self.transport.is_readonly():
196
 
            raise UnlockableTransport(self.transport)
197
197
        self._trace("create lock directory")
198
 
        self.transport.mkdir(self.path, mode=mode)
 
198
        try:
 
199
            self.transport.mkdir(self.path, mode=mode)
 
200
        except (TransportError, PathError), e:
 
201
            raise LockFailed(self, e)
 
202
 
199
203
 
200
204
    def _attempt_lock(self):
201
205
        """Make the pending directory and attempt to rename into place.
202
 
        
 
206
 
203
207
        If the rename succeeds, we read back the info file to check that we
204
208
        really got the lock.
205
209
 
214
218
        """
215
219
        self._trace("lock_write...")
216
220
        start_time = time.time()
217
 
        tmpname = self._create_pending_dir()
 
221
        try:
 
222
            tmpname = self._create_pending_dir()
 
223
        except (errors.TransportError, PathError), e:
 
224
            self._trace("... failed to create pending dir, %s", e)
 
225
            raise LockFailed(self, e)
218
226
        try:
219
227
            self.transport.rename(tmpname, self._held_dir)
220
 
        except (PathError, DirectoryNotEmpty, FileExists, ResourceBusy), e:
 
228
        except (errors.TransportError, PathError, DirectoryNotEmpty,
 
229
                FileExists, ResourceBusy), e:
221
230
            self._trace("... contention, %s", e)
222
231
            self._remove_pending_dir(tmpname)
223
232
            raise LockContention(self)
245
254
    def _remove_pending_dir(self, tmpname):
246
255
        """Remove the pending directory
247
256
 
248
 
        This is called if we failed to rename into place, so that the pending 
 
257
        This is called if we failed to rename into place, so that the pending
249
258
        dirs don't clutter up the lockdir.
250
259
        """
251
260
        self._trace("remove %s", tmpname)
284
293
            self._fake_read_lock = False
285
294
            return
286
295
        if not self._lock_held:
287
 
            raise LockNotHeld(self)
 
296
            return lock.cant_unlock_not_held(self)
288
297
        if self._locked_via_token:
289
298
            self._locked_via_token = False
290
299
            self._lock_held = False
291
300
        else:
 
301
            old_nonce = self.nonce
292
302
            # rename before deleting, because we can't atomically remove the
293
303
            # whole tree
294
304
            start_time = time.time()
314
324
                self.transport.delete_tree(tmpname)
315
325
            self._trace("... unlock succeeded after %dms",
316
326
                    (time.time() - start_time) * 1000)
 
327
            result = lock.LockResult(self.transport.abspath(self.path),
 
328
                                     old_nonce)
 
329
            for hook in self.hooks['lock_released']:
 
330
                hook(result)
317
331
 
318
332
    def break_lock(self):
319
333
        """Break a lock not held by this instance of LockDir.
328
342
            lock_info = '\n'.join(self._format_lock_info(holder_info))
329
343
            if bzrlib.ui.ui_factory.get_boolean("Break %s" % lock_info):
330
344
                self.force_break(holder_info)
331
 
        
 
345
 
332
346
    def force_break(self, dead_holder_info):
333
347
        """Release a lock held by another process.
334
348
 
342
356
        LockBreakMismatch is raised.
343
357
 
344
358
        After the lock is broken it will not be held by any process.
345
 
        It is possible that another process may sneak in and take the 
 
359
        It is possible that another process may sneak in and take the
346
360
        lock before the breaking process acquires it.
347
361
        """
348
362
        if not isinstance(dead_holder_info, dict):
357
371
        tmpname = '%s/broken.%s.tmp' % (self.path, rand_chars(20))
358
372
        self.transport.rename(self._held_dir, tmpname)
359
373
        # check that we actually broke the right lock, not someone else;
360
 
        # there's a small race window between checking it and doing the 
 
374
        # there's a small race window between checking it and doing the
361
375
        # rename.
362
376
        broken_info_path = tmpname + self.__INFO_NAME
363
377
        broken_info = self._read_info_file(broken_info_path)
365
379
            raise LockBreakMismatch(self, broken_info, dead_holder_info)
366
380
        self.transport.delete(broken_info_path)
367
381
        self.transport.rmdir(tmpname)
 
382
        result = lock.LockResult(self.transport.abspath(self.path),
 
383
                                 current_info.get('nonce'))
 
384
        for hook in self.hooks['lock_broken']:
 
385
            hook(result)
368
386
 
369
387
    def _check_not_locked(self):
370
388
        """If the lock is held by this instance, raise an error."""
378
396
        or if the lock has been affected by a bug.
379
397
 
380
398
        If the lock is not thought to be held, raises LockNotHeld.  If
381
 
        the lock is thought to be held but has been broken, raises 
 
399
        the lock is thought to be held but has been broken, raises
382
400
        LockBroken.
383
401
        """
384
402
        if not self._lock_held:
390
408
        if info.get('nonce') != self.nonce:
391
409
            # there is a lock, but not ours
392
410
            raise LockBroken(self)
393
 
        
 
411
 
394
412
    def _read_info_file(self, path):
395
413
        """Read one given info file.
396
414
 
400
418
 
401
419
    def peek(self):
402
420
        """Check if the lock is held by anyone.
403
 
        
 
421
 
404
422
        If it is held, this returns the lock info structure as a rio Stanza,
405
423
        which contains some information about the current lock holder.
406
424
        Otherwise returns None.
408
426
        try:
409
427
            info = self._read_info_file(self._held_info_path)
410
428
            self._trace("peek -> held")
411
 
            assert isinstance(info, dict), \
412
 
                    "bad parse result %r" % info
413
429
            return info
414
430
        except NoSuchFile, e:
415
431
            self._trace("peek -> not held")
417
433
    def _prepare_info(self):
418
434
        """Write information about a pending lock to a temporary file.
419
435
        """
420
 
        import socket
421
436
        # XXX: is creating this here inefficient?
422
437
        config = bzrlib.config.GlobalConfig()
423
438
        try:
424
439
            user = config.user_email()
425
440
        except errors.NoEmailInUsername:
426
441
            user = config.username()
427
 
        s = Stanza(hostname=socket.gethostname(),
 
442
        s = rio.Stanza(hostname=get_host_name(),
428
443
                   pid=str(os.getpid()),
429
444
                   start_time=str(int(time.time())),
430
445
                   nonce=self.nonce,
433
448
        return s.to_string()
434
449
 
435
450
    def _parse_info(self, info_file):
436
 
        return read_stanza(info_file.readlines()).as_dict()
 
451
        return rio.read_stanza(info_file.readlines()).as_dict()
437
452
 
438
453
    def attempt_lock(self):
439
454
        """Take the lock; fail if it's already held.
440
 
        
 
455
 
441
456
        If you wish to block until the lock can be obtained, call wait_lock()
442
457
        instead.
443
458
 
446
461
        """
447
462
        if self._fake_read_lock:
448
463
            raise LockContention(self)
449
 
        if self.transport.is_readonly():
450
 
            raise UnlockableTransport(self.transport)
451
 
        return self._attempt_lock()
 
464
        result = self._attempt_lock()
 
465
        hook_result = lock.LockResult(self.transport.abspath(self.path),
 
466
                self.nonce)
 
467
        for hook in self.hooks['lock_acquired']:
 
468
            hook(hook_result)
 
469
        return result
452
470
 
453
471
    def wait_lock(self, timeout=None, poll=None, max_attempts=None):
454
472
        """Wait a certain period for a lock.
461
479
 
462
480
        :param timeout: Approximate maximum amount of time to wait for the
463
481
        lock, in seconds.
464
 
         
 
482
 
465
483
        :param poll: Delay in seconds between retrying the lock.
466
484
 
467
485
        :param max_attempts: Maximum number of times to try to lock.
503
521
                if deadline_str is None:
504
522
                    deadline_str = time.strftime('%H:%M:%S',
505
523
                                                 time.localtime(deadline))
 
524
                lock_url = self.transport.abspath(self.path)
506
525
                self._report_function('%s %s\n'
507
526
                                      '%s\n' # held by
508
527
                                      '%s\n' # locked ... ago
509
 
                                      'Will continue to try until %s\n',
 
528
                                      'Will continue to try until %s, unless '
 
529
                                      'you press Ctrl-C\n'
 
530
                                      'If you\'re sure that it\'s not being '
 
531
                                      'modified, use bzr break-lock %s',
510
532
                                      start,
511
533
                                      formatted_info[0],
512
534
                                      formatted_info[1],
513
535
                                      formatted_info[2],
514
 
                                      deadline_str)
 
536
                                      deadline_str,
 
537
                                      lock_url)
515
538
 
516
539
            if (max_attempts is not None) and (attempt_count >= max_attempts):
517
540
                self._trace("exceeded %d attempts")
522
545
            else:
523
546
                self._trace("timeout after waiting %ss", timeout)
524
547
                raise LockContention(self)
525
 
    
 
548
 
526
549
    def leave_in_place(self):
527
550
        self._locked_via_token = True
528
551
 
531
554
 
532
555
    def lock_write(self, token=None):
533
556
        """Wait for and acquire the lock.
534
 
        
 
557
 
535
558
        :param token: if this is already locked, then lock_write will fail
536
559
            unless the token matches the existing lock.
537
560
        :returns: a token if this instance supports tokens, otherwise None.
543
566
        A token should be passed in if you know that you have locked the object
544
567
        some other way, and need to synchronise this object's state with that
545
568
        fact.
546
 
         
 
569
 
547
570
        XXX: docstring duplicated from LockableFiles.lock_write.
548
571
        """
549
572
        if token is not None:
558
581
    def lock_read(self):
559
582
        """Compatibility-mode shared lock.
560
583
 
561
 
        LockDir doesn't support shared read-only locks, so this 
 
584
        LockDir doesn't support shared read-only locks, so this
562
585
        just pretends that the lock is taken but really does nothing.
563
586
        """
564
 
        # At the moment Branches are commonly locked for read, but 
 
587
        # At the moment Branches are commonly locked for read, but
565
588
        # we can't rely on that remotely.  Once this is cleaned up,
566
 
        # reenable this warning to prevent it coming back in 
 
589
        # reenable this warning to prevent it coming back in
567
590
        # -- mbp 20060303
568
591
        ## warn("LockDir.lock_read falls back to write lock")
569
592
        if self._lock_held or self._fake_read_lock: