RFR: 8130212: Thread::current() might access freed memory on Solaris

Thomas Stüfe thomas.stuefe at gmail.com
Tue Aug 4 09:32:11 UTC 2015


Hi David,

On Mon, Aug 3, 2015 at 10:22 PM, David Holmes <david.holmes at oracle.com>
wrote:

> Hi Thomas,
>
> On 4/08/2015 1:38 AM, Thomas Stüfe wrote:
>
>> Hi David,
>>
>> we added compiler-level TLS (__thread) for Linux a while ago to do
>> exactly what you are doing, but then had to remove it again. Bugs in the
>> glibc caused memory leaks - basically it looked like glibc was not
>> cleaning TLS related structures. Leak was small but it added up over time.
>>
>> I know you implemented this for Solaris. Just thought I give you a
>> warning, maybe this is something to keep in mind.
>>
>
> Thanks for the heads-up! Linux et al are next on the list. I'll put
> together a simple thread creation test and see if the memory use changes
> over time.
>
>
Sounds good. Unfortunately this was a gcc/glibc bug and therefore you may
not see the bug on every linux system.

I think this may have been the bug:

https://sourceware.org/bugzilla/show_bug.cgi?id=12650

Kind Regards, Thomas

David
>
> Thanks, Thomas
>>
>>
>> On Wed, Jul 29, 2015 at 7:46 AM, David Holmes <david.holmes at oracle.com
>> <mailto:david.holmes at oracle.com>> wrote:
>>
>>     Summary: replace complex custom code for maintaining
>>     ThreadLocalStorage with compiler supported thread-local variables
>>     (Solaris only)
>>
>>     This is a non-public bug so let me explain with some background, the
>>     bug, and then the fix - which involves lots of complex-code deletion
>>     and addition of some very simple code. :)
>>
>>     webrev: http://cr.openjdk.java.net/~dholmes/8130212/webrev/
>>
>>     In various parts of the runtime and in compiler generated code we
>>     need to get a reference to the (VM-level) Thread* of the currently
>>     executing thread. This is what Thread::current() returns. For
>>     performance reasons we also have a fast-path on 64-bit where the
>>     Thread* is stashed away in a register (g7 on sparc, r15 on x64).
>>
>>     So Thread::current() is actually a slow-path mechanism and it
>>     delegates to ThreadLocalStorage::thread().
>>
>>     On some systems ThreadLocalStorage::thread utilizes a caching
>>     mechanism to try and speed up access to the current thread.
>>     Otherwise it calls into yet another "slow" path which uses the
>>     available platform thread-specific-storage APIs.
>>
>>     Compiled code also has a slow-path get_thread() method which uses
>>     assembly code to invoke the same platform thread-specific-storage
>>     APIs (in some cases - on sparc it simply calls
>>     ThreadLocalStorage::thread()).
>>
>>     On Solaris 64-bit (which is all we support today) there is a simple
>>     1-level thread cache which is an array of Thread*. If a thread
>>     doesn't find itself in the slot for the hash of its id it inserts
>>     itself there. As a thread terminates it clears out its
>>     ThreadLocalStorage values including any cached reference.
>>
>>     The bug is that we have potential for a read-after-free error due to
>>     this code:
>>
>>        46   uintptr_t raw = pd_raw_thread_id();
>>        47   int ix = pd_cache_index(raw);  // hashes id
>>        48   Thread* candidate = ThreadLocalStorage::_get_thread_cache[ix];
>>        49   if (candidate->self_raw_id() == raw) {
>>        50     // hit
>>        51     return candidate;
>>        52   } else {
>>        53     return
>>     ThreadLocalStorage::get_thread_via_cache_slowly(raw, ix);
>>        54   }
>>
>>     The problem is that the value read as candidate could be a thread
>>     that (after line 48) terminated and was freed. But line #49 then
>>     reads the raw id of that thread, which is then a read-after-free -
>>     which is a "Bad Thing (TM)".
>>
>>     There's no simple fix for the caching code - you would need a
>>     completely different approach (or synchronization that would nullify
>>     the whole point of the cache).
>>
>>     Now all this ThreadLocalStorage code is pretty old and was put in
>>     place to deal with inadequacies of the system provided
>>     thread-specific-storage API. In fact on Solaris we even by-pass the
>>     public API (thr_getspecific/thr_setspecific) when we can and
>>     implement our own version using lower-level APIs available in the
>>     T1/T2 threading libraries!
>>
>>     In mid-2015 things have changed considerably and we have reliable
>>     and performant support for thread-local variables at the C+
>>     language-level. So the way to maintain the current thread is simply
>>     using:
>>
>>       // Declaration of thread-local variable
>>       static __thread Thread * _thr_current
>>
>>       inline Thread* ThreadLocalStorage::thread()  {
>>         return _thr_current;
>>       }
>>
>>       inline void ThreadLocalStorage::set_thread(Thread* thread) {
>>         _thr_current = thread;
>>       }
>>
>>     And all the complex ThreadLocalStorage code with caching etc all
>>     vanishes!
>>
>>     For my next trick I plan to try and remove the ThreadLocalStorage
>>     class completely by using language-based thread-locals on all
>>     platforms. But for now this is just Solaris and so we still need the
>>     ThreadLocalStorage API. However a lot of that API is not needed any
>>     more on Solaris so I have excluded it from there in the shared code
>>     (ifndef SOLARIS). But to avoid changing other shared-code callsites
>>     of ThreadLocalStorage I've kept part of the API with trivial
>>     implementations on Solaris.
>>
>>     Testing: JPRT
>>               All hotspot regression tests
>>
>>     I'm happy to run more tests but the nice thing about such low-level
>>     code is that if it is broken, it is always broken :) Every use of
>>     Thread::current or MacroAssembler::get_thread now hits this code.
>>
>>     Performance: I've run a basic set of benchmarks that is readily
>>     available to me on our performance testing system. The best way to
>>     describe the result is neutral. There are some slight wins, and some
>>     slight losses, with most showing no statistical difference. And even
>>     the "wins" and "losses" are within the natural variations of the
>>     benchmarks. So a lot of complex code has been replaced by simple
>>     code and we haven't lost any observable performance - which seems
>>     like a win to me.
>>
>>     Also product mode x64 libjvm.so has shrunk by 921KB - which is a
>>     little surprising but very nice.
>>
>>     Thanks,
>>     David
>>
>>
>>


More information about the hotspot-runtime-dev mailing list