I'm doing URLyBird 1.3.2, and have been thinking about locking for a while now. The best solution I can think of is to use multiversion concurrency control
http://en.wikipedia.org/wiki/Multiversion_concurrency_control which makes reading and finding records easy.
Glossary:
Client: not a GUI client, but something that directly calls methods on a Data.java instance.
Timestamp: a unique serial number, larger number means newer (like an order number at a restaurant)
The interface methods I'm required to implement (DBMain.java) in the data access class (Data.java) don't have a cookie argument. Therefore I make an instance of Data for each client, and each Data object contains a timestamp which acts as the client ID (i.e. cookie). I can't think of any other way to identify a client.
Each Data.java also has a set of the timestamps of the records returned when the last search (or read) was done by this client, to avoid locking records that have been changed by some other client in the mean time.
Data.java has to implement this method:
To avoid blocking all the other clients, each instance of Data is run in a seperate
thread.
If a client is waiting for a record to become available, they won't be able to do anything else.
The table is stored in memory as a ConcurrentHashMap of Record objects. Each Record has a list of RecordVersions. Each RecordVersion has a timestamp and a copy of the field values as they were at the time. RecordVersions are immutable.
I'll probably use a CopyOnWriteArrayList for Record.
The singleton lock manager contains:
-a synchronized treemap<int recordNo,int clientID> of the currently active locks, sorted by the time when they were acquired
-a synchronized treeset<Request> of the currently active requests, sorted by timestamp
-an unsorted synchronized set<int recordNo> of the record numbers of deleted records, so they can be reused for new records
Locking:
Check the record exists. Check that the Record's timestamp is not newer than our corresponding timestamp in our Data instance.
If it is, the Record has been changed by someone else in the mean time. Wait on the Record object. Do the check again when notified (BTW the Record will probably have been modified when we wake up, so waiting for the record to become unlocked seems kind of dumb to me. Oh well). Locks time out eventually (this should be enough to avoid deadlock).
Unlocking:
Check that Record is actually locked by us, unlock, notify any request that is waiting for this Record to become available.
Creating:
This is probably the most difficult operation.
First do a "find" to make sure the record does not exist...presumably the "primary key" is all the fields?
To avoid duplicates due to a race condition if 2 identical records are created at once, maintain a synchronized "create list" of the current pending Create requests.
We need to get a record number somehow, and should really re-use deleted record numbers, so attempt to get a number from the delete list.
Do we need to lock the record?? Hmmm....the record should definitely be unlocked (because it doesn't exist!) so if we just add it we should be ok, assuming we are careful about synchronizing the record number allocation properly.
Update corresponding timestamp in Data.
isLocked:
Just returns the Record's lock status as it currently is, makes no guarantees. A pretty useless method.
The alternative is to return the lock status as it *was* when the request was submitted, but that is even more useless.
Read:
If the current Record's timestamp is newer than ours, we need to go through the list of old versions. RecordVersions are immutable, so that makes reading easy :-)
Update corresponding timestamp in Data.
Find:
Iterate through the ConcurrentHashMap looking for matches, basically Read() in a loop.
* The following operations require a lock *
Updating/booking:
Check that the lock is ok and the record is valid, create a new RecordVersion object, populate the fields to the same as the most recent RecordVersion (except for the changed fields obviously), set timestamp to our timestamp, then add to the ConcurrentHashMap. Inform database file handler of the changed fields.
Do we need to check timestamps? I don't think so, because we have locked this record and each Data object can only be run by one thread.
Update corresponding timestamp in Data.
Deleting:
Very similar to updating. Add the record number to the delete list, mark RecordVersion as deleted.
We will have to trim off the old copies of RecordVersions as requests are fulfilled...should be easy to do with CopyOnWriteArrayList...after any write, the request checks to see if there are any obsolete RecordVersions, and if so removes them. *Any client holding a lock on the record should lose the lock*
Update corresponding timestamp in Data.
Well that's basically it...have I overlooked anything?
[ April 25, 2006: Message edited by: Roy Mallard ]