/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/lockdir.py

  • Committer: Martin von Gagern
  • Date: 2010-04-20 08:47:38 UTC
  • mfrom: (5167 +trunk)
  • mto: This revision was merged to the branch mainline in revision 5195.
  • Revision ID: martin.vgagern@gmx.net-20100420084738-ygymnqmdllzrhpfn
merge trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
 
1
# Copyright (C) 2006-2010 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,
113
112
    lock,
 
113
    osutils,
114
114
    )
115
115
import bzrlib.config
 
116
from bzrlib.decorators import only_raises
116
117
from bzrlib.errors import (
117
118
        DirectoryNotEmpty,
118
119
        FileExists,
125
126
        PathError,
126
127
        ResourceBusy,
127
128
        TransportError,
128
 
        UnlockableTransport,
129
129
        )
130
 
from bzrlib.hooks import Hooks
131
130
from bzrlib.trace import mutter, note
132
 
from bzrlib.transport import Transport
133
 
from bzrlib.osutils import rand_chars, format_delta, get_host_name
134
 
from bzrlib.rio import read_stanza, Stanza
 
131
from bzrlib.osutils import format_delta, rand_chars, get_host_name
135
132
import bzrlib.ui
136
133
 
 
134
from bzrlib.lazy_import import lazy_import
 
135
lazy_import(globals(), """
 
136
from bzrlib import rio
 
137
""")
137
138
 
138
139
# XXX: At the moment there is no consideration of thread safety on LockDir
139
140
# objects.  This should perhaps be updated - e.g. if two threads try to take a
167
168
 
168
169
        :param transport: Transport which will contain the lock
169
170
 
170
 
        :param path: Path to the lock within the base directory of the 
 
171
        :param path: Path to the lock within the base directory of the
171
172
            transport.
172
173
        """
173
174
        self.transport = transport
192
193
    def create(self, mode=None):
193
194
        """Create the on-disk lock.
194
195
 
195
 
        This is typically only called when the object/directory containing the 
 
196
        This is typically only called when the object/directory containing the
196
197
        directory is first created.  The lock is not held when it's created.
197
198
        """
198
199
        self._trace("create lock directory")
204
205
 
205
206
    def _attempt_lock(self):
206
207
        """Make the pending directory and attempt to rename into place.
207
 
        
 
208
 
208
209
        If the rename succeeds, we read back the info file to check that we
209
210
        really got the lock.
210
211
 
241
242
        # incorrect.  It's possible some other servers or filesystems will
242
243
        # have a similar bug allowing someone to think they got the lock
243
244
        # when it's already held.
 
245
        #
 
246
        # See <https://bugs.edge.launchpad.net/bzr/+bug/498378> for one case.
 
247
        #
 
248
        # Strictly the check is unnecessary and a waste of time for most
 
249
        # people, but probably worth trapping if something is wrong.
244
250
        info = self.peek()
245
251
        self._trace("after locking, info=%r", info)
246
 
        if info['nonce'] != self.nonce:
 
252
        if info is None:
 
253
            raise LockFailed(self, "lock was renamed into place, but "
 
254
                "now is missing!")
 
255
        if info.get('nonce') != self.nonce:
247
256
            self._trace("rename succeeded, "
248
257
                "but lock is still held by someone else")
249
258
            raise LockContention(self)
255
264
    def _remove_pending_dir(self, tmpname):
256
265
        """Remove the pending directory
257
266
 
258
 
        This is called if we failed to rename into place, so that the pending 
 
267
        This is called if we failed to rename into place, so that the pending
259
268
        dirs don't clutter up the lockdir.
260
269
        """
261
270
        self._trace("remove %s", tmpname)
287
296
                                            info_bytes)
288
297
        return tmpname
289
298
 
 
299
    @only_raises(LockNotHeld, LockBroken)
290
300
    def unlock(self):
291
301
        """Release a held lock
292
302
        """
294
304
            self._fake_read_lock = False
295
305
            return
296
306
        if not self._lock_held:
297
 
            raise LockNotHeld(self)
 
307
            return lock.cant_unlock_not_held(self)
298
308
        if self._locked_via_token:
299
309
            self._locked_via_token = False
300
310
            self._lock_held = False
326
336
            self._trace("... unlock succeeded after %dms",
327
337
                    (time.time() - start_time) * 1000)
328
338
            result = lock.LockResult(self.transport.abspath(self.path),
329
 
                old_nonce)
 
339
                                     old_nonce)
330
340
            for hook in self.hooks['lock_released']:
331
341
                hook(result)
332
342
 
343
353
            lock_info = '\n'.join(self._format_lock_info(holder_info))
344
354
            if bzrlib.ui.ui_factory.get_boolean("Break %s" % lock_info):
345
355
                self.force_break(holder_info)
346
 
        
 
356
 
347
357
    def force_break(self, dead_holder_info):
348
358
        """Release a lock held by another process.
349
359
 
357
367
        LockBreakMismatch is raised.
358
368
 
359
369
        After the lock is broken it will not be held by any process.
360
 
        It is possible that another process may sneak in and take the 
 
370
        It is possible that another process may sneak in and take the
361
371
        lock before the breaking process acquires it.
362
372
        """
363
373
        if not isinstance(dead_holder_info, dict):
372
382
        tmpname = '%s/broken.%s.tmp' % (self.path, rand_chars(20))
373
383
        self.transport.rename(self._held_dir, tmpname)
374
384
        # check that we actually broke the right lock, not someone else;
375
 
        # there's a small race window between checking it and doing the 
 
385
        # there's a small race window between checking it and doing the
376
386
        # rename.
377
387
        broken_info_path = tmpname + self.__INFO_NAME
378
388
        broken_info = self._read_info_file(broken_info_path)
380
390
            raise LockBreakMismatch(self, broken_info, dead_holder_info)
381
391
        self.transport.delete(broken_info_path)
382
392
        self.transport.rmdir(tmpname)
 
393
        result = lock.LockResult(self.transport.abspath(self.path),
 
394
                                 current_info.get('nonce'))
 
395
        for hook in self.hooks['lock_broken']:
 
396
            hook(result)
383
397
 
384
398
    def _check_not_locked(self):
385
399
        """If the lock is held by this instance, raise an error."""
393
407
        or if the lock has been affected by a bug.
394
408
 
395
409
        If the lock is not thought to be held, raises LockNotHeld.  If
396
 
        the lock is thought to be held but has been broken, raises 
 
410
        the lock is thought to be held but has been broken, raises
397
411
        LockBroken.
398
412
        """
399
413
        if not self._lock_held:
405
419
        if info.get('nonce') != self.nonce:
406
420
            # there is a lock, but not ours
407
421
            raise LockBroken(self)
408
 
        
 
422
 
409
423
    def _read_info_file(self, path):
410
424
        """Read one given info file.
411
425
 
412
426
        peek() reads the info file of the lock holder, if any.
413
427
        """
414
 
        return self._parse_info(self.transport.get(path))
 
428
        return self._parse_info(self.transport.get_bytes(path))
415
429
 
416
430
    def peek(self):
417
431
        """Check if the lock is held by anyone.
418
 
        
419
 
        If it is held, this returns the lock info structure as a rio Stanza,
 
432
 
 
433
        If it is held, this returns the lock info structure as a dict
420
434
        which contains some information about the current lock holder.
421
435
        Otherwise returns None.
422
436
        """
436
450
            user = config.user_email()
437
451
        except errors.NoEmailInUsername:
438
452
            user = config.username()
439
 
        s = Stanza(hostname=get_host_name(),
 
453
        s = rio.Stanza(hostname=get_host_name(),
440
454
                   pid=str(os.getpid()),
441
455
                   start_time=str(int(time.time())),
442
456
                   nonce=self.nonce,
444
458
                   )
445
459
        return s.to_string()
446
460
 
447
 
    def _parse_info(self, info_file):
448
 
        return read_stanza(info_file.readlines()).as_dict()
 
461
    def _parse_info(self, info_bytes):
 
462
        stanza = rio.read_stanza(osutils.split_lines(info_bytes))
 
463
        if stanza is None:
 
464
            # see bug 185013; we fairly often end up with the info file being
 
465
            # empty after an interruption; we could log a message here but
 
466
            # there may not be much we can say
 
467
            return {}
 
468
        else:
 
469
            return stanza.as_dict()
449
470
 
450
471
    def attempt_lock(self):
451
472
        """Take the lock; fail if it's already held.
452
 
        
 
473
 
453
474
        If you wish to block until the lock can be obtained, call wait_lock()
454
475
        instead.
455
476
 
476
497
 
477
498
        :param timeout: Approximate maximum amount of time to wait for the
478
499
        lock, in seconds.
479
 
         
 
500
 
480
501
        :param poll: Delay in seconds between retrying the lock.
481
502
 
482
503
        :param max_attempts: Maximum number of times to try to lock.
519
540
                    deadline_str = time.strftime('%H:%M:%S',
520
541
                                                 time.localtime(deadline))
521
542
                lock_url = self.transport.abspath(self.path)
 
543
                # See <https://bugs.edge.launchpad.net/bzr/+bug/250451>
 
544
                # the URL here is sometimes not one that is useful to the
 
545
                # user, perhaps being wrapped in a lp-%d or chroot decorator,
 
546
                # especially if this error is issued from the server.
522
547
                self._report_function('%s %s\n'
523
 
                                      '%s\n' # held by
524
 
                                      '%s\n' # locked ... ago
525
 
                                      'Will continue to try until %s, unless '
526
 
                                      'you press Ctrl-C\n'
527
 
                                      'If you\'re sure that it\'s not being '
528
 
                                      'modified, use bzr break-lock %s',
529
 
                                      start,
530
 
                                      formatted_info[0],
531
 
                                      formatted_info[1],
532
 
                                      formatted_info[2],
533
 
                                      deadline_str,
534
 
                                      lock_url)
 
548
                    '%s\n' # held by
 
549
                    '%s\n' # locked ... ago
 
550
                    'Will continue to try until %s, unless '
 
551
                    'you press Ctrl-C.\n'
 
552
                    'See "bzr help break-lock" for more.',
 
553
                    start,
 
554
                    formatted_info[0],
 
555
                    formatted_info[1],
 
556
                    formatted_info[2],
 
557
                    deadline_str,
 
558
                    )
535
559
 
536
560
            if (max_attempts is not None) and (attempt_count >= max_attempts):
537
561
                self._trace("exceeded %d attempts")
542
566
            else:
543
567
                self._trace("timeout after waiting %ss", timeout)
544
568
                raise LockContention(self)
545
 
    
 
569
 
546
570
    def leave_in_place(self):
547
571
        self._locked_via_token = True
548
572
 
551
575
 
552
576
    def lock_write(self, token=None):
553
577
        """Wait for and acquire the lock.
554
 
        
 
578
 
555
579
        :param token: if this is already locked, then lock_write will fail
556
580
            unless the token matches the existing lock.
557
581
        :returns: a token if this instance supports tokens, otherwise None.
563
587
        A token should be passed in if you know that you have locked the object
564
588
        some other way, and need to synchronise this object's state with that
565
589
        fact.
566
 
         
 
590
 
567
591
        XXX: docstring duplicated from LockableFiles.lock_write.
568
592
        """
569
593
        if token is not None:
578
602
    def lock_read(self):
579
603
        """Compatibility-mode shared lock.
580
604
 
581
 
        LockDir doesn't support shared read-only locks, so this 
 
605
        LockDir doesn't support shared read-only locks, so this
582
606
        just pretends that the lock is taken but really does nothing.
583
607
        """
584
 
        # At the moment Branches are commonly locked for read, but 
 
608
        # At the moment Branches are commonly locked for read, but
585
609
        # we can't rely on that remotely.  Once this is cleaned up,
586
 
        # reenable this warning to prevent it coming back in 
 
610
        # reenable this warning to prevent it coming back in
587
611
        # -- mbp 20060303
588
612
        ## warn("LockDir.lock_read falls back to write lock")
589
613
        if self._lock_held or self._fake_read_lock:
593
617
    def _format_lock_info(self, info):
594
618
        """Turn the contents of peek() into something for the user"""
595
619
        lock_url = self.transport.abspath(self.path)
596
 
        delta = time.time() - int(info['start_time'])
 
620
        start_time = info.get('start_time')
 
621
        if start_time is None:
 
622
            time_ago = '(unknown)'
 
623
        else:
 
624
            time_ago = format_delta(time.time() - int(info['start_time']))
597
625
        return [
598
626
            'lock %s' % (lock_url,),
599
 
            'held by %(user)s on host %(hostname)s [process #%(pid)s]' % info,
600
 
            'locked %s' % (format_delta(delta),),
 
627
            'held by %s on host %s [process #%s]' %
 
628
                tuple([info.get(x, '<unknown>') for x in ['user', 'hostname', 'pid']]),
 
629
            'locked %s' % (time_ago,),
601
630
            ]
602
631
 
603
632
    def validate_token(self, token):