/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: Martin Pool
  • Date: 2006-10-06 02:04:17 UTC
  • mfrom: (1908.10.1 bench_usecases.merge2)
  • mto: This revision was merged to the branch mainline in revision 2068.
  • Revision ID: mbp@sourcefrog.net-20061006020417-4949ca86f4417a4d
merge additional fix from cfbolz

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
 
102
101
import bzrlib.config
103
102
from bzrlib.errors import (
106
105
        LockBreakMismatch,
107
106
        LockBroken,
108
107
        LockContention,
109
 
        LockError,
110
108
        LockNotHeld,
111
109
        NoSuchFile,
 
110
        PathError,
112
111
        ResourceBusy,
113
112
        UnlockableTransport,
114
113
        )
 
114
from bzrlib.trace import mutter, note
115
115
from bzrlib.transport import Transport
116
 
from bzrlib.osutils import rand_chars
117
 
from bzrlib.rio import RioWriter, read_stanza, Stanza
 
116
from bzrlib.osutils import rand_chars, format_delta
 
117
from bzrlib.rio import read_stanza, Stanza
 
118
 
118
119
 
119
120
# XXX: At the moment there is no consideration of thread safety on LockDir
120
121
# objects.  This should perhaps be updated - e.g. if two threads try to take a
121
122
# lock at the same time they should *both* get it.  But then that's unlikely
122
123
# to be a good idea.
123
124
 
124
 
# TODO: Transport could offer a simpler put() method that avoids the
125
 
# rename-into-place for cases like creating the lock template, where there is
126
 
# no chance that the file already exists.
127
 
 
128
125
# TODO: Perhaps store some kind of note like the bzr command line in the lock
129
126
# info?
130
127
 
134
131
# TODO: Make sure to pass the right file and directory mode bits to all
135
132
# files/dirs created.
136
133
 
 
134
 
137
135
_DEFAULT_TIMEOUT_SECONDS = 300
138
 
_DEFAULT_POLL_SECONDS = 0.5
 
136
_DEFAULT_POLL_SECONDS = 1.0
 
137
 
139
138
 
140
139
class LockDir(object):
141
140
    """Write-lock guarding access to data."""
164
163
        self._dir_modebits = dir_modebits
165
164
        self.nonce = rand_chars(20)
166
165
 
 
166
        self._report_function = note
 
167
 
167
168
    def __repr__(self):
168
169
        return '%s(%s%s)' % (self.__class__.__name__,
169
170
                             self.transport.base,
171
172
 
172
173
    is_held = property(lambda self: self._lock_held)
173
174
 
174
 
    def create(self):
 
175
    def create(self, mode=None):
175
176
        """Create the on-disk lock.
176
177
 
177
178
        This is typically only called when the object/directory containing the 
179
180
        """
180
181
        if self.transport.is_readonly():
181
182
            raise UnlockableTransport(self.transport)
182
 
        self.transport.mkdir(self.path)
 
183
        self.transport.mkdir(self.path, mode=mode)
183
184
 
184
185
    def attempt_lock(self):
185
186
        """Take the lock; fail if it's already held.
193
194
            raise UnlockableTransport(self.transport)
194
195
        try:
195
196
            tmpname = '%s/pending.%s.tmp' % (self.path, rand_chars(20))
196
 
            self.transport.mkdir(tmpname)
197
 
            sio = StringIO()
198
 
            self._prepare_info(sio)
199
 
            sio.seek(0)
200
 
            self.transport.put(tmpname + self.__INFO_NAME, sio)
 
197
            try:
 
198
                self.transport.mkdir(tmpname)
 
199
            except NoSuchFile:
 
200
                # This may raise a FileExists exception
 
201
                # which is okay, it will be caught later and determined
 
202
                # to be a LockContention.
 
203
                self.create(mode=self._dir_modebits)
 
204
                
 
205
                # After creating the lock directory, try again
 
206
                self.transport.mkdir(tmpname)
 
207
 
 
208
            info_bytes = self._prepare_info()
 
209
            # We use put_file_non_atomic because we just created a new unique
 
210
            # directory so we don't have to worry about files existing there.
 
211
            # We'll rename the whole directory into place to get atomic
 
212
            # properties
 
213
            self.transport.put_bytes_non_atomic(tmpname + self.__INFO_NAME,
 
214
                                                info_bytes)
 
215
 
201
216
            self.transport.rename(tmpname, self._held_dir)
202
217
            self._lock_held = True
203
218
            self.confirm()
204
 
            return
205
 
        except (DirectoryNotEmpty, FileExists, ResourceBusy), e:
206
 
            pass
207
 
        # fall through to here on contention
208
 
        raise LockContention(self)
 
219
        except (PathError, DirectoryNotEmpty, FileExists, ResourceBusy), e:
 
220
            mutter("contention on %r: %s", self, e)
 
221
            raise LockContention(self)
209
222
 
210
223
    def unlock(self):
211
224
        """Release a held lock
218
231
        # rename before deleting, because we can't atomically remove the whole
219
232
        # tree
220
233
        tmpname = '%s/releasing.%s.tmp' % (self.path, rand_chars(20))
 
234
        # gotta own it to unlock
 
235
        self.confirm()
221
236
        self.transport.rename(self._held_dir, tmpname)
222
237
        self._lock_held = False
223
238
        self.transport.delete(tmpname + self.__INFO_NAME)
224
239
        self.transport.rmdir(tmpname)
225
240
 
 
241
    def break_lock(self):
 
242
        """Break a lock not held by this instance of LockDir.
 
243
 
 
244
        This is a UI centric function: it uses the bzrlib.ui.ui_factory to
 
245
        prompt for input if a lock is detected and there is any doubt about
 
246
        it possibly being still active.
 
247
        """
 
248
        self._check_not_locked()
 
249
        holder_info = self.peek()
 
250
        if holder_info is not None:
 
251
            lock_info = '\n'.join(self._format_lock_info(holder_info))
 
252
            if bzrlib.ui.ui_factory.get_boolean("Break %s" % lock_info):
 
253
                self.force_break(holder_info)
 
254
        
226
255
    def force_break(self, dead_holder_info):
227
256
        """Release a lock held by another process.
228
257
 
241
270
        """
242
271
        if not isinstance(dead_holder_info, dict):
243
272
            raise ValueError("dead_holder_info: %r" % dead_holder_info)
244
 
        if self._lock_held:
245
 
            raise AssertionError("can't break own lock: %r" % self)
 
273
        self._check_not_locked()
246
274
        current_info = self.peek()
247
275
        if current_info is None:
248
276
            # must have been recently released
261
289
        self.transport.delete(broken_info_path)
262
290
        self.transport.rmdir(tmpname)
263
291
 
 
292
    def _check_not_locked(self):
 
293
        """If the lock is held by this instance, raise an error."""
 
294
        if self._lock_held:
 
295
            raise AssertionError("can't break own lock: %r" % self)
 
296
 
264
297
    def confirm(self):
265
298
        """Make sure that the lock is still held by this locker.
266
299
 
303
336
        except NoSuchFile, e:
304
337
            return None
305
338
 
306
 
    def _prepare_info(self, outf):
 
339
    def _prepare_info(self):
307
340
        """Write information about a pending lock to a temporary file.
308
341
        """
309
342
        import socket
315
348
                   nonce=self.nonce,
316
349
                   user=config.user_email(),
317
350
                   )
318
 
        RioWriter(outf).write_stanza(s)
 
351
        return s.to_string()
319
352
 
320
353
    def _parse_info(self, info_file):
321
354
        return read_stanza(info_file.readlines()).as_dict()
322
355
 
323
 
    def wait_lock(self, timeout=_DEFAULT_TIMEOUT_SECONDS,
324
 
                  poll=_DEFAULT_POLL_SECONDS):
 
356
    def wait_lock(self, timeout=None, poll=None):
325
357
        """Wait a certain period for a lock.
326
358
 
327
359
        If the lock can be acquired within the bounded time, it
330
362
        approximately `timeout` seconds.  (It may be a bit more if
331
363
        a transport operation takes a long time to complete.)
332
364
        """
 
365
        if timeout is None:
 
366
            timeout = _DEFAULT_TIMEOUT_SECONDS
 
367
        if poll is None:
 
368
            poll = _DEFAULT_POLL_SECONDS
 
369
 
333
370
        # XXX: the transport interface doesn't let us guard 
334
371
        # against operations there taking a long time.
335
372
        deadline = time.time() + timeout
 
373
        deadline_str = None
 
374
        last_info = None
336
375
        while True:
337
376
            try:
338
377
                self.attempt_lock()
339
378
                return
340
379
            except LockContention:
341
380
                pass
 
381
            new_info = self.peek()
 
382
            mutter('last_info: %s, new info: %s', last_info, new_info)
 
383
            if new_info is not None and new_info != last_info:
 
384
                if last_info is None:
 
385
                    start = 'Unable to obtain'
 
386
                else:
 
387
                    start = 'Lock owner changed for'
 
388
                last_info = new_info
 
389
                formatted_info = self._format_lock_info(new_info)
 
390
                if deadline_str is None:
 
391
                    deadline_str = time.strftime('%H:%M:%S',
 
392
                                                 time.localtime(deadline))
 
393
                self._report_function('%s %s\n'
 
394
                                      '%s\n' # held by
 
395
                                      '%s\n' # locked ... ago
 
396
                                      'Will continue to try until %s\n',
 
397
                                      start,
 
398
                                      formatted_info[0],
 
399
                                      formatted_info[1],
 
400
                                      formatted_info[2],
 
401
                                      deadline_str)
 
402
 
342
403
            if time.time() + poll < deadline:
343
404
                time.sleep(poll)
344
405
            else:
346
407
 
347
408
    def lock_write(self):
348
409
        """Wait for and acquire the lock."""
349
 
        self.attempt_lock()
 
410
        self.wait_lock()
350
411
 
351
412
    def lock_read(self):
352
 
        """Compatability-mode shared lock.
 
413
        """Compatibility-mode shared lock.
353
414
 
354
415
        LockDir doesn't support shared read-only locks, so this 
355
416
        just pretends that the lock is taken but really does nothing.
376
437
            else:
377
438
                raise LockContention(self)
378
439
 
 
440
    def _format_lock_info(self, info):
 
441
        """Turn the contents of peek() into something for the user"""
 
442
        lock_url = self.transport.abspath(self.path)
 
443
        delta = time.time() - int(info['start_time'])
 
444
        return [
 
445
            'lock %s' % (lock_url,),
 
446
            'held by %(user)s on host %(hostname)s [process #%(pid)s]' % info,
 
447
            'locked %s' % (format_delta(delta),),
 
448
            ]
 
449