87
87
>>> # typically will be obtained from a BzrDir, Branch, etc
88
88
>>> t = MemoryTransport()
89
89
>>> l = LockDir(t, 'sample-lock')
92
91
>>> # do something here
143
137
__INFO_NAME = '/info'
145
def __init__(self, transport, path, file_modebits=0644, dir_modebits=0755):
139
def __init__(self, transport, path):
146
140
"""Create a new LockDir object.
148
142
The LockDir is initially unlocked - this just creates the object.
157
151
self.transport = transport
159
153
self._lock_held = False
160
self._fake_read_lock = False
161
self._held_dir = path + '/held'
162
self._held_info_path = self._held_dir + self.__INFO_NAME
163
self._file_modebits = file_modebits
164
self._dir_modebits = dir_modebits
154
self._info_path = path + self.__INFO_NAME
165
155
self.nonce = rand_chars(20)
167
157
def __repr__(self):
172
162
is_held = property(lambda self: self._lock_held)
175
"""Create the on-disk lock.
177
This is typically only called when the object/directory containing the
178
directory is first created. The lock is not held when it's created.
180
if self.transport.is_readonly():
181
raise UnlockableTransport(self.transport)
182
self.transport.mkdir(self.path)
184
164
def attempt_lock(self):
185
165
"""Take the lock; fail if it's already held.
187
167
If you wish to block until the lock can be obtained, call wait_lock()
190
if self._fake_read_lock:
191
raise LockContention(self)
192
170
if self.transport.is_readonly():
193
171
raise UnlockableTransport(self.transport)
195
tmpname = '%s/pending.%s.tmp' % (self.path, rand_chars(20))
173
tmpname = '%s.pending.%s.tmp' % (self.path, rand_chars(20))
196
174
self.transport.mkdir(tmpname)
198
176
self._prepare_info(sio)
200
178
self.transport.put(tmpname + self.__INFO_NAME, sio)
201
self.transport.rename(tmpname, self._held_dir)
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)
202
184
self._lock_held = True
205
except (DirectoryNotEmpty, FileExists, ResourceBusy), e:
187
except (DirectoryNotEmpty, FileExists), e:
207
189
# fall through to here on contention
208
190
raise LockContention(self)
210
192
def unlock(self):
211
193
"""Release a held lock
213
if self._fake_read_lock:
214
self._fake_read_lock = False
216
195
if not self._lock_held:
217
196
raise LockNotHeld(self)
218
197
# rename before deleting, because we can't atomically remove the whole
220
tmpname = '%s/releasing.%s.tmp' % (self.path, rand_chars(20))
221
self.transport.rename(self._held_dir, tmpname)
199
tmpname = '%s.releasing.%s.tmp' % (self.path, rand_chars(20))
200
self.transport.rename(self.path, tmpname)
222
201
self._lock_held = False
223
202
self.transport.delete(tmpname + self.__INFO_NAME)
224
203
self.transport.rmdir(tmpname)
250
229
if current_info != dead_holder_info:
251
230
raise LockBreakMismatch(self, current_info, dead_holder_info)
252
tmpname = '%s/broken.%s.tmp' % (self.path, rand_chars(20))
253
self.transport.rename(self._held_dir, tmpname)
231
tmpname = '%s.broken.%s.tmp' % (self.path, rand_chars(20))
232
self.transport.rename(self.path, tmpname)
254
233
# check that we actually broke the right lock, not someone else;
255
234
# there's a small race window between checking it and doing the
282
261
raise LockBroken(self)
284
263
def _read_info_file(self, path):
285
"""Read one given info file.
287
peek() reads the info file of the lock holder, if any.
289
264
return self._parse_info(self.transport.get(path))
296
271
Otherwise returns None.
299
info = self._read_info_file(self._held_info_path)
274
info = self._read_info_file(self._info_path)
300
275
assert isinstance(info, dict), \
301
276
"bad parse result %r" % info
345
320
raise LockContention(self)
347
def lock_write(self):
348
"""Wait for and acquire the lock."""
352
"""Compatability-mode shared lock.
354
LockDir doesn't support shared read-only locks, so this
355
just pretends that the lock is taken but really does nothing.
357
# At the moment Branches are commonly locked for read, but
358
# we can't rely on that remotely. Once this is cleaned up,
359
# reenable this warning to prevent it coming back in
361
## warn("LockDir.lock_read falls back to write lock")
362
if self._lock_held or self._fake_read_lock:
363
raise LockContention(self)
364
self._fake_read_lock = True
366
322
def wait(self, timeout=20, poll=0.5):
367
323
"""Wait a certain period for a lock to be released."""
368
324
# XXX: the transport interface doesn't let us guard