Specification for locking of moin resources
- status
- Describe the different classes, their roles and usage, without going into api details.
Creating a locked resource
Any mutable shared resource must be locked.
Example: using a resource
from MoinMoin import lock resource = lock.sharedResource('resource_name', writeTimeout=None, updateTimeout=None, readTimeout=None)
This will create the directory wiki/data/lock/resource_name, which will hold the locks for this resourse.
Only one instance can be created for a 'resource_name'. Calling again will return the same instance.
Clients can shoot themselves into the foot by using two SharedResoruces instances with different names for the same physical resource. See also WYGIWYD
Questions
- Do we need to enable changing the timeouts after creation?
- Do we need to enable removing a resource?
- They should be automatically removed when there lifetime has ended
- What it the lifetime of a resource?
- They should be automatically removed when there lifetime has ended
- What should happen if you call sharedResource on the second time with different timeouts than the first call?
- The timeouts will be updated to the new values affecting all the current locks that access the timeouts from the shared instance?
Options:
Create a resource with lock.addSharedResource(key, *timeouts)
- You can call this only once per resource
Access with lock.sharedResource(key), or directly from the object to be locked, object.sharedResource()
Creating a ReadLock
When you want to use the resource, you must acquire a ReadLock. This will prevent other processes or threads from acquiring a WriteLock and changing the data while you read it.
Example: using a read lock:
readLock = resource.readLock() if readlock.acquire(timeout): try: # read the resource finally: readLock.release()
You can create many ReadLocks as you need for one resource, in the same process or in many processes or threads.
A read lock will create wiki/data/lock/resource_name/read_XXXXX. XXXX is a unique name created with tempfile.mkdtemp. Each lock will have a new unique directory in the resource directory.
Creating a WriteLock
When you want to replace the resource, you must acquire a WriteLock. This will prevent others from acquiring new ReadLocks or UpdateLocks. This lock must be used for the shortest time possible.
Only one WriteLock can be acquired.
Exmaple: using a WriteLock
writeLock = resource. writeLock() if writeLock.acquire(timeout): try: # write the resource finally: writeLock.release()
A write lock will create wiki/data/lock/resource_name/write.
A WriteLock will be acquired only if there are no active ReadLocks or UpdateLock in the lock directory.
Creating an UpdateLock
When you want to replace the resource, but making the replacement will take long time and you don't want to make the resource unreadable, you create an UpdateLock. This will prevent others from acquiring a WriteLock and changing the resource while you read it. This will let other to create ReadLocks, and use the current state of the resource, while you update it to a new state in a temporary location.
Only one UpdateLock can be acquired.
Exmaple: using an UpdateLock
lock = resource.updateLock() if lock.acquire(timeout): try: # copy the resource to temporary location # change the copy if lock.upgrade(WriteLock, timeout): # replace the old resource with the updated copy finally: lock.release()
An update lock will create wiki/data/lock/resource_name/update.
Expiring locks
Any expired locks in the directory will be removed when trying to acquire a new WriteLock or UpdateLock. The timeouts are the timeouts used in the constructor: writeTimeout, updateTimeout, readTimeout.
Any lock with age bigger then the timeout for that lock kind will be removed.
Converting a lock
To convert a read lock or an update lock to a write lock, call convert(LockClass, timeout). The lock will try to acquire a LockClass and remove the original lock directory.
Typical use:
lock = resource.updateLock() if lock.acquire(timeout): try: # do the update if lock.convert(WriteLock, timeout): # write finally: lock.release()
Possible implementation:
def convert(lockClass, timeout=None): if lockClass == self.__class__: raise LockConvertError('same class') delegate = lockClass(self.dir) if delegate.acquire(timeout): self._removeLockDir() self.delegate = delegate # from now on, everything should be delegated to self.delegate return True return False
Making a resource lockable
To make other developers life easier and prevent locking errors, each lockable class should supply a sharedResource method, with good default timeouts.
class Page: def sharedResource(writeTimeout=60, updateTimeout=3600, readTimeout=60): return lock.sharedResource(self.getPagePath('lock'), writeTimeout, updateTimeout, readTimeout)
Now to lock a page, the client work directly with the page object:
lock = page.sharedResource().readLock() if lock.acquire(timeout): try: # read page finally: lock.release()
Todo
Create a stress tests for locking, take stuff from AlexanderSchremmer/FileLocking