How to implement an offline reader writer lock
- by Peter Morris
Some context for the question
All objects in this question are persistent.
All requests will be from a Silverlight client talking to an app server via a binary protocol (Hessian) and not WCF.
Each user will have a session key (not an ASP.NET session) which will be a string, integer, or GUID (undecided so far).
Some objects might take a long time to edit (30 or more minutes) so we have decided to use pessimistic offline locking. Pessimistic because having to reconcile conflicts would be far too annoying for users, offline because the client is not permanently connected to the server.
Rather than storing session/object locking information in the object itself I have decided that any aggregate root that may have its instances locked should implement an interface ILockable
public interface ILockable
{
Guid LockID { get; }
}
This LockID will be the identity of a "Lock" object which holds the information of which session is locking it.
Now, if this were simple pessimistic locking I'd be able to achieve this very simply (using an incrementing version number on Lock to identify update conflicts), but what I actually need is ReaderWriter pessimistic offline locking.
The reason is that some parts of the application will perform actions that read these complex structures. These include things like
Reading a single structure to clone it.
Reading multiple structures in order to create a binary file to "publish" the data to an external source.
Read locks will be held for a very short period of time, typically less than a second, although in some circumstances they could be held for about 5 seconds at a guess.
Write locks will mostly be held for a long time as they are mostly held by humans.
There is a high probability of two users trying to edit the same aggregate at the same time, and a high probability of many users needing to temporarily read-lock at the same time too. I'm looking for suggestions as to how I might implement this.
One additional point to make is that if I want to place a write lock and there are some read locks, I would like to "queue" the write lock so that no new read locks are placed. If the read locks are removed withing X seconds then the write lock is obtained, if not then the write lock backs off; no new read-locks would be placed while a write lock is queued.
So far I have this idea
The Lock object will have a version number (int) so I can detect multi-update conflicts, reload, try again.
It will have a string[] for read locks
A string to hold the session ID that has a write lock
A string to hold the queued write lock
Possibly a recursion counter to allow the same session to lock multiple times (for both read and write locks), but not sure about this yet.
Rules:
Can't place a read lock if there is a write lock or queued write lock.
Can't place a write lock if there is a write lock or queued write lock.
If there are no locks at all then a write lock may be placed.
If there are read locks then a write lock will be queued instead of a full write lock placed. (If after X time the read locks are not gone the lock backs off, otherwise it is upgraded).
Can't queue a write lock for a session that has a read lock.
Can anyone see any problems? Suggest alternatives? Anything? I'd appreciate feedback before deciding on what approach to take.