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
108
LockBreakMismatch,
114
115
UnlockableTransport,
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
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.
135
139
_DEFAULT_TIMEOUT_SECONDS = 300
136
_DEFAULT_POLL_SECONDS = 0.5
140
_DEFAULT_POLL_SECONDS = 1.0
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)
170
self._report_function = note
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)
193
200
tmpname = '%s/pending.%s.tmp' % (self.path, rand_chars(20))
194
self.transport.mkdir(tmpname)
196
self._prepare_info(sio)
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
202
self.transport.append(tmpname + self.__INFO_NAME, sio)
202
self.transport.mkdir(tmpname)
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)
209
# After creating the lock directory, try again
210
self.transport.mkdir(tmpname)
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
217
self.transport.put_bytes_non_atomic(tmpname + self.__INFO_NAME,
203
220
self.transport.rename(tmpname, self._held_dir)
204
221
self._lock_held = True
223
except errors.PermissionDenied:
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]" % (
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)
246
261
def force_break(self, dead_holder_info):
327
342
except NoSuchFile, e:
330
def _prepare_info(self, outf):
345
def _prepare_info(self):
331
346
"""Write information about a pending lock to a temporary file.
334
349
# XXX: is creating this here inefficient?
335
350
config = bzrlib.config.GlobalConfig()
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(),
342
RioWriter(outf).write_stanza(s)
344
363
def _parse_info(self, info_file):
345
364
return read_stanza(info_file.readlines()).as_dict()
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.
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.)
376
timeout = _DEFAULT_TIMEOUT_SECONDS
378
poll = _DEFAULT_POLL_SECONDS
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
362
387
self.attempt_lock()
364
389
except LockContention:
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'
397
start = 'Lock owner changed for'
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'
405
'%s\n' # locked ... ago
406
'Will continue to try until %s\n',
366
413
if time.time() + poll < deadline:
371
418
def lock_write(self):
372
419
"""Wait for and acquire the lock."""
375
422
def lock_read(self):
376
"""Compatability-mode shared lock.
423
"""Compatibility-mode shared lock.
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.
401
448
raise LockContention(self)
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'])
455
'lock %s' % (lock_url,),
456
'held by %(user)s on host %(hostname)s [process #%(pid)s]' % info,
457
'locked %s' % (format_delta(delta),),