~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lockdir.py

  • Committer: Michael Ellerman
  • Date: 2006-02-28 14:45:51 UTC
  • mto: (1558.1.18 Aaron's integration)
  • mto: This revision was merged to the branch mainline in revision 1586.
  • Revision ID: michael@ellerman.id.au-20060228144551-3d9941ecde4a0b0a
Update contrib/pwk for -p1 diffs from bzr

Show diffs side-by-side

added added

removed removed

Lines of Context:
87
87
>>> # typically will be obtained from a BzrDir, Branch, etc
88
88
>>> t = MemoryTransport()
89
89
>>> l = LockDir(t, 'sample-lock')
90
 
>>> l.create()
91
90
>>> l.wait_lock()
92
91
>>> # do something here
93
92
>>> l.unlock()
105
104
        LockBreakMismatch,
106
105
        LockBroken,
107
106
        LockContention,
 
107
        LockError,
108
108
        LockNotHeld,
109
109
        NoSuchFile,
110
 
        PathError,
111
 
        ResourceBusy,
112
110
        UnlockableTransport,
113
111
        )
114
 
from bzrlib.trace import mutter
115
112
from bzrlib.transport import Transport
116
113
from bzrlib.osutils import rand_chars
117
114
from bzrlib.rio import RioWriter, read_stanza, Stanza
121
118
# lock at the same time they should *both* get it.  But then that's unlikely
122
119
# to be a good idea.
123
120
 
 
121
# TODO: Transport could offer a simpler put() method that avoids the
 
122
# rename-into-place for cases like creating the lock template, where there is
 
123
# no chance that the file already exists.
 
124
 
124
125
# TODO: Perhaps store some kind of note like the bzr command line in the lock
125
126
# info?
126
127
 
127
128
# TODO: Some kind of callback run while polling a lock to show progress
128
129
# indicators.
129
130
 
130
 
# TODO: Make sure to pass the right file and directory mode bits to all
131
 
# files/dirs created.
132
 
 
133
131
_DEFAULT_TIMEOUT_SECONDS = 300
134
132
_DEFAULT_POLL_SECONDS = 0.5
135
133
 
138
136
 
139
137
    __INFO_NAME = '/info'
140
138
 
141
 
    def __init__(self, transport, path, file_modebits=0644, dir_modebits=0755):
 
139
    def __init__(self, transport, path):
142
140
        """Create a new LockDir object.
143
141
 
144
142
        The LockDir is initially unlocked - this just creates the object.
153
151
        self.transport = transport
154
152
        self.path = path
155
153
        self._lock_held = False
156
 
        self._fake_read_lock = False
157
 
        self._held_dir = path + '/held'
158
 
        self._held_info_path = self._held_dir + self.__INFO_NAME
159
 
        self._file_modebits = file_modebits
160
 
        self._dir_modebits = dir_modebits
 
154
        self._info_path = path + self.__INFO_NAME
161
155
        self.nonce = rand_chars(20)
162
156
 
163
157
    def __repr__(self):
167
161
 
168
162
    is_held = property(lambda self: self._lock_held)
169
163
 
170
 
    def create(self, mode=None):
171
 
        """Create the on-disk lock.
172
 
 
173
 
        This is typically only called when the object/directory containing the 
174
 
        directory is first created.  The lock is not held when it's created.
175
 
        """
176
 
        if self.transport.is_readonly():
177
 
            raise UnlockableTransport(self.transport)
178
 
        self.transport.mkdir(self.path, mode=mode)
179
 
 
180
164
    def attempt_lock(self):
181
165
        """Take the lock; fail if it's already held.
182
166
        
183
167
        If you wish to block until the lock can be obtained, call wait_lock()
184
168
        instead.
185
169
        """
186
 
        if self._fake_read_lock:
187
 
            raise LockContention(self)
188
170
        if self.transport.is_readonly():
189
171
            raise UnlockableTransport(self.transport)
190
172
        try:
191
 
            tmpname = '%s/pending.%s.tmp' % (self.path, rand_chars(20))
 
173
            tmpname = '%s.pending.%s.tmp' % (self.path, rand_chars(20))
192
174
            self.transport.mkdir(tmpname)
193
175
            sio = StringIO()
194
176
            self._prepare_info(sio)
195
177
            sio.seek(0)
196
 
            # append will create a new file; we use append rather than put
197
 
            # because we don't want to write to a temporary file and rename
198
 
            # into place, because that's going to happen to the whole
199
 
            # directory
200
 
            self.transport.append(tmpname + self.__INFO_NAME, sio)
201
 
            self.transport.rename(tmpname, self._held_dir)
 
178
            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)
202
184
            self._lock_held = True
203
185
            self.confirm()
204
 
        except (PathError, DirectoryNotEmpty, FileExists, ResourceBusy), e:
205
 
            mutter("contention on %r: %s", self, e)
206
 
            raise LockContention(self)
 
186
            return
 
187
        except (DirectoryNotEmpty, FileExists), e:
 
188
            pass
 
189
        # fall through to here on contention
 
190
        raise LockContention(self)
207
191
 
208
192
    def unlock(self):
209
193
        """Release a held lock
210
194
        """
211
 
        if self._fake_read_lock:
212
 
            self._fake_read_lock = False
213
 
            return
214
195
        if not self._lock_held:
215
196
            raise LockNotHeld(self)
216
197
        # rename before deleting, because we can't atomically remove the whole
217
198
        # tree
218
 
        tmpname = '%s/releasing.%s.tmp' % (self.path, rand_chars(20))
219
 
        # gotta own it to unlock
220
 
        self.confirm()
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)
225
204
 
226
 
    def break_lock(self):
227
 
        """Break a lock not held by this instance of LockDir.
228
 
 
229
 
        This is a UI centric function: it uses the bzrlib.ui.ui_factory to
230
 
        prompt for input if a lock is detected and there is any doubt about
231
 
        it possibly being still active.
232
 
        """
233
 
        self._check_not_locked()
234
 
        holder_info = self.peek()
235
 
        if holder_info is not None:
236
 
            if bzrlib.ui.ui_factory.get_boolean(
237
 
                "Break lock %s held by %s@%s [process #%s]" % (
238
 
                    self.transport,
239
 
                    holder_info["user"],
240
 
                    holder_info["hostname"],
241
 
                    holder_info["pid"])):
242
 
                self.force_break(holder_info)
243
 
        
244
205
    def force_break(self, dead_holder_info):
245
206
        """Release a lock held by another process.
246
207
 
259
220
        """
260
221
        if not isinstance(dead_holder_info, dict):
261
222
            raise ValueError("dead_holder_info: %r" % dead_holder_info)
262
 
        self._check_not_locked()
 
223
        if self._lock_held:
 
224
            raise AssertionError("can't break own lock: %r" % self)
263
225
        current_info = self.peek()
264
226
        if current_info is None:
265
227
            # must have been recently released
266
228
            return
267
229
        if current_info != dead_holder_info:
268
230
            raise LockBreakMismatch(self, current_info, dead_holder_info)
269
 
        tmpname = '%s/broken.%s.tmp' % (self.path, rand_chars(20))
270
 
        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)
271
233
        # check that we actually broke the right lock, not someone else;
272
234
        # there's a small race window between checking it and doing the 
273
235
        # rename.
278
240
        self.transport.delete(broken_info_path)
279
241
        self.transport.rmdir(tmpname)
280
242
 
281
 
    def _check_not_locked(self):
282
 
        """If the lock is held by this instance, raise an error."""
283
 
        if self._lock_held:
284
 
            raise AssertionError("can't break own lock: %r" % self)
285
 
 
286
243
    def confirm(self):
287
244
        """Make sure that the lock is still held by this locker.
288
245
 
304
261
            raise LockBroken(self)
305
262
        
306
263
    def _read_info_file(self, path):
307
 
        """Read one given info file.
308
 
 
309
 
        peek() reads the info file of the lock holder, if any.
310
 
        """
311
264
        return self._parse_info(self.transport.get(path))
312
265
 
313
266
    def peek(self):
318
271
        Otherwise returns None.
319
272
        """
320
273
        try:
321
 
            info = self._read_info_file(self._held_info_path)
 
274
            info = self._read_info_file(self._info_path)
322
275
            assert isinstance(info, dict), \
323
276
                    "bad parse result %r" % info
324
277
            return info
366
319
            else:
367
320
                raise LockContention(self)
368
321
 
369
 
    def lock_write(self):
370
 
        """Wait for and acquire the lock."""
371
 
        self.attempt_lock()
372
 
 
373
 
    def lock_read(self):
374
 
        """Compatibility-mode shared lock.
375
 
 
376
 
        LockDir doesn't support shared read-only locks, so this 
377
 
        just pretends that the lock is taken but really does nothing.
378
 
        """
379
 
        # At the moment Branches are commonly locked for read, but 
380
 
        # we can't rely on that remotely.  Once this is cleaned up,
381
 
        # reenable this warning to prevent it coming back in 
382
 
        # -- mbp 20060303
383
 
        ## warn("LockDir.lock_read falls back to write lock")
384
 
        if self._lock_held or self._fake_read_lock:
385
 
            raise LockContention(self)
386
 
        self._fake_read_lock = True
387
 
 
388
322
    def wait(self, timeout=20, poll=0.5):
389
323
        """Wait a certain period for a lock to be released."""
390
324
        # XXX: the transport interface doesn't let us guard