RFR(s): 8023541 Race condition in rmid initialization
David Holmes
david.holmes at oracle.com
Thu Jan 30 03:24:31 UTC 2014
This simpler form, with the interruption logic corrected, seems fine to me.
David
On 30/01/2014 12:57 PM, Stuart Marks wrote:
> Hi all, wow, lots of comments on this. Let me see if I can tackle them
> in one message.
>
> Quick aside before I get to the issues: my priorities for this code are
> correctness and maintainability, possibly at the expense of performance.
> In other words I'm willing to make the code more complex so that it's
> correct, but I'm less willing to make it more complex in order to make
> it go faster.
>
> (Tristan, David) Making 'initialized' be volatile. As things stand, as
> David has pointed out (thanks!) it's not necessary for it to be
> volatile. There are other circumstances (see below) where it would be
> necessary to make it volatile, though.
>
> (Alan, Paul) We could convert to double-checked locking and avoid a
> synchronization in the common case, paying only a volatile read.
> Something like,
>
> volatile boolean initialized = false;
> ...
> private void awaitInitialized() {
> if (!initialized) {
> synchronized (this) {
> try {
> while (!initialized) {
> wait();
> } catch (InterruptedException ie) {
> Thread.currentThread().interrupt();
> }
> }
> }
> }
>
> I *think* that's right. (Is it?) I don't know whether this performs any
> better, or if it does, whether it's worth the additional complexity. I
> had to squint at this for a while to convince myself it's correct.
>
> I am fairly sure this is not a performance-critical area of code.
> (Famous last words, I know.) The other threads that can be active here
> are handling remote calls, so they're already doing network I/O,
> unmarshalling, and dispatching to the RMI thread pool. Compared to this,
> the incremental cost of a synchronization block seems inconsequential. I
> don't have much intuition about how much we'd save by substituting a
> volatile read for a full synchronization in the common case, but given
> that this is within the context of a fairly expensive operation, it
> doesn't seem like it's worth it to me.
>
> (Alan, Paul) Calling awaitInitialized isn't strictly necessary anywhere
> except for the equals(NAME) case of lookup(). Yes, that's right. I added
> it everywhere because of a possibly misguided sense of completeness and
> consistency. One could essentially redefine awaitInitialized() to
> protect just the systemStub field, not the "entire" object, whose only
> state is that field anyway. Also, see below regarding renaming this method.
>
> (Alan) Use systemStub == null as the condition instead of !initialized.
> I considered at first this but it got confusing really fast. Take a look:
>
> private final ActivationSystem systemStub;
>
> SystemRegistryImpl(..., systemStub) {
> ...
> this.systemStub = systemStub;
> notifyAll();
> ...
> }
>
> private synchronized void awaitInitialized() {
> ...
> while (systemStub == null) {
> wait();
> }
> ...
> }
>
> We rely on systemStub to be initialized at object creation time (before
> construction!) to its default value of null. I think this is right. The
> constructor then initializes the blank final to non-null and notifies.
>
> Then, awaitInitialized seems straightforward until you see that the
> condition is waiting for the value of a final variable to change! JLS
> sec 17.5 [1] allows all sorts of optimizations for final fields, but
> they all are qualified with what is essentially a safe publication
> requirement on the reference:
>
> An object is considered to be completely initialized when its
> constructor
> finishes. A thread that can only see a reference to an object after
> that
> object has been completely initialized is guaranteed to see the
> correctly
> initialized values for that object's final fields.
>
> [1] http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5
>
> Unfortunately this code *unsafely* publishes a reference to 'this' which
> is the root of this whole problem. Under these circumstances I'd prefer
> to be really conservative about the code here. I can't quite convince
> myself that a condition loop waiting for a final field to change value
> is safe. That's why I added a separate field.
>
> I can see removing the boolean and using systemStub == null as the
> condition making things simpler, since there are fewer variables to
> reason about, but only if systemStub is made non-final. Making it
> non-final prevents any unwanted optimizations resulting from it being
> final. Having it be final doesn't add much anyway. I'll also move all
> accesses to systemStub within synchronized blocks so there is no
> question about visibility. (As a result, awaitInitialized now gets
> turned into getSystemStub.)
>
> (Peter) Should the interrupt break out of the loop even though the
> condition isn't satisfied? This is a good point. Usually I think of
> interrupt as wanting to avoid waiting indefinitely for the condition to
> become true (which is the point of supporting interruption) but in this
> case the condition will always occur in a timely fashion. So I'll accept
> the suggestion to save the interrupt state and let the condition loop
> terminate.
>
> Updated webrev here:
>
> http://cr.openjdk.java.net/~smarks/reviews/8023541/webrev.1/
>
> Thanks,
>
> s'marks
More information about the core-libs-dev
mailing list