~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lockdir.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2008-03-16 14:01:20 UTC
  • mfrom: (3280.2.5 integration)
  • Revision ID: pqm@pqm.ubuntu.com-20080316140120-i3yq8yr1l66m11h7
Start 1.4 development

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
 
1
# Copyright (C) 2006, 2007 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
"""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
108
109
 
109
110
from bzrlib import (
110
111
    debug,
111
112
    errors,
112
 
    lock,
113
113
    )
114
114
import bzrlib.config
115
115
from bzrlib.errors import (
124
124
        PathError,
125
125
        ResourceBusy,
126
126
        TransportError,
 
127
        UnlockableTransport,
127
128
        )
128
129
from bzrlib.trace import mutter, note
129
 
from bzrlib.osutils import format_delta, rand_chars, get_host_name
 
130
from bzrlib.transport import Transport
 
131
from bzrlib.osutils import rand_chars, format_delta
 
132
from bzrlib.rio import read_stanza, Stanza
130
133
import bzrlib.ui
131
134
 
132
 
from bzrlib.lazy_import import lazy_import
133
 
lazy_import(globals(), """
134
 
from bzrlib import rio
135
 
""")
136
135
 
137
136
# XXX: At the moment there is no consideration of thread safety on LockDir
138
137
# objects.  This should perhaps be updated - e.g. if two threads try to take a
153
152
_DEFAULT_POLL_SECONDS = 1.0
154
153
 
155
154
 
156
 
class LockDir(lock.Lock):
157
 
    """Write-lock guarding access to data.
158
 
    """
 
155
class LockDir(object):
 
156
    """Write-lock guarding access to data."""
159
157
 
160
158
    __INFO_NAME = '/info'
161
159
 
166
164
 
167
165
        :param transport: Transport which will contain the lock
168
166
 
169
 
        :param path: Path to the lock within the base directory of the
 
167
        :param path: Path to the lock within the base directory of the 
170
168
            transport.
171
169
        """
 
170
        assert isinstance(transport, Transport), \
 
171
            ("not a transport: %r" % transport)
172
172
        self.transport = transport
173
173
        self.path = path
174
174
        self._lock_held = False
191
191
    def create(self, mode=None):
192
192
        """Create the on-disk lock.
193
193
 
194
 
        This is typically only called when the object/directory containing the
 
194
        This is typically only called when the object/directory containing the 
195
195
        directory is first created.  The lock is not held when it's created.
196
196
        """
197
197
        self._trace("create lock directory")
203
203
 
204
204
    def _attempt_lock(self):
205
205
        """Make the pending directory and attempt to rename into place.
206
 
 
 
206
        
207
207
        If the rename succeeds, we read back the info file to check that we
208
208
        really got the lock.
209
209
 
254
254
    def _remove_pending_dir(self, tmpname):
255
255
        """Remove the pending directory
256
256
 
257
 
        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 
258
258
        dirs don't clutter up the lockdir.
259
259
        """
260
260
        self._trace("remove %s", tmpname)
298
298
            self._locked_via_token = False
299
299
            self._lock_held = False
300
300
        else:
301
 
            old_nonce = self.nonce
302
301
            # rename before deleting, because we can't atomically remove the
303
302
            # whole tree
304
303
            start_time = time.time()
324
323
                self.transport.delete_tree(tmpname)
325
324
            self._trace("... unlock succeeded after %dms",
326
325
                    (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)
331
326
 
332
327
    def break_lock(self):
333
328
        """Break a lock not held by this instance of LockDir.
342
337
            lock_info = '\n'.join(self._format_lock_info(holder_info))
343
338
            if bzrlib.ui.ui_factory.get_boolean("Break %s" % lock_info):
344
339
                self.force_break(holder_info)
345
 
 
 
340
        
346
341
    def force_break(self, dead_holder_info):
347
342
        """Release a lock held by another process.
348
343
 
356
351
        LockBreakMismatch is raised.
357
352
 
358
353
        After the lock is broken it will not be held by any process.
359
 
        It is possible that another process may sneak in and take the
 
354
        It is possible that another process may sneak in and take the 
360
355
        lock before the breaking process acquires it.
361
356
        """
362
357
        if not isinstance(dead_holder_info, dict):
371
366
        tmpname = '%s/broken.%s.tmp' % (self.path, rand_chars(20))
372
367
        self.transport.rename(self._held_dir, tmpname)
373
368
        # check that we actually broke the right lock, not someone else;
374
 
        # there's a small race window between checking it and doing the
 
369
        # there's a small race window between checking it and doing the 
375
370
        # rename.
376
371
        broken_info_path = tmpname + self.__INFO_NAME
377
372
        broken_info = self._read_info_file(broken_info_path)
379
374
            raise LockBreakMismatch(self, broken_info, dead_holder_info)
380
375
        self.transport.delete(broken_info_path)
381
376
        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)
386
377
 
387
378
    def _check_not_locked(self):
388
379
        """If the lock is held by this instance, raise an error."""
396
387
        or if the lock has been affected by a bug.
397
388
 
398
389
        If the lock is not thought to be held, raises LockNotHeld.  If
399
 
        the lock is thought to be held but has been broken, raises
 
390
        the lock is thought to be held but has been broken, raises 
400
391
        LockBroken.
401
392
        """
402
393
        if not self._lock_held:
408
399
        if info.get('nonce') != self.nonce:
409
400
            # there is a lock, but not ours
410
401
            raise LockBroken(self)
411
 
 
 
402
        
412
403
    def _read_info_file(self, path):
413
404
        """Read one given info file.
414
405
 
418
409
 
419
410
    def peek(self):
420
411
        """Check if the lock is held by anyone.
421
 
 
 
412
        
422
413
        If it is held, this returns the lock info structure as a rio Stanza,
423
414
        which contains some information about the current lock holder.
424
415
        Otherwise returns None.
426
417
        try:
427
418
            info = self._read_info_file(self._held_info_path)
428
419
            self._trace("peek -> held")
 
420
            assert isinstance(info, dict), \
 
421
                    "bad parse result %r" % info
429
422
            return info
430
423
        except NoSuchFile, e:
431
424
            self._trace("peek -> not held")
433
426
    def _prepare_info(self):
434
427
        """Write information about a pending lock to a temporary file.
435
428
        """
 
429
        import socket
436
430
        # XXX: is creating this here inefficient?
437
431
        config = bzrlib.config.GlobalConfig()
438
432
        try:
439
433
            user = config.user_email()
440
434
        except errors.NoEmailInUsername:
441
435
            user = config.username()
442
 
        s = rio.Stanza(hostname=get_host_name(),
 
436
        s = Stanza(hostname=socket.gethostname(),
443
437
                   pid=str(os.getpid()),
444
438
                   start_time=str(int(time.time())),
445
439
                   nonce=self.nonce,
448
442
        return s.to_string()
449
443
 
450
444
    def _parse_info(self, info_file):
451
 
        return rio.read_stanza(info_file.readlines()).as_dict()
 
445
        return read_stanza(info_file.readlines()).as_dict()
452
446
 
453
447
    def attempt_lock(self):
454
448
        """Take the lock; fail if it's already held.
455
 
 
 
449
        
456
450
        If you wish to block until the lock can be obtained, call wait_lock()
457
451
        instead.
458
452
 
461
455
        """
462
456
        if self._fake_read_lock:
463
457
            raise LockContention(self)
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
 
458
        return self._attempt_lock()
470
459
 
471
460
    def wait_lock(self, timeout=None, poll=None, max_attempts=None):
472
461
        """Wait a certain period for a lock.
479
468
 
480
469
        :param timeout: Approximate maximum amount of time to wait for the
481
470
        lock, in seconds.
482
 
 
 
471
         
483
472
        :param poll: Delay in seconds between retrying the lock.
484
473
 
485
474
        :param max_attempts: Maximum number of times to try to lock.
521
510
                if deadline_str is None:
522
511
                    deadline_str = time.strftime('%H:%M:%S',
523
512
                                                 time.localtime(deadline))
524
 
                lock_url = self.transport.abspath(self.path)
525
513
                self._report_function('%s %s\n'
526
514
                                      '%s\n' # held by
527
515
                                      '%s\n' # locked ... ago
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',
 
516
                                      'Will continue to try until %s\n',
532
517
                                      start,
533
518
                                      formatted_info[0],
534
519
                                      formatted_info[1],
535
520
                                      formatted_info[2],
536
 
                                      deadline_str,
537
 
                                      lock_url)
 
521
                                      deadline_str)
538
522
 
539
523
            if (max_attempts is not None) and (attempt_count >= max_attempts):
540
524
                self._trace("exceeded %d attempts")
545
529
            else:
546
530
                self._trace("timeout after waiting %ss", timeout)
547
531
                raise LockContention(self)
548
 
 
 
532
    
549
533
    def leave_in_place(self):
550
534
        self._locked_via_token = True
551
535
 
554
538
 
555
539
    def lock_write(self, token=None):
556
540
        """Wait for and acquire the lock.
557
 
 
 
541
        
558
542
        :param token: if this is already locked, then lock_write will fail
559
543
            unless the token matches the existing lock.
560
544
        :returns: a token if this instance supports tokens, otherwise None.
566
550
        A token should be passed in if you know that you have locked the object
567
551
        some other way, and need to synchronise this object's state with that
568
552
        fact.
569
 
 
 
553
         
570
554
        XXX: docstring duplicated from LockableFiles.lock_write.
571
555
        """
572
556
        if token is not None:
581
565
    def lock_read(self):
582
566
        """Compatibility-mode shared lock.
583
567
 
584
 
        LockDir doesn't support shared read-only locks, so this
 
568
        LockDir doesn't support shared read-only locks, so this 
585
569
        just pretends that the lock is taken but really does nothing.
586
570
        """
587
 
        # At the moment Branches are commonly locked for read, but
 
571
        # At the moment Branches are commonly locked for read, but 
588
572
        # we can't rely on that remotely.  Once this is cleaned up,
589
 
        # reenable this warning to prevent it coming back in
 
573
        # reenable this warning to prevent it coming back in 
590
574
        # -- mbp 20060303
591
575
        ## warn("LockDir.lock_read falls back to write lock")
592
576
        if self._lock_held or self._fake_read_lock: