/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: John Arbash Meinel
  • Date: 2006-09-20 14:51:03 UTC
  • mfrom: (0.8.23 version_info)
  • mto: This revision was merged to the branch mainline in revision 2028.
  • Revision ID: john@arbash-meinel.com-20060920145103-02725c6d6c886040
[merge] version-info plugin, and cleanup for layout in bzr

Show diffs side-by-side

added added

removed removed

Lines of Context:
96
96
 
97
97
import os
98
98
import time
99
 
from cStringIO import StringIO
 
99
from StringIO import StringIO
100
100
 
101
 
from bzrlib import (
102
 
    errors,
103
 
    )
104
101
import bzrlib.config
105
102
from bzrlib.errors import (
106
103
        DirectoryNotEmpty,
114
111
        ResourceBusy,
115
112
        UnlockableTransport,
116
113
        )
117
 
from bzrlib.trace import mutter, note
 
114
from bzrlib.trace import mutter
118
115
from bzrlib.transport import Transport
119
 
from bzrlib.osutils import rand_chars, format_delta
 
116
from bzrlib.osutils import rand_chars
120
117
from bzrlib.rio import read_stanza, Stanza
121
 
import bzrlib.ui
122
 
 
123
118
 
124
119
# XXX: At the moment there is no consideration of thread safety on LockDir
125
120
# objects.  This should perhaps be updated - e.g. if two threads try to take a
135
130
# TODO: Make sure to pass the right file and directory mode bits to all
136
131
# files/dirs created.
137
132
 
138
 
 
139
133
_DEFAULT_TIMEOUT_SECONDS = 300
140
 
_DEFAULT_POLL_SECONDS = 1.0
141
 
 
 
134
_DEFAULT_POLL_SECONDS = 0.5
142
135
 
143
136
class LockDir(object):
144
137
    """Write-lock guarding access to data."""
160
153
        self.transport = transport
161
154
        self.path = path
162
155
        self._lock_held = False
163
 
        self._locked_via_token = False
164
156
        self._fake_read_lock = False
165
157
        self._held_dir = path + '/held'
166
158
        self._held_info_path = self._held_dir + self.__INFO_NAME
167
159
        self._file_modebits = file_modebits
168
160
        self._dir_modebits = dir_modebits
169
 
 
170
 
        self._report_function = note
 
161
        self.nonce = rand_chars(20)
171
162
 
172
163
    def __repr__(self):
173
164
        return '%s(%s%s)' % (self.__class__.__name__,
209
200
                # After creating the lock directory, try again
210
201
                self.transport.mkdir(tmpname)
211
202
 
212
 
            self.nonce = rand_chars(20)
213
203
            info_bytes = self._prepare_info()
214
204
            # We use put_file_non_atomic because we just created a new unique
215
205
            # directory so we don't have to worry about files existing there.
221
211
            self.transport.rename(tmpname, self._held_dir)
222
212
            self._lock_held = True
223
213
            self.confirm()
224
 
        except errors.PermissionDenied:
225
 
            raise
226
214
        except (PathError, DirectoryNotEmpty, FileExists, ResourceBusy), e:
227
215
            mutter("contention on %r: %s", self, e)
228
216
            raise LockContention(self)
235
223
            return
236
224
        if not self._lock_held:
237
225
            raise LockNotHeld(self)
238
 
        if self._locked_via_token:
239
 
            self._locked_via_token = False
240
 
            self._lock_held = False
241
 
        else:
242
 
            # rename before deleting, because we can't atomically remove the
243
 
            # whole tree
244
 
            tmpname = '%s/releasing.%s.tmp' % (self.path, rand_chars(20))
245
 
            # gotta own it to unlock
246
 
            self.confirm()
247
 
            self.transport.rename(self._held_dir, tmpname)
248
 
            self._lock_held = False
249
 
            self.transport.delete(tmpname + self.__INFO_NAME)
250
 
            self.transport.rmdir(tmpname)
 
226
        # rename before deleting, because we can't atomically remove the whole
 
227
        # tree
 
228
        tmpname = '%s/releasing.%s.tmp' % (self.path, rand_chars(20))
 
229
        # gotta own it to unlock
 
230
        self.confirm()
 
231
        self.transport.rename(self._held_dir, tmpname)
 
232
        self._lock_held = False
 
233
        self.transport.delete(tmpname + self.__INFO_NAME)
 
234
        self.transport.rmdir(tmpname)
251
235
 
252
236
    def break_lock(self):
253
237
        """Break a lock not held by this instance of LockDir.
259
243
        self._check_not_locked()
260
244
        holder_info = self.peek()
261
245
        if holder_info is not None:
262
 
            lock_info = '\n'.join(self._format_lock_info(holder_info))
263
 
            if bzrlib.ui.ui_factory.get_boolean("Break %s" % lock_info):
 
246
            if bzrlib.ui.ui_factory.get_boolean(
 
247
                "Break lock %s held by %s@%s [process #%s]" % (
 
248
                    self.transport,
 
249
                    holder_info["user"],
 
250
                    holder_info["hostname"],
 
251
                    holder_info["pid"])):
264
252
                self.force_break(holder_info)
265
253
        
266
254
    def force_break(self, dead_holder_info):
353
341
        import socket
354
342
        # XXX: is creating this here inefficient?
355
343
        config = bzrlib.config.GlobalConfig()
356
 
        try:
357
 
            user = config.user_email()
358
 
        except errors.NoEmailInUsername:
359
 
            user = config.username()
360
344
        s = Stanza(hostname=socket.gethostname(),
361
345
                   pid=str(os.getpid()),
362
346
                   start_time=str(int(time.time())),
363
347
                   nonce=self.nonce,
364
 
                   user=user,
 
348
                   user=config.user_email(),
365
349
                   )
366
350
        return s.to_string()
367
351
 
368
352
    def _parse_info(self, info_file):
369
353
        return read_stanza(info_file.readlines()).as_dict()
370
354
 
371
 
    def wait_lock(self, timeout=None, poll=None):
 
355
    def wait_lock(self, timeout=_DEFAULT_TIMEOUT_SECONDS,
 
356
                  poll=_DEFAULT_POLL_SECONDS):
372
357
        """Wait a certain period for a lock.
373
358
 
374
359
        If the lock can be acquired within the bounded time, it
377
362
        approximately `timeout` seconds.  (It may be a bit more if
378
363
        a transport operation takes a long time to complete.)
379
364
        """
380
 
        if timeout is None:
381
 
            timeout = _DEFAULT_TIMEOUT_SECONDS
382
 
        if poll is None:
383
 
            poll = _DEFAULT_POLL_SECONDS
384
 
 
385
365
        # XXX: the transport interface doesn't let us guard 
386
366
        # against operations there taking a long time.
387
367
        deadline = time.time() + timeout
388
 
        deadline_str = None
389
 
        last_info = None
390
368
        while True:
391
369
            try:
392
370
                self.attempt_lock()
393
371
                return
394
372
            except LockContention:
395
373
                pass
396
 
            new_info = self.peek()
397
 
            mutter('last_info: %s, new info: %s', last_info, new_info)
398
 
            if new_info is not None and new_info != last_info:
399
 
                if last_info is None:
400
 
                    start = 'Unable to obtain'
401
 
                else:
402
 
                    start = 'Lock owner changed for'
403
 
                last_info = new_info
404
 
                formatted_info = self._format_lock_info(new_info)
405
 
                if deadline_str is None:
406
 
                    deadline_str = time.strftime('%H:%M:%S',
407
 
                                                 time.localtime(deadline))
408
 
                self._report_function('%s %s\n'
409
 
                                      '%s\n' # held by
410
 
                                      '%s\n' # locked ... ago
411
 
                                      'Will continue to try until %s\n',
412
 
                                      start,
413
 
                                      formatted_info[0],
414
 
                                      formatted_info[1],
415
 
                                      formatted_info[2],
416
 
                                      deadline_str)
417
 
 
418
374
            if time.time() + poll < deadline:
419
375
                time.sleep(poll)
420
376
            else:
421
377
                raise LockContention(self)
422
 
    
423
 
    def leave_in_place(self):
424
 
        self._locked_via_token = True
425
 
 
426
 
    def dont_leave_in_place(self):
427
 
        self._locked_via_token = False
428
 
 
429
 
    def lock_write(self, token=None):
430
 
        """Wait for and acquire the lock.
431
 
        
432
 
        :param token: if this is already locked, then lock_write will fail
433
 
            unless the token matches the existing lock.
434
 
        :returns: a token if this instance supports tokens, otherwise None.
435
 
        :raises TokenLockingNotSupported: when a token is given but this
436
 
            instance doesn't support using token locks.
437
 
        :raises MismatchedToken: if the specified token doesn't match the token
438
 
            of the existing lock.
439
 
 
440
 
        A token should be passed in if you know that you have locked the object
441
 
        some other way, and need to synchronise this object's state with that
442
 
        fact.
443
 
         
444
 
        XXX: docstring duplicated from LockableFiles.lock_write.
445
 
        """
446
 
        if token is not None:
447
 
            self.validate_token(token)
448
 
            self.nonce = token
449
 
            self._lock_held = True
450
 
            self._locked_via_token = True
451
 
            return token
452
 
        else:
453
 
            self.wait_lock()
454
 
            return self.peek().get('nonce')
 
378
 
 
379
    def lock_write(self):
 
380
        """Wait for and acquire the lock."""
 
381
        self.attempt_lock()
455
382
 
456
383
    def lock_read(self):
457
384
        """Compatibility-mode shared lock.
481
408
            else:
482
409
                raise LockContention(self)
483
410
 
484
 
    def _format_lock_info(self, info):
485
 
        """Turn the contents of peek() into something for the user"""
486
 
        lock_url = self.transport.abspath(self.path)
487
 
        delta = time.time() - int(info['start_time'])
488
 
        return [
489
 
            'lock %s' % (lock_url,),
490
 
            'held by %(user)s on host %(hostname)s [process #%(pid)s]' % info,
491
 
            'locked %s' % (format_delta(delta),),
492
 
            ]
493
 
 
494
 
    def validate_token(self, token):
495
 
        if token is not None:
496
 
            info = self.peek()
497
 
            if info is None:
498
 
                # Lock isn't held
499
 
                lock_token = None
500
 
            else:
501
 
                lock_token = info.get('nonce')
502
 
            if token != lock_token:
503
 
                raise errors.TokenMismatch(token, lock_token)
504