~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lockdir.py

  • Committer: Martin Pool
  • Date: 2007-07-11 01:55:33 UTC
  • mto: This revision was merged to the branch mainline in revision 2599.
  • Revision ID: mbp@sourcefrog.net-20070711015533-dzcxkjg0ujh8yuhl
Option help improvements (thanks jamesw)

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