87
87
>>> # typically will be obtained from a BzrDir, Branch, etc
88
88
>>> t = MemoryTransport()
89
89
>>> l = LockDir(t, 'sample-lock')
91
92
>>> # do something here
137
142
__INFO_NAME = '/info'
139
def __init__(self, transport, path):
144
def __init__(self, transport, path, file_modebits=0644, dir_modebits=0755):
140
145
"""Create a new LockDir object.
142
147
The LockDir is initially unlocked - this just creates the object.
151
156
self.transport = transport
153
158
self._lock_held = False
154
self._info_path = path + self.__INFO_NAME
159
self._fake_read_lock = False
160
self._held_dir = path + '/held'
161
self._held_info_path = self._held_dir + self.__INFO_NAME
162
self._file_modebits = file_modebits
163
self._dir_modebits = dir_modebits
155
164
self.nonce = rand_chars(20)
157
166
def __repr__(self):
162
171
is_held = property(lambda self: self._lock_held)
174
"""Create the on-disk lock.
176
This is typically only called when the object/directory containing the
177
directory is first created. The lock is not held when it's created.
179
if self.transport.is_readonly():
180
raise UnlockableTransport(self.transport)
181
self.transport.mkdir(self.path)
164
183
def attempt_lock(self):
165
184
"""Take the lock; fail if it's already held.
167
186
If you wish to block until the lock can be obtained, call wait_lock()
189
if self._fake_read_lock:
190
raise LockContention(self)
170
191
if self.transport.is_readonly():
171
192
raise UnlockableTransport(self.transport)
173
tmpname = '%s.pending.%s.tmp' % (self.path, rand_chars(20))
194
tmpname = '%s/pending.%s.tmp' % (self.path, rand_chars(20))
174
195
self.transport.mkdir(tmpname)
176
197
self._prepare_info(sio)
178
199
self.transport.put(tmpname + self.__INFO_NAME, sio)
179
# FIXME: this turns into os.rename on posix, but into a fancy rename
180
# on Windows that may overwrite existing directory trees.
181
# NB: posix rename will overwrite empty directories, but not
182
# non-empty directories.
183
self.transport.move(tmpname, self.path)
200
self.transport.rename(tmpname, self._held_dir)
184
201
self._lock_held = True
192
209
def unlock(self):
193
210
"""Release a held lock
212
if self._fake_read_lock:
213
self._fake_read_lock = False
195
215
if not self._lock_held:
196
216
raise LockNotHeld(self)
197
217
# rename before deleting, because we can't atomically remove the whole
199
tmpname = '%s.releasing.%s.tmp' % (self.path, rand_chars(20))
200
self.transport.rename(self.path, tmpname)
219
tmpname = '%s/releasing.%s.tmp' % (self.path, rand_chars(20))
220
self.transport.rename(self._held_dir, tmpname)
201
221
self._lock_held = False
202
222
self.transport.delete(tmpname + self.__INFO_NAME)
203
223
self.transport.rmdir(tmpname)
229
249
if current_info != dead_holder_info:
230
250
raise LockBreakMismatch(self, current_info, dead_holder_info)
231
tmpname = '%s.broken.%s.tmp' % (self.path, rand_chars(20))
232
self.transport.rename(self.path, tmpname)
251
tmpname = '%s/broken.%s.tmp' % (self.path, rand_chars(20))
252
self.transport.rename(self._held_dir, tmpname)
233
253
# check that we actually broke the right lock, not someone else;
234
254
# there's a small race window between checking it and doing the
261
281
raise LockBroken(self)
263
283
def _read_info_file(self, path):
284
"""Read one given info file.
286
peek() reads the info file of the lock holder, if any.
264
288
return self._parse_info(self.transport.get(path))
271
295
Otherwise returns None.
274
info = self._read_info_file(self._info_path)
298
info = self._read_info_file(self._held_info_path)
275
299
assert isinstance(info, dict), \
276
300
"bad parse result %r" % info
320
344
raise LockContention(self)
346
def lock_write(self):
347
"""Wait for and acquire the lock."""
351
"""Compatability-mode shared lock.
353
LockDir doesn't support shared read-only locks, so this
354
just pretends that the lock is taken but really does nothing.
356
# At the moment Branches are commonly locked for read, but
357
# we can't rely on that remotely. Once this is cleaned up,
358
# reenable this warning to prevent it coming back in
360
## warn("LockDir.lock_read falls back to write lock")
361
if self._lock_held or self._fake_read_lock:
362
raise LockContention(self)
363
self._fake_read_lock = True
322
365
def wait(self, timeout=20, poll=0.5):
323
366
"""Wait a certain period for a lock to be released."""
324
367
# XXX: the transport interface doesn't let us guard