~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lockdir.py

  • Committer: wang
  • Date: 2006-10-29 13:41:32 UTC
  • mto: (2104.4.1 wang_65714)
  • mto: This revision was merged to the branch mainline in revision 2109.
  • Revision ID: wang@ubuntu-20061029134132-3d7f4216f20c4aef
Replace python's difflib by patiencediff because the worst case 
performance is cubic for difflib and people commiting large data 
files are often hurt by this. The worst case performance of patience is 
quadratic. Fix bug 65714.

Show diffs side-by-side

added added

removed removed

Lines of Context:
96
96
 
97
97
import os
98
98
import time
99
 
from StringIO import StringIO
 
99
from cStringIO import StringIO
100
100
 
 
101
from bzrlib import (
 
102
    errors,
 
103
    )
101
104
import bzrlib.config
102
105
from bzrlib.errors import (
103
106
        DirectoryNotEmpty,
111
114
        ResourceBusy,
112
115
        UnlockableTransport,
113
116
        )
114
 
from bzrlib.trace import mutter
 
117
from bzrlib.trace import mutter, note
115
118
from bzrlib.transport import Transport
116
 
from bzrlib.osutils import rand_chars
117
 
from bzrlib.rio import RioWriter, read_stanza, Stanza
 
119
from bzrlib.osutils import rand_chars, format_delta
 
120
from bzrlib.rio import read_stanza, Stanza
 
121
 
118
122
 
119
123
# XXX: At the moment there is no consideration of thread safety on LockDir
120
124
# objects.  This should perhaps be updated - e.g. if two threads try to take a
130
134
# TODO: Make sure to pass the right file and directory mode bits to all
131
135
# files/dirs created.
132
136
 
 
137
 
133
138
_DEFAULT_TIMEOUT_SECONDS = 300
134
 
_DEFAULT_POLL_SECONDS = 0.5
 
139
_DEFAULT_POLL_SECONDS = 1.0
 
140
 
135
141
 
136
142
class LockDir(object):
137
143
    """Write-lock guarding access to data."""
160
166
        self._dir_modebits = dir_modebits
161
167
        self.nonce = rand_chars(20)
162
168
 
 
169
        self._report_function = note
 
170
 
163
171
    def __repr__(self):
164
172
        return '%s(%s%s)' % (self.__class__.__name__,
165
173
                             self.transport.base,
189
197
            raise UnlockableTransport(self.transport)
190
198
        try:
191
199
            tmpname = '%s/pending.%s.tmp' % (self.path, rand_chars(20))
192
 
            self.transport.mkdir(tmpname)
193
 
            sio = StringIO()
194
 
            self._prepare_info(sio)
195
 
            sio.seek(0)
196
 
            # append will create a new file; we use append rather than put
197
 
            # because we don't want to write to a temporary file and rename
198
 
            # into place, because that's going to happen to the whole
199
 
            # directory
200
 
            self.transport.append(tmpname + self.__INFO_NAME, sio)
 
200
            try:
 
201
                self.transport.mkdir(tmpname)
 
202
            except NoSuchFile:
 
203
                # This may raise a FileExists exception
 
204
                # which is okay, it will be caught later and determined
 
205
                # to be a LockContention.
 
206
                self.create(mode=self._dir_modebits)
 
207
                
 
208
                # After creating the lock directory, try again
 
209
                self.transport.mkdir(tmpname)
 
210
 
 
211
            info_bytes = self._prepare_info()
 
212
            # We use put_file_non_atomic because we just created a new unique
 
213
            # directory so we don't have to worry about files existing there.
 
214
            # We'll rename the whole directory into place to get atomic
 
215
            # properties
 
216
            self.transport.put_bytes_non_atomic(tmpname + self.__INFO_NAME,
 
217
                                                info_bytes)
 
218
 
201
219
            self.transport.rename(tmpname, self._held_dir)
202
220
            self._lock_held = True
203
221
            self.confirm()
233
251
        self._check_not_locked()
234
252
        holder_info = self.peek()
235
253
        if holder_info is not None:
236
 
            if bzrlib.ui.ui_factory.get_boolean(
237
 
                "Break lock %s held by %s@%s [process #%s]" % (
238
 
                    self.transport,
239
 
                    holder_info["user"],
240
 
                    holder_info["hostname"],
241
 
                    holder_info["pid"])):
 
254
            lock_info = '\n'.join(self._format_lock_info(holder_info))
 
255
            if bzrlib.ui.ui_factory.get_boolean("Break %s" % lock_info):
242
256
                self.force_break(holder_info)
243
257
        
244
258
    def force_break(self, dead_holder_info):
325
339
        except NoSuchFile, e:
326
340
            return None
327
341
 
328
 
    def _prepare_info(self, outf):
 
342
    def _prepare_info(self):
329
343
        """Write information about a pending lock to a temporary file.
330
344
        """
331
345
        import socket
332
346
        # XXX: is creating this here inefficient?
333
347
        config = bzrlib.config.GlobalConfig()
 
348
        try:
 
349
            user = config.user_email()
 
350
        except errors.NoEmailInUsername:
 
351
            user = config.username()
334
352
        s = Stanza(hostname=socket.gethostname(),
335
353
                   pid=str(os.getpid()),
336
354
                   start_time=str(int(time.time())),
337
355
                   nonce=self.nonce,
338
 
                   user=config.user_email(),
 
356
                   user=user,
339
357
                   )
340
 
        RioWriter(outf).write_stanza(s)
 
358
        return s.to_string()
341
359
 
342
360
    def _parse_info(self, info_file):
343
361
        return read_stanza(info_file.readlines()).as_dict()
344
362
 
345
 
    def wait_lock(self, timeout=_DEFAULT_TIMEOUT_SECONDS,
346
 
                  poll=_DEFAULT_POLL_SECONDS):
 
363
    def wait_lock(self, timeout=None, poll=None):
347
364
        """Wait a certain period for a lock.
348
365
 
349
366
        If the lock can be acquired within the bounded time, it
352
369
        approximately `timeout` seconds.  (It may be a bit more if
353
370
        a transport operation takes a long time to complete.)
354
371
        """
 
372
        if timeout is None:
 
373
            timeout = _DEFAULT_TIMEOUT_SECONDS
 
374
        if poll is None:
 
375
            poll = _DEFAULT_POLL_SECONDS
 
376
 
355
377
        # XXX: the transport interface doesn't let us guard 
356
378
        # against operations there taking a long time.
357
379
        deadline = time.time() + timeout
 
380
        deadline_str = None
 
381
        last_info = None
358
382
        while True:
359
383
            try:
360
384
                self.attempt_lock()
361
385
                return
362
386
            except LockContention:
363
387
                pass
 
388
            new_info = self.peek()
 
389
            mutter('last_info: %s, new info: %s', last_info, new_info)
 
390
            if new_info is not None and new_info != last_info:
 
391
                if last_info is None:
 
392
                    start = 'Unable to obtain'
 
393
                else:
 
394
                    start = 'Lock owner changed for'
 
395
                last_info = new_info
 
396
                formatted_info = self._format_lock_info(new_info)
 
397
                if deadline_str is None:
 
398
                    deadline_str = time.strftime('%H:%M:%S',
 
399
                                                 time.localtime(deadline))
 
400
                self._report_function('%s %s\n'
 
401
                                      '%s\n' # held by
 
402
                                      '%s\n' # locked ... ago
 
403
                                      'Will continue to try until %s\n',
 
404
                                      start,
 
405
                                      formatted_info[0],
 
406
                                      formatted_info[1],
 
407
                                      formatted_info[2],
 
408
                                      deadline_str)
 
409
 
364
410
            if time.time() + poll < deadline:
365
411
                time.sleep(poll)
366
412
            else:
368
414
 
369
415
    def lock_write(self):
370
416
        """Wait for and acquire the lock."""
371
 
        self.attempt_lock()
 
417
        self.wait_lock()
372
418
 
373
419
    def lock_read(self):
374
420
        """Compatibility-mode shared lock.
398
444
            else:
399
445
                raise LockContention(self)
400
446
 
 
447
    def _format_lock_info(self, info):
 
448
        """Turn the contents of peek() into something for the user"""
 
449
        lock_url = self.transport.abspath(self.path)
 
450
        delta = time.time() - int(info['start_time'])
 
451
        return [
 
452
            'lock %s' % (lock_url,),
 
453
            'held by %(user)s on host %(hostname)s [process #%(pid)s]' % info,
 
454
            'locked %s' % (format_delta(delta),),
 
455
            ]
 
456