On 8/31/15 11:11 PM, David Holmes wrote:
Hi Dan,
On 1/09/2015 2:58 AM, Daniel D. Daugherty wrote:
Hi David,
Replies embedded below...
Likewise ... but with some trimming (I'm getting RSI hitting page-down).
Yup. I use an Apple MagicMouse and I'm getting tired of
the scrolling drag... :-)
On 8/30/15 7:30 PM, David Holmes wrote:
On 29/08/2015 2:27 AM, Daniel D. Daugherty wrote:
On 8/28/15 8:45 AM, Tom Benson wrote:
For my shutdown use, we are transitioning from '1' -> '0' and
we need that to be seen proactively so:
Nit: OrderAccess "barriers" enforce ordering constraints but don't in
general provide any guarantees about visibility - ie they are not
necessarily "flushes". So while it may be true on some platforms by
virtue of the underlying barrier mechanism, in general they don't
change when a write becomes visible and so there is nothing
"proactive" about them.
My apologies for being sloppy with my wording.
What I'm trying to do is put up a barrier so that once the
deleting thread has changed the flag from '1' -> '0' another
thread trying to read the flag won't see the '1' when it
should see the '0'. In order words, I don't want the other
thread's read of the flag to float above the setting of
the flag to '0'.
That still suggest to me a notion that the barrier somehow forces
visibility and that after the barrier_store returns all other threads
are now guaranteed to see the value '1' - which is not the case. All
the barriers here do is ensure a global observability ordering: if
another thread sees an action that happened after the barrier, then it
must see the action that happened before the barrier.
I'm not trying to say that visibility is forced. I was shooting
for something like "happens before" and "happens after", but I
didn't use those terms...
More below ...
OrderAccess::release_store(&_has_PerfData, 0);
OrderAccess::storeload();
I agree with Tom that the release is unnecessary - though harmless.
I tried to switch the code to:
OrderAccess::store(&_has_PerfData, 0)
OrderAccess::storeload();
but that wouldn't compile on Solaris X64:
Error: static OrderAccess::store(volatile int*, int) is not accessible
from static PerfDataManager::destroy().
Strange - either a bug or an issue with including the .inline.hpp file.
Right. Not worth chasing since I don't really need it.
But then it occurred to me that I was trying too hard
to use OrderAccess functions...
The real ordering constraint here is that we preserve:
_has_PerfData = 0;
<do actual deallocation>
I think I can switch to a straight "_has_PerfData = 0;" here and
not use either OrderAccess::release_store() or OrderAccess::store().
Yes. Particularly as OrderAccess::store is just a plain store for
32-bit types.
I also reread OrderAccess.hpp again and convinced myself that
an "OrderAccess::fence()" call is the only way to be sure here.
I'm pretty sure that achieves 'storeload|storestore' barrier
semantics... Yes, I have a headache again... :-)
Yes it does, as well as loadLoad and loadStore.
for which a storeload|storestore barrier after the write seems most
appropriate. Though with the insertion of the sleep after the write
there won't be any reordering anyway so explicit barriers seem
redundant.
I didn't think that os::naked_short_sleep() would do anything
to keep another thread's read from floating above the
"_has_PerfData = 0;". What did I miss?
This is somewhat of a side-discussion but ...
First your notion of floating reads in other threads is somewhat
troubling to me. I kind of get what you mean but ...
Now for the sleep. As always with ordering constraints there are two
sides: the writer and the reader. Here we are dealing with the writer.
The writer is executing:
_has_PerfData = 0;
for (int index = 0; index < _all->length(); index++) {
PerfData* p = _all->at(index);
delete p;
}
for the sake of discussion lets assume there's only one PerfData
object and that deleting it amounts to setting an internal field to
NULL. So our code becomes:
_has_PerfData = 0;
data->ref = NULL;
The ordering constraint is that the above two statements happen in the
order written - so we don't delete the data while the data is claimed
to be valid. That requires both that the compiler not reorder them,
nor the hardware. If we did:
_has_PerfData = 0;
membar storeload|storeStore;
data->ref = NULL;
then we achieve that goal. Now consider if we instead have:
_has_PerfData = 0;
os::naked_short_sleep(1);
data->ref = NULL;
The sleep call, leads to a library call and a syscall and ultimately
likely a context switch as the current thread gets descheduled for
1ms. In terms of machine instructions the distance between the initial
write of _has_PerfData and the load of 'data' and so the store to data
is huge. Can those stores be reordered by the hardware? Theoretically
yes, but in practice I say not at all likely: the separation is too
large and there are likely additional barriers already within the code
that will do the sleep. Can the stores be reordered by the compiler?
Again theoretically yes, but in practice for a compiler to reorder
across the sleep requires intimate knowledge of everything that
happens within the sleep - no compiler we currently use would even
attempt to try and figure that out. So the sleep, while not a
guarantee, is most likely to act to prevent the undesired reordering.
Is that lack of guarantee an issue? No - because we know we already
have a race condition. No matter what we do with this section of code,
in terms of barriers, we can't avoid the race.
All the way to this point, I was expecting you to say leave the
barrier in...
Hence my view that adding explicit barriers together with the sleep
serves no practical purpose here. It would be different if a barrier
forced the write to become visible to all other threads - but that is
not guaranteed by OrderAccess.
Not the conclusion I was expecting based on the last couple of
paragraphs. :-)
My preference is to leave the barrier in for these reasons:
- communicates intent to any future maintainer; we could also say that
this might communicate my silliness at continuing with a lock free
algorithm when everyone know that I hate them.
- does not make assumptions about os::naked_short_sleep(1); it might
be turned into an in-line function someday that the compiler can
digest and optimize around...
- does no harm; I think that's a paraphrase of what you said in the
round 0 code review :-)
Now lets return to the reader side of this, somewhere we have code
that is effectively doing something like:
if (_has_PerfData == 1)
cur_data = *data->ref;
This is where we see the race: the value of the flag can change the
instant after we read it and so we can access data->ref after it is
NULL. Going back to the sleep, the purpose of the sleep is to give
threads that have seen _has_PerfData==1 a chance to get past the data
access before the PerfData object is deleted.
In the monitor subsystem, the check is little different:
if (data != NULL && _has_PerfData == 1) {
cur_data = *data->ref
}
so the read of data->ref can't float above the if-statement. It looks
like most (if not all) of the PerfData usage relies on NULL checks of
the PerfData object as the trigger to determine whether to call into
the PerfData object.
In terms of ordering constraints we don't want the read of data->ref
to be moved/float ahead of the read of _has_PerfData, so technically
we need a loadload barrier to prevent that:
if (_has_PerfData == 1) {
membar loadload;
cur_data = *data->ref;
}
From a hardware perspective I don't think a speculative load can cause
a problem - if we read data->ref early and see NULL then we "must" see
_has_perfData==0 (due to ordering at the writer) and so the code is
skipped. And I think similarly for compiler reorderings (though my
head is hurting now too :)). But in either case it doesn't really
matter if this reasoning is wrong because even if the code is kept in
the required order the race still exists and we can't tell exactly how
things came about. So adding the loadload would achieve little, if
anything.
Agreed that a loadload would achieve little, if anything here.
Now feel free to ignore all that, go get a coffee and move on to my
reply to your next webrev. :)
Will do. Needed a coffee for this one also...
Dan
Cheers,
David
My current plan:
- switch from "OrderAccess::release_store(&_has_PerfData, 0);"
to "_has_PerfData = 0;"
- keep "OrderAccess::fence();"
- retest and send out for another round of review
Dan
Cheers,
David
-----
which is modeled after _owner field transitions from non-zeo
-> NULL in ObjectMonitor.cpp
Also, I think the comment above that release_store() could be
clarified. It is fine as is if you're familiar with this bug report
and discussion, but... I think it should explicitly say there is
still a very small window for the lack of true synchronization to
cause a failure. And perhaps that the release_store() (or
store/release()) is not half of an acquire/release pair.
Here's the existing comment:
286 // Clear the flag before we free the PerfData counters.
Thus
begins
287 // the race between this thread and another thread that
has just
288 // queried PerfDataManager::has_PerfData() and gotten back
'true'.
289 // The hope is that the other thread will finish its
PerfData
290 // manipulation before we free the memory. The two
alternatives
291 // are 1) leak the PerfData memory or 2) do some form of
ordered
292 // access before every PerfData operation.
I think it pretty clearly states that there is still a race here.
And I think that option 2 covers that we're not doing completely
safe ordered access. I'm not sure how to make this comment more
clear, but if you have specific suggestions...
Dan
Tom