-
Notifications
You must be signed in to change notification settings - Fork 6.2k
8277486: NMT: Cleanup ThreadStackTracker #6504
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
8277486: NMT: Cleanup ThreadStackTracker #6504
Conversation
|
👋 Welcome back zgu! A progress list of the required criteria for merging this PR into |
|
@zhengyu123 The following label will be automatically applied to this pull request:
When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing list. If you would like to change these labels, use the /label pull request command. |
Use of atomic updates with MO_RELAXED will not affect whether an Atomic::load sees a stale value or not. |
I thought we settled this in PR 6065, as you stated:
If you are referring the inaccurate read of the counter w/o the lock, then yes, it is a general problem with NMT, where various counters are not strictly sync'd to avoid taking locks. |
|
Sorry @zhengyu123 but every time I re-read the C++ memory model stuff I get a different take on exactly what different MO's provide. The write-read coherence in the modification order requires that the write happens-before the read. And the inter-thread happens-before ordering then states the five conditions, any of which, establish the happens-before ordering. But I do not see any of those conditions being met for the Atomic::load that does the read and the Atomic::inc or dec that last updated the counter. They do not synchronize-with each other (unlike C++ atomics), there is no dependency order and no obvious transitive happens-before or synchronizes-with ordering. The atomic load is AFAICS no different from a memory ordering or visibility perspective, to a plain load as it takes no MO parameter and is not specified to provide any specific MO semantics - unlike in C++ where the default is seq-cst. So unless the Atomic::inc/dec provide some form of full memory synchronization barrier, the load need not see the most recent write. |
| assert_post_init(); | ||
| if (tracking_level() < NMT_summary) return; | ||
| if (addr != NULL) { | ||
| ThreadCritical tc; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would this not require, to prevent the assert in (new|delete)_thread_stack, to repeat the tracking level check inside the ThreadCritical scope? Since the tracking level could have changed between lines 268 and 270?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With/without ThreadCritical lock, the assertion in (new|delete)_thread_stack is unreliable, because tracking level is undergraded without ThreadCritical lock, therefore, there is no synchronize-with relationship. So to check tracking level inside (new|delete)_thread_stack, it can only rely on
Early return at line #268 avoids very expensive ThreadCritcal lock, given NMT is off by default and also tracking level can only be downgraded monotonically.
Anyway, I believe current shutdown logic for virtual memory tracking is racy, filed JDK-8277788.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also would like one less use of ThreadCritical lock, since it's used for malloc if needed for resource allocation.
| if (_simple_thread_stacks == NULL) { | ||
| assert(MemTracker::tracking_level() < NMT_summary, "Must be"); | ||
| return false; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I looked at MemTracker::transition_to, the downgrade path:
} else if (current_level > level) {
// Downgrade tracking level, we want to lower the tracking level first
_tracking_level = level;
// Make _tracking_level visible immediately.
OrderAccess::fence();
VirtualMemoryTracker::transition(current_level, level);
MallocTracker::transition(current_level, level);
ThreadStackTracker::transition(current_level, level);
Is the comment about "immediately" correct? I would have assumed OrderAccess::fence() has not have anything to do with timing, just with the order the writes are perceptible from outside?
So, concurrent threads may observe tracking level == minimal but the pointers to the infrastructure are still there?
I see that you do not guard MemTracker::transition_to with ThreadCritical. You do guard teardown inside ThreadStackTracker::transition and VirtualMemoryTracker::transition but it looks like teardown in MallocTracker::transition is unguarded by ThreadCritical at least.
Just a proposal, would guarding upstairs in MemTracker::transition_to not be clearer and safer, especially if it includes changing the tracking level?
Alternatively, do we even have to delete all those structures on shutdown? They are not that costly. We shut down either manually - nobody cares then - or if we encounter native OOM. In the latter case, those detail structures are arguably important since they contain data that may help analyze the OOM. The malloc site table, for instance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To add to that, I always wondered why we even expose shutdown via jcmd... especially since this is a one-way operation, so its use is limited.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ThreadStackTracker could be tracked as malloc memory, and yes, MallocTracker::transition is not guarded by ThreadCritical, because it is too expensive on other operations.
Yes, I believe we have yet seen any bug reported, solely due to limited use of shutdown function :-)
| static void new_thread_stack(void* base, size_t size, const NativeCallStack& stack); | ||
| static void delete_thread_stack(void* base, size_t size); | ||
|
|
||
| static bool track_as_vm() { return AIX_ONLY(false) NOT_AIX(true); } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unrelated to your patch: I wonder whether this could be controlled as a diagnostic switch, to be able to test both implementations on normal platforms.
|
@tstuefe :
fence() is supposed to be a sledgehammer that forces a consistent view of memory for all threads/processors. Any writes before a fence should be made visible to all other processors before the fence returns, such that loads of any of this locations will see the value written. It is "immediate" in that sense. |
@dholmes-ora I am with you. In fact, our internal discussion started when I questioned assertions in NMT, e.g. From another perspective, in PR 6065, I mentioned that atomic r-m-w update at single location is total order, guaranteed by coherence protocol (AFAICT, all supported platforms are coherence based multiple processor systems). For a processor to perform atomic r-m-w operation, it has to acquire corresponding cache line in exclusive state. Therefore, other processors who happen to own the cache line, have to invalidate the cache line before atomic operation can be performed. After atomic r-m-w operation, when other thread tries to load the value, coherence protocol ensures that it sees new value. |
|
Hi Zhengyu, Could we not simply remove the shutdown logic from NMT altogether? I think its not needed and makes things more complex than necessary. We use shutdown in two cases:
Note how rare Point (2) is: It depends on MST fill size, which depends on NMT stack depth. With the default stack depth of 4, atm we never even reach ~1000 individual call sites. I once did an experiment with stack depth=16 and reached about 10000 call sites. For (2.1), the chance of one of the few MST node mallocs to run into OOM is astronomically low. Especially since if MST is that full, we will see tons of user mallocs, and one of those will certainly hit OOM first. And (2.2) cannot happen at all: even with an MST size of 1, we could accommodate 65k entries, a lot more than even the 10k I reached with stack depth = 16. But of course, MST size is not 1. In fact, I think we should even assert against MST overflow, not handle it gracefully. So, let us remove shutdown. It would simplify NMT quite a bit, remove quite a bit of code, testing too. And the tracking level would be constant throughout VM life, making concurrency much simpler. Cheers, Thomas |
Hi Thomas, I am with you, I don't see it is very useful. I believe it is a remnant of old implementation, that NMT could consume excessive amount of memory and needed to shutdown itself to ensure health of JVM. How we proceed to remove the command? need CSR? Thanks, -Zhengyu |
|
The last comments attracted my attention. It does seem like the shutdown logic was because of the 1.0 implementation and shouldn't be needed anymore. If you write a CSR to deprecated it for 18 (quickly), you could remove it in a couple of weeks. |
There are several issues with
ThreadStackTracker.Following assertion:
assert(MemTracker::tracking_level() >= NMT_summary, "Must be");in
ThreadStackTracker::record/release_thread_stack()is unreliable. The full fence after downgrading tracking level is not sufficient to avoid the racy.NMT tracking level is downgraded without
ThreadCriticallock held. But, it does requireThreadCriticallock to be held when it actually downgrade internal tracking data structure, so checking internal state is reliable to determine current tracking state.Add assertion to ensure correct tracking state
_thread_counteris updated withThreadCriticallock, but is read without the lock. Change to atomic update to ensure reader will not read stale value.NMT events are relative rare. So far, I have yet seen (1) assertion failure but add new test may help to spot such problem.
Progress
Issue
Reviewing
Using
gitCheckout this PR locally:
$ git fetch https://git.openjdk.java.net/jdk pull/6504/head:pull/6504$ git checkout pull/6504Update a local copy of the PR:
$ git checkout pull/6504$ git pull https://git.openjdk.java.net/jdk pull/6504/headUsing Skara CLI tools
Checkout this PR locally:
$ git pr checkout 6504View PR using the GUI difftool:
$ git pr show -t 6504Using diff file
Download this PR as a diff file:
https://git.openjdk.java.net/jdk/pull/6504.diff