Hello,
I would like to introduce my thoughts on lock / unlock and the question, if and how a client should identified. I am at a point where the app works without locking, but I've also build a locking prototype (for immense
testing purposes); now I'm at the last redesign phase, putting these together in the most intelligent way...
I will start with the locking:
My first thought, after reading some articles in here, was: The synchronization
must be on the record, not on the whole database. That should be a minimalistic approach concerning efficiency and fair behaviour. So I have implemented a LockManager who manages a list of locks (one for each record). The synchronization now works on the lock (so on the record, if you want).
Not bad. But not very much. The second thought concerned fair behaviour again, but it would be too less to say it's just a question of this. It is more a question of "correctness", "stability" or whatever you want. It is the question of implementing a queue. Or in my thoughts: It is not a question, it is a must, all other solution "hope" that the right
thread will be called some time...
But I have also read in here some say they didn't do that for not going too deep - my solution isn't deep at all, short, but a bit tricky (not complicated, just nice) concerning locking the whole database.
It works like this: The lockmanager or better each locker manages a queue of N entries per lock. The locker hands out numbers (0 to N-1) to each thread trying to lock a record. Locking only works if (a) the record concerned isn't locked yet and (b) the number the thread got from the locker is the first on the row of numbers the locker manages (for example: max 5 threads per locker, locker hands out numbers 0,1,2,3,4 and if 0 is out of queue, locker hands out 0 again, now 1 is the first number on the queue and 0 the last. If the queue is full, the locking is rejected immediately ("server busy"; for synchronizing on records, not on the database, the max can be low; I would call 10 a big number even if the system works all around the world).
Now let's have a look on locking the whole database: The lockmanager not only has locks for each record, it also has a special lock for locking the whole db. In this case, it (a) handles this try to lock like each other try, that means for locking "-1" a thread get's a number and maybe is queued; but (b) when it is it's turn, the "record -1" is locked, and, now the main point, it starts so many threads as records are there, each thread trying to lock one record. As the threads have been started, the thread constructing these "under-threads" joins one after the other, so "locking -1" stops blocking in the moment all recors have been locked! There is a small thought to be thought on what happens if the "server" is busy (the client can wait for a second and try again or just inform the user, but working on the server should be more intelligent). Simple - just wait on the locker one tries to get, so one gets informed if a record gets unlocked - that is the first moment the thread get's the chance(!) to be queued.
Alltogether it took about 180 lines coding (reusable) including heavy logging (very nice to read
).
Ok�, now the second "big" question: Should the server identify the client (for the request not to react on unlock(int) a record which was not locked by "this" connection)?
It really took me some days to say YES! There are good reasons for NO: Less work, less thoughts, less problems.
But there are two good reasons for the YES: Guaranteed correctness - and the request for implementing it this way. Some sai - SUN says each client would call lock-read-modify-unlock, and so there is no need for identifying the client, for we can trust the clients. I say: If you interpret this that way, where should be the sense of the feature-demanding sentence?!?! There would be no sense at all...
Ok�, now I will have more work, more thoughts and some more problems :roll: Here is my suggestion:
Have three(!) kinds of DataBase interface implementing classes: Let us call them ServerDB, ClientDBlocal, ClientDBremote. Nothing to say about ClientDBlocal, just that it's lock and unlock methods stay empty (you could change this if you think locking is requested also here, but I do not think so). Ok�, ClientDBremote: In it's constructor, it get's the (one) ServerDB reference and calls a method getConnectionID(), returning a
string (constructing the string is a bit like the ID-handler from the locker, but I will add some feature for not only having 1,2,3,4, but something like "[nr][codingstring]", where number is a number between 0 and N-1, N is the max connections to the server, and codingstring is a string of length, let's say 8 or 10 or 16, whatever you like, of random characters, just for a something like security. It's not much work to implement this but it's a step to security (the step is much bigger than the work).
In ClientDBremote, close() means closeConnection(String id) on ServerDB. lock(int i) means lock(int i, String id) on ServerDB. The same with unlock.
Why do I think that this is a good solution (of course there are many others also doing the job)? First, if SUN says "lock (int)" and "unlock (int)" are requested, I don't think "lock(string, int)" should be used, at least not in the DataInterface. SUN says very clearly and strictly, what we have to implement; if I would correct the assignments, I would subtract infinit points for violating the requirements...
On the other hand, this solution does introduce an extra intermediate "business logic" class with methods like "book(int)". Also at this point, I think (here would not bet my fingers
) that SUN makes clear what they want - the more or less direct use of the DataInterface methods. One may like it or not - but one
can do it this way, even somehow elegant.
So, why am I talking, talking, talking without an end? Of course to get clear in my mind... There was no sense for
you listening to all this shit...
No, that was a joke... I really would appreciate your thoughts on this. Really!
Maybe it also helps some others?!
Detlev