Part of bzrlib
bzr on-disk objects are locked by the existence of a directory with a particular name within the control directory. We use this rather than OS internal locks (such as flock etc) because they can be seen across all transports, including http.
Objects can be read if there is only physical read access; therefore readers can never be required to create a lock, though they will check whether a writer is using the lock. Writers can't detect whether anyone else is reading from the resource as they write. This works because of ordering constraints that make sure readers see a consistent view of existing data.
Waiting for a lock must be done by polling; this can be aborted after a timeout.
Locks must always be explicitly released, typically from a try/finally block -- they are not released from a finalizer or when Python exits.
Locks may fail to be released if the process is abruptly terminated (machine stop, SIGKILL) or if a remote transport becomes permanently disconnected. There is therefore a method to break an existing lock. This should rarely be used, and generally only with user approval. Locks contain some information on when the lock was taken and by who which may guide in deciding whether it can safely be broken. (This is similar to the messages displayed by emacs and vim.) Note that if the lock holder is still alive they will get no notification that the lock has been broken and will continue their work -- so it is important to be sure they are actually dead.
A lock is represented on disk by a directory of a particular name, containing an information file. Taking a lock is done by renaming a temporary directory into place. We use temporary directories because for all known transports and filesystems we believe that exactly one attempt to claim the lock will succeed and the others will fail. (Files won't do because some filesystems or transports only have rename-and-overwrite, making it hard to tell who won.)
The desired characteristics are:
Storage formats use the locks, and also need to consider concurrency issues underneath the lock. A format may choose not to use a lock at all for some operations.
LockDirs always operate over a Transport. The transport may be readonly, in which case the lock can be queried but not acquired.
Locks are identified by a path name, relative to a base transport.
Calling code will typically want to make sure there is exactly one LockDir object per actual lock on disk. This module does nothing to prevent aliasing and deadlocks will likely occur if the locks are aliased.
In the future we may add a "freshen" method which can be called by a lock holder to check that their lock has not been broken, and to update the timestamp within it.
>>> from bzrlib.transport.memory import MemoryTransport >>> # typically will be obtained from a BzrDir, Branch, etc >>> t = MemoryTransport() >>> l = LockDir(t, 'sample-lock') >>> l.create() >>> token = l.wait_lock() >>> # do something here >>> l.unlock()
Some classes of stale locks can be predicted by checking: the host name is the same as the local host name; the user name is the same as the local user; the process id no longer exists. The check on user name is not strictly necessary but helps protect against colliding host names.
|Class||LockDir||Write-lock guarding access to data.|
|Class||LockHeldInfo||The information recorded about a held lock.|
|Function||get_username_for_lock_info||Get a username suitable for putting into a lock.|