On Wed, 18 Sep 2024 05:53:05 GMT, Thomas Stuefe <stu...@openjdk.org> wrote:

>> Hi @roberttoyonaga 
>> 
>> (Pinging @afshin-zafari and @jdksjolen)
>> 
>> First off, it is good that you ran into this since it highlights a possible 
>> real deadlock. Both locks are quite coarse. I cannot think from the top of 
>> my head of a scenario where we hold the tty lock and need NMT locking, but 
>> would not bet money on it. E.g. I bet we call malloc under ttylock (if only 
>> as a side effect of calling functions that return resource area). Malloc 
>> does not need locking; but its brittle and small changes can cause deadlocks 
>> (e.g. folks were thinking about moving ResourceArea memory to mmap, which 
>> needs NMT locks for tracking).
>> 
>>> Hi @tstuefe, it looks like calling 
>>> [`os::print_memory_mappings`](https://github.com/openjdk/jdk/blob/master/src/hotspot/os/windows/os_windows.cpp#L3785)
>>>  from the windows implementation of `os::pd_release_memory` is causing the 
>>> rank conflict between `tty_lock` and `NMT_lock`. This is getting called 
>>> from `os::release_memory` [**after** we've already acquired the lock for 
>>> NMT](https://github.com/openjdk/jdk/blob/master/src/hotspot/share/runtime/os.cpp#L2185-L2189).
>>> 
>>> gtest `release_bad_ranges` --> `os::release_memory` and acquire `NMT_lock` 
>>> --> `os::pd_release_memory` --> `os::print_memory_mappings` and acquire 
>>> `tty_lock`
>>> 
>> 
>> Ah thank you. I think I added that printing ages ago. I should not have used 
>> tty. The immediate solution to get you going may be to just change this from 
>> tty to `fileStream fs(stderr);`
>> 
>>> Is there a reason we cast a wider net and lock from outside of NMT code in 
>>> `os::release_memory`, `os::uncommit_memory`, and `os::unmap_memory`? By 
>>> contrast, in other functions such as `os::commit_memory` and 
>>> `os::reserve_memory` we lock inside of `MemTracker` instead. For example, 
>>> `pd_release_memory` is protected by the NMT lock, but `pd_reserve_memory` 
>>> is not. Is the lock meant to synchronize the actual memory operations as 
>>> well as NMT recording in some cases? If not, then maybe it makes sense to 
>>> delay and move the lock acquisition inside `MemTracker` for consistency and 
>>> to reduce surface area for lock conflicts.
>> 
>> The problem is that both tty_lock and the NMT lock are quite coarse. I 
>> always maintained that tty_lock is too coarse - we have many places where 
>> there is a ttyLocker somewhere at the start of a printing function, then the 
>> function goes and prints tons of infos it *retrieves under lock protection*. 
>> The better way to solve this is to assemble the text outside of lock 
>> protection, then only do the printing...
>
>> Hi @tstuefe,
>> 
>> > Ah thank you. I think I added that printing ages ago. I should not have 
>> > used tty. The immediate solution to get you going may be to just change 
>> > this from tty to fileStream fs(stderr);
>> 
>> Thanks! I adopted your suggestion and replaced tty. The lock conflict is 
>> gone now.
>> 
>> > Ensuring that we feed NMT the tracking information in the order they 
>> > happened. This is only relevant for mmap accounting, since only there do 
>> > we need to track address ranges. For malloc accounting, we just 
>> > increase/decrease counters, and there, the order of operations does not 
>> > matter, just that we do it atomically.
>> > ...
>> > A pragmatic solution would be to move the locking down the stack. 
>> > Currently, we lock in the generic layer around { pd_release_memory() + 
>> > MemTracker::record }. But we only need to lock around the OS allocation 
>> > function (mmap/munmap/VirtualAlloc etc). So we could move the locks 
>> > downward to the OS specific layer where the actual allocations happen in 
>> > the OS code, e.g. just lock around { munmap() + MemTracker::record }.
>> 
>> Ok I see. So the locking should indeed not happen inside of `MemTracker`. 
>> Yes, maybe moving the locking down the stack into platform specific code 
>> would be a good task for the future. However, if the intention is to feed 
>> NMT the tracking info in the order it happened, why are the operations on 
>> memory in `os::commit_memory` and `os::reserve_memory` not protected by the 
>> lock? In those, we delay and lock inside `MemTracker` instead.
> 
> That looks like an error that should be fixed.

Hi @tstuefe, @roberttoyonaga 

We saw some test failures in non-generational ZGC due to this change. That GC 
is removed now, so there's no worry there. I did however look at all of our 
remaining usages of `ThreadCritical` and saw that there are thrre left that are 
related to NMT:


src/hotspot/share/nmt/nmtUsage.cpp
56     ThreadCritical tc;

src/hotspot/share/nmt/mallocTracker.cpp
65     // Use ThreadCritical to make sure that mtChunks don't get deallocated 
while the
68     ThreadCritical tc;


src/hotspot/share/memory/arena.cpp
178      ThreadCritical tc;  // Free chunks under TC lock so that NMT 
adjustment is stable.


I am not that familiar with this code. Is leaving these as they are 
intentional, or have we introduced a potential bug?

Thank you.

-------------

PR Comment: https://git.openjdk.org/jdk/pull/20852#issuecomment-2446981854

Reply via email to