RFR for JDK-8030057: speed up forceLogSnapshot and checkAnnotations
Stuart Marks
stuart.marks at oracle.com
Sat Dec 21 01:58:52 UTC 2013
Hi Tristan,
First of all, these are two completely separate changes. They're sort-of related
in that they involve replacing sleep() loops and polling with different
constructs, but other than that, they're completely independent. As you'll see
from my review comments, there are different issues going on with each as well.
It might be worthwhile to consider splitting these into separate subtasks, one
for each of these changes, so that if one of the changes converges more quickly
(seems like) it can go in independently of the others.
CheckAnnotations
This is a pretty complicated test to begin with, and the changes make it even
more complicated. It adds two threads, a single Lock, two conditions, and stream
subclasses that notify these conditions. It's not clear to me why the low-level
constructs from j.u.concurrent.locks are necessary. At first glance the idea of
having a notifying specialization of a ByteArrayOutputStream is reasonable. I'd
expect the lock and condition to be inside the stream object, though, not
external to it. In addition, the condition being notified is simply "something
has been written". We don't actually know that all the output from the
subprocess has been read. The old version waited around for a while and assumed
that it had collected it all. This of course is racy; but having the notifying
stream is still racy, if it happens that the output is written in multiple chunks.
Note that if we want to get subprocess output as it arrives, there are two
threads hidden inside of RMI's TestLibrary.JavaVM class that collect stdout and
stderr from the subprocess. By default they simply write to whatever stream they
are given. An alternative approach would be to plumb an interface to these
threads and have the output sent directly to the test as it comes in. (I think
one of the other RMI tests does this, though it does it in a particularly clumsy
way that I wouldn't recommend emulating.)
But stepping back a bit, the original test is over-complicated to begin with,
and I'm reluctant to add all these additional mechanisms in this proposed
change, because they add more complexity, and also because I can't quite
convince myself that they're correct or that they even solve the problem that
exists in the original test.
The original test probably needs to be reconceived entirely. The idea is to
activate several objects in several different activation groups (which are child
JVMs of the RMID process), and to make sure that the output from each is
"annotated" with a prefix that indicates which activation group it came from. It
seems to me that a much simpler way to approach the problem is to activate
several objects and have them each emit a unique bit of output. Then, shut
everything down, collect ALL the output, and check to make sure that each
object's unique output is on a line that was annotated properly. This completely
avoids threads, locks, conditions, timing loops, and race conditions.
If you think this alternative approach would be an effective way to proceed, I'd
be happy to help you out with it.
ForceLogSnapshot
This is another fairly complicated test that is partially helped by the addition
of CountDownLatches. Basically, it registers 50 activatable+restartable objects
and 50 activatable (but not restartable) objects with the activation system.
Then it restarts the activation system. The test wants to ensure that the
restartable objects are all restarted, but that *none* of the non-restartable
objects are restarted.
In the first case, having a CountDownLatch that starts at 50 and awaits for them
all to be restarted makes perfect sense. The await() call has a timeout to make
sure we don't wait excessively long for all 50 to be restarted, but the
CountDownLatch allows the test to continue immediately as soon as all 50 have
been restarted. For this case, it's also not necessary to have a retry loop in
waitAllStarted(). We just wait once until the latch is decremented to zero, or
until we time out. It might make sense to raise the timeout here, since there's
a lot of activity; 30 or even 60 seconds might be reasonable. In the normal case
the restarts will all occur quickly, so we'll only wait the full timeout if
there's a failure.
In the second case, we expect zero objects to be restarted, so having a
CountDownLatch starting at 50 doesn't make sense. There is no notion of having
the test proceed immediately as soon as "all" of some events have happened. One
choice here is to wait around for a while and count how many of the
non-restartable objects actually get restarted. An AtomicInteger would be
sufficient for that. An alternative might be to have a CountDownLatch
initialized to 1. If any one of the non-restartable objects is restarted, we
know immediately that the test has failed and we can report that. We might not
care if additional non-restartable objects are restarted after we've observed
the first one having been restarted erroneously. (Then again, we might.) But if
others do, decrementing the CountDownLatch further after it's reached zero does
nothing, so that's OK.
Unfortunately, the successful case needs the test to wait around for a while,
because it's trying to verify a negative, i.e., that no non-restartable objects
were actually restarted. I'm not sure whether that's better done with a
CountDownLatch(1) plus timeout, or a sleep call followed by a check of an
AtomicInteger. I think the latter is a bit easier understand, since the
"latching" behavior in this case is the error, not the normal condition.
Finally, these changes are intended to improve performance. It would be good to
see some measurements to ensure that the performance has actually improved. The
CountDownLatch stuff is an improvement over the original array of booleans, and
it potentially makes the code cleaner, but I think its potential for performance
improvement is limited.
On my system the test run takes about 80 seconds. (It actually does all of the
above twice: once after shutting down rmid, and once after causing the
activation group JVMs to exit ("crash").) The howManyRestarted() loop for
restartable objects sleeps for ten seconds before polling the number of objects
restarted. On average it might wait five seconds too long; given the two test
scenarios, that's a savings of ten seconds. The howManyRestarted() loop for the
objects that *aren't* supposed to be restarted waits for 20 seconds
unconditionally. With two scenarios run, that's 40 seconds right there, fully
half of the time of the test run. (Where does the other half of the time go?)
That time is unavoidable waiting around to make sure the wrong thing (restarting
a non-restartable object) doesn't happen.
I think some of the CountDownLatch stuff will improve the test code, but let's
not make any assumptions about performance.
s'marks
On 12/18/13 7:37 AM, Tristan Yan wrote:
> Hi Everyone
>
> Please help to review the code change for bug JDK-8030057.
>
>
> http://cr.openjdk.java.net/~tyan/JDK-8030057/webrev.00/
> <http://cr.openjdk.java.net/%7Etyan/JDK-8030057/webrev.00/>
> Description:
>
> Performance improvement for two RMI tests
>
> java/rmi/activation/Activatable/forceLogSnapshot
>
> method waitAllStarted is using recursive sleep to poll 50 restartedObject to be
> true, we can use modern CountDownLatch to implement blocking-time wait.
>
> Also suggest shorten waiting time to 5 seconds.
>
> java/rmi/activation/Activatable/checkAnnotations
>
> We can subclass ByteArrayOutputStream which support notification when data was
> written. Also use two thread wait output string and error string to be not null
>
> Thank you
> Tristan
More information about the core-libs-dev
mailing list