''' 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? * 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