~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lockdir.py

  • Committer: Robert Collins
  • Date: 2007-03-08 04:06:06 UTC
  • mfrom: (2323.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 2442.
  • Revision ID: robertc@robertcollins.net-20070308040606-84gsniv56huiyjt4
Merge bzr.dev.

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