~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lockdir.py

Optimize Tree._iter_changes with specific file_ids

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# Copyright (C) 2006 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
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
 
 
7
#
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
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
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
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
import bzrlib.ui
 
122
 
118
123
 
119
124
# XXX: At the moment there is no consideration of thread safety on LockDir
120
125
# objects.  This should perhaps be updated - e.g. if two threads try to take a
130
135
# TODO: Make sure to pass the right file and directory mode bits to all
131
136
# files/dirs created.
132
137
 
 
138
 
133
139
_DEFAULT_TIMEOUT_SECONDS = 300
134
 
_DEFAULT_POLL_SECONDS = 0.5
 
140
_DEFAULT_POLL_SECONDS = 1.0
 
141
 
135
142
 
136
143
class LockDir(object):
137
144
    """Write-lock guarding access to data."""
160
167
        self._dir_modebits = dir_modebits
161
168
        self.nonce = rand_chars(20)
162
169
 
 
170
        self._report_function = note
 
171
 
163
172
    def __repr__(self):
164
173
        return '%s(%s%s)' % (self.__class__.__name__,
165
174
                             self.transport.base,
189
198
            raise UnlockableTransport(self.transport)
190
199
        try:
191
200
            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)
 
201
            try:
 
202
                self.transport.mkdir(tmpname)
 
203
            except NoSuchFile:
 
204
                # This may raise a FileExists exception
 
205
                # which is okay, it will be caught later and determined
 
206
                # to be a LockContention.
 
207
                self.create(mode=self._dir_modebits)
 
208
                
 
209
                # After creating the lock directory, try again
 
210
                self.transport.mkdir(tmpname)
 
211
 
 
212
            info_bytes = self._prepare_info()
 
213
            # We use put_file_non_atomic because we just created a new unique
 
214
            # directory so we don't have to worry about files existing there.
 
215
            # We'll rename the whole directory into place to get atomic
 
216
            # properties
 
217
            self.transport.put_bytes_non_atomic(tmpname + self.__INFO_NAME,
 
218
                                                info_bytes)
 
219
 
201
220
            self.transport.rename(tmpname, self._held_dir)
202
221
            self._lock_held = True
203
222
            self.confirm()
233
252
        self._check_not_locked()
234
253
        holder_info = self.peek()
235
254
        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"])):
 
255
            lock_info = '\n'.join(self._format_lock_info(holder_info))
 
256
            if bzrlib.ui.ui_factory.get_boolean("Break %s" % lock_info):
242
257
                self.force_break(holder_info)
243
258
        
244
259
    def force_break(self, dead_holder_info):
325
340
        except NoSuchFile, e:
326
341
            return None
327
342
 
328
 
    def _prepare_info(self, outf):
 
343
    def _prepare_info(self):
329
344
        """Write information about a pending lock to a temporary file.
330
345
        """
331
346
        import socket
332
347
        # XXX: is creating this here inefficient?
333
348
        config = bzrlib.config.GlobalConfig()
 
349
        try:
 
350
            user = config.user_email()
 
351
        except errors.NoEmailInUsername:
 
352
            user = config.username()
334
353
        s = Stanza(hostname=socket.gethostname(),
335
354
                   pid=str(os.getpid()),
336
355
                   start_time=str(int(time.time())),
337
356
                   nonce=self.nonce,
338
 
                   user=config.user_email(),
 
357
                   user=user,
339
358
                   )
340
 
        RioWriter(outf).write_stanza(s)
 
359
        return s.to_string()
341
360
 
342
361
    def _parse_info(self, info_file):
343
362
        return read_stanza(info_file.readlines()).as_dict()
344
363
 
345
 
    def wait_lock(self, timeout=_DEFAULT_TIMEOUT_SECONDS,
346
 
                  poll=_DEFAULT_POLL_SECONDS):
 
364
    def wait_lock(self, timeout=None, poll=None):
347
365
        """Wait a certain period for a lock.
348
366
 
349
367
        If the lock can be acquired within the bounded time, it
352
370
        approximately `timeout` seconds.  (It may be a bit more if
353
371
        a transport operation takes a long time to complete.)
354
372
        """
 
373
        if timeout is None:
 
374
            timeout = _DEFAULT_TIMEOUT_SECONDS
 
375
        if poll is None:
 
376
            poll = _DEFAULT_POLL_SECONDS
 
377
 
355
378
        # XXX: the transport interface doesn't let us guard 
356
379
        # against operations there taking a long time.
357
380
        deadline = time.time() + timeout
 
381
        deadline_str = None
 
382
        last_info = None
358
383
        while True:
359
384
            try:
360
385
                self.attempt_lock()
361
386
                return
362
387
            except LockContention:
363
388
                pass
 
389
            new_info = self.peek()
 
390
            mutter('last_info: %s, new info: %s', last_info, new_info)
 
391
            if new_info is not None and new_info != last_info:
 
392
                if last_info is None:
 
393
                    start = 'Unable to obtain'
 
394
                else:
 
395
                    start = 'Lock owner changed for'
 
396
                last_info = new_info
 
397
                formatted_info = self._format_lock_info(new_info)
 
398
                if deadline_str is None:
 
399
                    deadline_str = time.strftime('%H:%M:%S',
 
400
                                                 time.localtime(deadline))
 
401
                self._report_function('%s %s\n'
 
402
                                      '%s\n' # held by
 
403
                                      '%s\n' # locked ... ago
 
404
                                      'Will continue to try until %s\n',
 
405
                                      start,
 
406
                                      formatted_info[0],
 
407
                                      formatted_info[1],
 
408
                                      formatted_info[2],
 
409
                                      deadline_str)
 
410
 
364
411
            if time.time() + poll < deadline:
365
412
                time.sleep(poll)
366
413
            else:
368
415
 
369
416
    def lock_write(self):
370
417
        """Wait for and acquire the lock."""
371
 
        self.attempt_lock()
 
418
        self.wait_lock()
372
419
 
373
420
    def lock_read(self):
374
421
        """Compatibility-mode shared lock.
398
445
            else:
399
446
                raise LockContention(self)
400
447
 
 
448
    def _format_lock_info(self, info):
 
449
        """Turn the contents of peek() into something for the user"""
 
450
        lock_url = self.transport.abspath(self.path)
 
451
        delta = time.time() - int(info['start_time'])
 
452
        return [
 
453
            'lock %s' % (lock_url,),
 
454
            'held by %(user)s on host %(hostname)s [process #%(pid)s]' % info,
 
455
            'locked %s' % (format_delta(delta),),
 
456
            ]
 
457