The Sun Certified Java Developer Exam with J2SE 5: paper version from Amazon, PDF from Apress, Online reference: Books 24x7 Personal blog
Everything is done from single method, but it is not an atomic operation.
The Sun Certified Java Developer Exam with J2SE 5: paper version from Amazon, PDF from Apress, Online reference: Books 24x7 Personal blog
The Sun Certified Java Developer Exam with J2SE 5: paper version from Amazon, PDF from Apress, Online reference: Books 24x7 Personal blog
SCJP, SCJD, SCWCD, SCBCD, SCEA, SCJP6
To be obtained: SCEA 5
The Sun Certified Java Developer Exam with J2SE 5: paper version from Amazon, PDF from Apress, Online reference: Books 24x7 Personal blog
The Sun Certified Java Developer Exam with J2SE 5: paper version from Amazon, PDF from Apress, Online reference: Books 24x7 Personal blog
I chose to ignore deadlocks in my assignment, as I believed it was out of scope. None of my clients would ever attempt to gain two locks at the same time, so deadlock could not occur with my code.
Your deadlock prevention will still allow a deadlock if three or more clients are doing locking at once:
client 'A' locks record 1 and attempts to lock record 2
client 'B' locks record 2 and attempts to lock record 3
client 'C' locks record 3 and attempts to lock record 1
What do you do if the lockTimeout property is exceeded?
I am not sure about you throwing RuntimeExceptions for DeadLock (and presumably Timeout). I would think that client code should be able to handle both conditions, but since the client does not have to catch a RuntimeException, a client application could die a horrible death if the programmer did not catch your exception.
Also, I am unsure about your looking at thread death as a way of determining if the client has died. I guess it depends on what thread you are using. Just using the thread running your remote class is not really valid though, as there is no guarantee that two calls to remote methods from the one client will run in the same thread.
When LockManager detects a deadlock, it's common sense that he must communicate it to the caller. As DBAccess interface cannot be changed (no new checked exceptions are allowed), I see only 3 ways to do it :
The Sun Certified Java Developer Exam with J2SE 5: paper version from Amazon, PDF from Apress, Online reference: Books 24x7 Personal blog
I have used many of the same arguments to explain why I believe that the client should be able to call the lock() and unlock() methods directly, rather than calling a modify() method on the server.
This has other issues though. And the one that I think you will hate is that this means that anyone accessing the Data class directly will loose all the work you have put into the LockManager.
Originally posted by Vlad Rabkin:
Hallo Andrew,
Hallo Philippe,
Could you help me understand the ideas about Unreferenced or WeakHashMap.
Let's take WeakHashMap:
My key is Record Number (Wrapped int).
It is my constructor of Data:
public Data(String fileName, LockManager lockManager)
My C-tor for RemoteData:
public DataRemote(DB db, ReadWriteLock rwManager)
In DataRemote in lock method I do have following:
cookie = db_.lock(recNo);
and in unlock:
db_.unlock(recNo, cookie);
, where recNo is int.
So, actually my DataRemote has no direct reference on Key (Integer(recNo)).
It has only reference on Data.
If I do WeakHashMap, lock will always suddenly dissapear! ??? Is it right? If so, how should I do it?
Thanx,
Vlad
In the WeakHashMap, your key is the reference to the client, and your value is the Integer wrapped recno.
Originally posted by Vlad Rabkin:
Hi Max,
WAW, I don't understand it ...
Where do U keep ypur cookies then?
My Key is ID(Integer) and my Value is Lock object (This class wrappes only the long cookie).
And what you mean id is client? What class/object do U mean?
Thanx,
Vlad
Let me back up a bit: Can you lay out your design in a bit more detail?
"I'm not back." - Bill Harding, Twister
Originally posted by Jim Yingst:
Mmmm, I don't think the WeakHashMap is as clean a fit here as Max has implies.
2. Make sure that your lock take an Integer as a pram, instead of taking an int and wrapping i on the fly.
This is a problem - the lock() method defined in the API we must implement required an int, not Integer. We can't change that.
I suppose we could provide an overloaded lock() method that takes an Integer and is not part of the DB interface (called DBMain in some assignments, I think). But that feels wrong to me, to have a method in the DB interface that is almost what we need, and then we don't use it at all, instead relying on another method. If we're going to do that, why not give Data some additional methods that just omit cookies entirely? After all, they just get in the way of a clear, simple program.
Seriously, my Data does provide some additional methods not in DB - notably getMetaData() which allows me to avoid hardcoding column names and lengths. But I try to use the previously specified DB methods wherever possible, which in this case means using lock(int) rather than lock(Integer). So this solution seems unsatisfactory to me. Furthermore I worry about those pesky junior programmers, who may not really understand why they must use lock(Integer) rather than lock(int). (And I hope they don't decide to use that same Integer anywhere else - though that seems unlikely.) I'm thinking the WeakHashMap approach makes more sense for people doing an assignment without cookies (there are some like that, right?).
My own solution is: A ConnectionFactory returns Connection instances, one per client connection. Connection is an interface whose server-side implementation is ConnectionImpl. Each ConnectionImpl carries its own HashMap or the record numbers and cookies pertaining to the locks held by that particular connection[/b]. Connection has a close() method which iterates though the map and closes all associated connections. This close() method can be called by both unreferenced() [i]and finalize(). I may not really need both, but why not; it can't hurt. The close() method is written in such a way that it doesn't matter if it's called more than once.
It does feel a bit cumbersome for each Connection to have its own HashMap.
They were previously very lightweight objects; now they're a bit heavier. If we had a lot of clients and were worrying about memory usage and performance, this could be a problem. But there's little justification for that particular concern now, IMO. In the real world, I'd advise the customer to just get rid of those dame cookies, and the problem will go away entirely.
Both are valid solutions, and both offer challenges. The problem with un referenced, IMO, is twofold. First, it's more complicated (you have to write extra code: WeakHashMap doesn't require any code at all).
A valid concern in general. Though I think there's a certain advantage in having cleanup code appear explicitly in our classes rather than being handled behind the scenes. With explicit code, junior programmers will at least know that it's there. It's isolated in one class; easy enough to skip past unless/until it's necessary to modify the cleanup somehow.
Second, it's possible that Unreferenced will be called several times, because the reference count can drop to zero, then back up again, then back to zero. Unreferenced, of course, is called whenever the reference count hits zero, not just the first time.
Interesting point. I'm thinking this is a non-issue for my code, but want to check my understanding. How would the reference count increase after it's hit zero? The main way that comes to mind is if the instance is bound to the registry - any clint that does Naming.lookup() with the appropriate name will get a new cleint reference to the object, no matter how many other client refereces exist.
So OK, beware of making a bound object Unreferenced, as the Unreferenced() method may be called multiple times. But with the ConnectionFactory, only the factory is bound. Each Connection instance returned by the factory's connect() method is a brand new instance, and only one client reference exists - from the client who did the connect(). In this context, a call to Unreferenced() seems pretty unambiguous - that client is done, period, and won't be called again. Does that make sense? Is there some other mechanism through which the count of remote references could increase from zero? My experience with RMI is pretty limited, so I could well be overlooking something.
"I'm not back." - Bill Harding, Twister
Second, it's possible that Unreferenced will be called several times, because the reference count can drop to zero, then back up again, then back to zero. Unreferenced, of course, is called whenever the reference count hits zero, not just the first time.
RMI may decide to recycle the object to another client. Try this. Write 2 remote threads, and give the object to one of them. Then, after the client releases the object, ref count hits zero. Now, give the object to the second client. When that second thread is finished(caput, dead, no longer with us, visiting Elvis), the ref count will again hit zero, and Unreferenced will again be called. The problem here is that you have to write fairly non-junior-level code to provide functionality that the user didn't ask for. Further, from an aesthetic point if view, you've limited the salability of your architecture, because your built in architectural premise is that each client gets a unique connection. If that premise breaks, you're going to be wanting a paddle
Originally posted by Jim Yingst:
[JY]: This is a problem - the lock() method defined in the API we must implement required an int, not Integer. We can't change that.
[MH]: Nor should we: However, that's not the map Vlad and I were talking about. Vlad isn't talking about Data, he's talking about his lock manager.
OK, I guess I need to understand this better. Does Data still have lock(int) and unlock(int, long) methods? Are they still used? Are they called by the LockManager, or do they call the LockManager's methods? Which object is actually enforcing the locks, and which is just forwarding calls? If a Client calls LockManager.lock(Integer), does that create a call to Data.lock(int)? Or is the record already locked once LockManager.lock(Integer) is called? If it's the latter, isn't this circumventing Data's API entirely (as far as locking is concerned at least)? I'm so confused...
Since I'm not sure I'm understanding how Vlad or Max's designs work here, I'll just talk about my own design while hoping for clarification. Swapping the order around a bit here...
[MH]: I don't think I understand what you're doing here. Can you elaborate a bit about what's in these maps?
Each Connection object has an internal HashMap of its own. The keys are Integers representing records that have been locked by that Connection; the values are Longs representing the cookie values that would be needed to unlock those records. To clarify: this data structure has nothing to do with enforcing locks (or the wait/notify that goes with them). That's handled by another HashMap inside Data, with entries for all locked records. The Connection's HashMap just helps the Connection know how to unlock any records it had previously locked, in case of disconnect. The Connection still needs to make a call to Data to actually perform the unlock().
[MH]: Your own code is limited by this. That is, if your Unreferenced code isn't synchronized on the hashmap, then it won't notify the map when you quitely remove an element. OTOH, if it is synchronized on the Map, it might never get called, because a client that's holding on the lock might die without ever calling notifyAll().
Sorry, I don't understand this. For what it's worth, I do access the HashMap from synchronized code, because RMI may allow multiple threads to be associated with a single Connection, and so I must protect mutable shared data. But why do we care about notify()/notifyAll() in here? Here's a simplified view of my close() method:
It's not performing any wait(), and so it doesn't need to receive any notify() or notifyAll() in order to function. You're not saying we need to receive a notify() in order to enter a synchronized block, are you? Or are you referring to the fact that other threads must receive a notify() if they were wait()ing for a record that the Connection unlocked? That's taken care of by db.unlock(). The db's lock() and unlock() implement a wait/notify, but that's using a separate monitor abstracted away inside the db instance. Not really relevant here, IMO.
----
[MH]: There's stuff going behind the scenes anyway: the gc, the dgc, the callbacks for Unreferenced, etc. IMO, and again, only MHO, the weakHashMap is a discreet bundle of isolation, written by Sun, that does everything we need here. I'm not sure I'm motivated to duplicate it's functionality.
OK, very valid point.
[MH]: RMI may decide to recycle the object to another client. Try this. Write 2 remote threads,
Is that two threads in the same remote JVM, or separate JVMs?
and give the object to one of them.
"Give" how? Are you talking about an object which has been bound, which any thread in a remote client can "get" with Naming.lookup()? That's what I do with my ConnectionFactory. Or are you talking about passing a remote object as a return value in a remote method invocation, as when ConnectionFactory.connect() returns a new Connection? Or are you talking about two threads in the same remote JVM passing a reference between them which refers to a single remote Connection_Stub instance on that remote JVM? Or some other means of "giving"?
Then, after the client releases the object, ref count hits zero. Now, give the object to the second client.
And here's where the method of "giving" is critical. If it's done via Naming.lookup() then yes the reference count went to zero and back, as I already discussed. If it's done using the return value of a remote method, then we have to look at where that method got the object. In the case of my ConnectionFactory's connect() method, it creates a new ConnectionImpl() every time it's called. The ConnectionImpl that the second thread talks to is not the same object that the first thread talked to. The new object has a reference count of 1, while the original object still has a reference count of 0. Or perhaps you mean something else?
When that second thread is finished(caput, dead, no longer with us, visiting Elvis), the ref count will again hit zero, and Unreferenced will again be called. The problem here is that you have to write fairly non-junior-level code to provide functionality that the user didn't ask for.
Well I still don't see how close() would be called again, but if it is, the map.clear() at the end ensures it won't attempt to unlock those records a second time. Doesn't seem too horribly non-junior-level.
Further, from an aesthetic point if view, you've limited the salability of your architecture, because your built in architectural premise is that each client gets a unique connection. If that premise breaks, you're going to be wanting a paddle.
Actually I've been assuming that muliple clients might eventually want to share a connection, and my Connection objects are indeed thread-safe. (Tested with all the same unit tests I ran on the original Data instance - I just had to put in an adapter to catch RemoteExceptions and convert the Connection back to a DB instance. No other special adaptations were necessary.) I normally talk about one client == one connection for simplicity, since that's all that's required for the assignment, and all that most people feel the need to talk about. But I can handle more if you want.
But regardless of how many clients share a connection - if that connection is lost, the server-side ConnectionImpl needs to unlock any associated records, or no one will ever be able to lock them again. (Where "ever" means "until someone restarts the server.") Unless of course we toss out disconnect handling entirely as too much work / not required. Though it's really not that difficult. This discussion may seem long and complex because we've got different competing versions of code in our minds - and can't see how the other person's vaugue description fits into what we were thinking. But looking at the complete code, it's not that hard to follow, IMO.
[ July 11, 2003: Message edited by: Jim Yingst ]
"I'm not back." - Bill Harding, Twister
Originally posted by Jim Yingst:
Ah, see, I knew there was a reason we made you a bartender.
Ah, I see, because you synchronize access to the client's internal hashmap, right?
Yup. Had to do it because of the RMI Spec allowing multiple threads for a single client.
In this design( which I do not advocate, even though I think it's acceptable) Data's lock/unlock pretty much go unused(am I correct here Vlad?). Basically, The LockManager contains metadata about the locks and the classes. It's an explicit redlight/greenlight controller that apart from the connections themselves
1) Unreferenced Interface:
So, In addition to our lockmanagers HashMap, we will have a Hashmap (In our RemoteObject, representing Data). This Hasmap will hold all active locks from the appropriate client. In public void unreferenced() we will go thougt all alements of this HashMap (active lock of the client) and force LockManager to unlock this locks.
- unreferenced() can be called several times.
2) WeakHashMap - is much more flexible, but have to handle problem that as lock is removed from HasMap, all Waiters must be notified.
I don't understand here following: Our RemoteObject still must have own HashMap which have references to HashMap in LockManager: Integer IDs (Record Numbers), meaning that RemoteObject holds a "client state".
The Sun Certified Java Developer Exam with J2SE 5: paper version from Amazon, PDF from Apress, Online reference: Books 24x7 Personal blog
Originally posted by Vlad Rabkin:
Hallo Max,
I don't understand here following: Our RemoteObject still must have own HashMap which have references to HashMap in LockManager: Integer IDs (Record Numbers), meaning that RemoteObject holds a "client state".
Is my assumtion correct?
Vlad
"I'm not back." - Bill Harding, Twister
"I'm not back." - Bill Harding, Twister
A method dispatched by the RMI runtime to a remote object implementation may or may not execute in a separate thread. The RMI runtime makes no guarantees with respect to mapping remote object invocations to threads. Since remote method invocation on the same remote object may execute concurrently, a remote object implementation needs to make sure its implementation is thread-safe.
"I'm not back." - Bill Harding, Twister
And if you did share, given your design constraints, then if GUI client #1 has records 4,5,6 locked, and GUI client #2 has records 7,8,9 locked, then how's client two to know supposed to know who has what? I'm missing something.
the remote client needs to keep track of which locks it has, and what cookies are associated with those locks: especially if you want to use Unreferenced to release them at a later time. That's the role of the second map.
What kind of "special care" must be taken to avoid
"unlimited loops" and assignement of the remoteobject to another client by RMI server?
The Sun Certified Java Developer Exam with J2SE 5: paper version from Amazon, PDF from Apress, Online reference: Books 24x7 Personal blog
Don't get me started about those stupid light bulbs. |