1
1
# Copyright (C) 2006 Canonical Ltd
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.
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.
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
106
105
LockBreakMismatch,
113
112
UnlockableTransport,
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
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.
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.
128
125
# TODO: Perhaps store some kind of note like the bzr command line in the lock
134
131
# TODO: Make sure to pass the right file and directory mode bits to all
135
132
# files/dirs created.
137
135
_DEFAULT_TIMEOUT_SECONDS = 300
138
_DEFAULT_POLL_SECONDS = 0.5
136
_DEFAULT_POLL_SECONDS = 1.0
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
self._report_function = note
167
168
def __repr__(self):
168
169
return '%s(%s%s)' % (self.__class__.__name__,
169
170
self.transport.base,
172
173
is_held = property(lambda self: self._lock_held)
175
def create(self, mode=None):
175
176
"""Create the on-disk lock.
177
178
This is typically only called when the object/directory containing the
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)
184
185
def attempt_lock(self):
185
186
"""Take the lock; fail if it's already held.
193
194
raise UnlockableTransport(self.transport)
195
196
tmpname = '%s/pending.%s.tmp' % (self.path, rand_chars(20))
196
self.transport.mkdir(tmpname)
198
self._prepare_info(sio)
200
self.transport.put(tmpname + self.__INFO_NAME, sio)
198
self.transport.mkdir(tmpname)
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)
205
# After creating the lock directory, try again
206
self.transport.mkdir(tmpname)
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
213
self.transport.put_bytes_non_atomic(tmpname + self.__INFO_NAME,
201
216
self.transport.rename(tmpname, self._held_dir)
202
217
self._lock_held = True
205
except (DirectoryNotEmpty, FileExists, ResourceBusy), e:
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)
210
223
def unlock(self):
211
224
"""Release a held lock
218
231
# rename before deleting, because we can't atomically remove the whole
220
233
tmpname = '%s/releasing.%s.tmp' % (self.path, rand_chars(20))
234
# gotta own it to unlock
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)
241
def break_lock(self):
242
"""Break a lock not held by this instance of LockDir.
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.
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)
226
255
def force_break(self, dead_holder_info):
227
256
"""Release a lock held by another process.
242
271
if not isinstance(dead_holder_info, dict):
243
272
raise ValueError("dead_holder_info: %r" % dead_holder_info)
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)
292
def _check_not_locked(self):
293
"""If the lock is held by this instance, raise an error."""
295
raise AssertionError("can't break own lock: %r" % self)
264
297
def confirm(self):
265
298
"""Make sure that the lock is still held by this locker.
315
348
nonce=self.nonce,
316
349
user=config.user_email(),
318
RioWriter(outf).write_stanza(s)
320
353
def _parse_info(self, info_file):
321
354
return read_stanza(info_file.readlines()).as_dict()
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.
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.)
366
timeout = _DEFAULT_TIMEOUT_SECONDS
368
poll = _DEFAULT_POLL_SECONDS
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
338
377
self.attempt_lock()
340
379
except LockContention:
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'
387
start = 'Lock owner changed for'
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'
395
'%s\n' # locked ... ago
396
'Will continue to try until %s\n',
342
403
if time.time() + poll < deadline:
347
408
def lock_write(self):
348
409
"""Wait for and acquire the lock."""
351
412
def lock_read(self):
352
"""Compatability-mode shared lock.
413
"""Compatibility-mode shared lock.
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.
377
438
raise LockContention(self)
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'])
445
'lock %s' % (lock_url,),
446
'held by %(user)s on host %(hostname)s [process #%(pid)s]' % info,
447
'locked %s' % (format_delta(delta),),