RFR (S) 8235962: os::current_thread_id() is not signal safe on macOS
David Holmes
david.holmes at oracle.com
Thu Jan 16 22:07:15 UTC 2020
Hi Gerard,
Thank you for the very detailed discussion on mach ports! Please add all
of that to the bug report (plus any references/links to where you found
the info).
I'm good with the current fix to "do the right thing" and keep the
reference count in sync.
Thanks,
David
On 17/01/2020 5:14 am, gerard ziemski wrote:
> Hi David,
>
> Thank you very much for the review.
>
>
> On 1/13/20 8:49 PM, David Holmes wrote:
>> Hi Gerard,
>>
>> Thanks for tackling this. All extremely confusing as usual :) far too
>> many different notions of "thread identity".
>>
>> On 14/01/2020 4:43 am, gerard ziemski wrote:
>>> hi all,
>>>
>>> Before JDK-8022808 we used to call "mach_thread_self()" to obtain a
>>> thread id, but back then we forgot to release the returned mach port,
>>> which would make a long running java process (like Kitchensink)
>>> eventually run out of resources and hang it.
>>>
>>> To solve that, in JDK-8022808, we switched to using
>>> "pthread_mach_thread_np(pthread_self())", which returns a static mach
>>> port, which does not need to be released.
>>> However, the "pthread_mach_thread_np()" API, which we started using
>>> is specifically knownn as not async-signal-safe API and that might
>>> cause an issue, because we use it to generate thread ids in
>>> hs_err_pid.log crash log files in our signal handler, which requires
>>> that only async-signal-safe API be used.
>>>
>>> Our crash handler is best effort approach, and to this extend I'd
>>> like to propose a fix, which reverts back to using
>>> "mach_thread_self()", which I must admit is unknown whether it itself
>>> is async-signal-safe, though it's not part of pthread API, like
>>> "pthread_mach_thread_np()" which itself is not part of
>>> async-signal-safe APIs (IEEE Std 1003.1, 2016). Additionally, the
>>> reporter of the issue suggest that it solves the actual issue with
>>> using "pthread_mach_thread_np()" for them in their project, so let's
>>> benefit from their experience.
>>
>> Okay that seems reasonable.
>>
>>> In this fix we revert back to using "mach_thread_self()" API, but
>>> make sure to release the mach port, and use MACH_PORT_VALID() macro
>>> to verify the validity of mach port.
>>
>> I'm unclear on this aspect. Now that we are releasing it doesn't that
>> mean that the port value could be reused and applied to a different
>> thread? It isn't at all clear to me what a "mach port" actually means
>> and what releasing it (or failing to) means.
>
> I've done more research and testing and here is how I understand our
> usage of mach ports:
>
> Mach ports are a resource a code needs in order to talk to mach thread
> (or task), though we simply use its value as thread id. They (i.e. the
> mach kernel) use reference counting to keep track of whether they need
> to exist, and get automatically destroyed when the ref count reaches 0.
>
> They can be created via APIs like mach_port_allocate(), and deleted
> directly via mach_port_destroy(), or indirectly via
> mach_port_deallocate(). The difference between these two (inaptly named
> imho) APIs is that while mach_port_destroy() will outright delete the
> port, mach_port_deallocate() will only decrease the ref count and will
> call mach_port_destroy() implicitly only if it reaches 0.
>
> If the user obtains mach port via mach_thread_self(), the ref count of
> that thread will increase, which needs to be matched by a call to
> mach_port_deallocate(). However, one can not delete such special mach
> port via mach_port_destroy() (only user own created ports can de deleted).
>
> Back in 2013 a call to mach_thread_self() would create a brand new mach
> port, so if you did not call mach_port_deallocate() after being done
> with it, the resource would be leaked. I believe that was actually a bug
> in early OS X, or possibly a bad API design, which affected many other
> users, not just us (i.e. Google's Chrome app for Mac). The confusion was
> primary due to the fact that mach_task_self() would not create a new
> port, so developers expected the same from mach_thread_self(). There are
> only about 300,000 ports available to a process (verified via a native
> sample that keeps creating new ports via mach_port_allocate() API), so
> eventually long lived apps, like our kitchensink, would run out of them.
>
> We could have fixed our issue by matching mach_thread_self() with
> mach_port_deallocate() even back then, but instead we chose to use
> pthread_mach_thread_np() API, which is documented to return thread's
> static mach port, which does not need to be returned via
> mach_port_deallocate(). I think that solution just bypassed the mystery
> of mach ports and when and if we need to delete them, but also was more
> efficient as it did not create a new port.
>
> Today, mach_thread_self() on OS X behaves differently, to how it did
> back then - it no longer creates new mach ports, and so in most cases
> the matching call to mach_port_deallocate() could be skipped, as the
> counter the mach kernel uses for ref count is a very big number. Though
> it would eventually overflow, and the kernel then would probably delete
> the mach port with tragic results. I tried to see if I could make that
> happen, but no luck in 24 hours of a test case continuously asking for
> mach_thread_self() every 10 micro seconds.
>
> So today we could have simply reverted to how the code used to be, and
> not even bother to call mach_port_deallocate() after mach_thread_self(),
> though theoretically that could overflow the ref count eventually.
>
> The proposed fix, reverts back to using mach_thread_self() (which
> increases the ref count) because it's probably signal-async-safe, unlike
> pthread_mach_thread_np(), AND calls mach_port_deallocate() (which
> decreases the refcount)
>
> Someone please correct me if I got anything wrong here.
>
>
>>
>>> bug: https://bugs.openjdk.java.net/browse/JDK-8235962
>>> webrev: http://cr.openjdk.java.net/~gziemski/8235962_rev1/index.html
>>
>> I found the refactoring around _unique_thread_id a bit hard to follow.
>> Your new set_unique_thread_id needs to use "#ifdef __APPLE__"
>> otherwise it won't compile on other BSD systems.
>
> I will fix it, test it and post a new webrev, assuming everyone is
> satisfied with my answers above.
>
>
>>
>> Thanks,
>> David
>> -----
>>
>>> Tested with Mach5 tier1,2,3,4,5 and Kitchensink24 and
>>> Kitchensink24Stress locally on Mac laptop over 2 days.
>>>
>>>
>>> cheers
>
More information about the hotspot-runtime-dev
mailing list