Changeset 286573 in webkit


Ignore:
Timestamp:
Dec 6, 2021, 2:53:46 PM (4 years ago)
Author:
ysuzuki@apple.com
Message:

[libpas] Set pthread_setspecific with marker in TLS destructor to detect TLS is destroyed
https://bugs.webkit.org/show_bug.cgi?id=233851

Reviewed by Mark Lam.

TLS has lifetime problem that,

  1. TLS is destroyed
  2. The other TLS is destroyed, and the destructor is called
  3. The destructor touches (1)'s TLS, then revive TLS of (1) (e.g. libpas's thread local cache).

To handle these cases, pthread library (libc) repeatedly calls destructor PAS_THREAD_LOCAL_CACHE_DESTROYED times
so that we clean up revived TLS again.

By using this mechanism, we can emulate pthread_self_is_exiting_np so that we can avoid reviving TLS.

  1. When destroying TLS, we set a marker (PAS_THREAD_LOCAL_CACHE_DESTROYED in this case) in TLS.
  2. During the other destructor calls, we can detect that TLS is destroyed by checking pthread_getspecific(...) == PAS_THREAD_LOCAL_CACHE_DESTROYED.
  3. We repeatedly calls the destructor of TLS, but every time, we set PAS_THREAD_LOCAL_CACHE_DESTROYED. So after PAS_THREAD_LOCAL_CACHE_DESTROYED times, it is left, and we achieve the goal (1) offering the way to detect the destroyed TLS and (2) avoiding reviving of TLS.

This patch implements it when pthread_self_is_exiting_np does not exist.

  • libpas/src/libpas/pas_segregated_page_inlines.h:

(pas_segregated_page_switch_lock_with_mode):

  • libpas/src/libpas/pas_thread_local_cache.c:

(destructor):
(pas_thread_local_cache_create):
(pas_thread_local_cache_destroy):
(pas_thread_local_cache_get_local_allocator_slow):
(pas_thread_local_cache_for_all):

  • libpas/src/libpas/pas_thread_local_cache.h:

(pas_thread_local_cache_try_get_impl):
(pas_thread_local_cache_try_get):
(pas_thread_local_cache_can_set):
(pas_thread_local_cache_set_impl):
(pas_thread_local_cache_set):
(pas_thread_local_cache_is_guaranteed_to_destruct): Deleted.

  • libpas/src/libpas/pas_try_reallocate.h:

(pas_try_reallocate):

  • libpas/src/libpas/pas_utils.h:
  • libpas/src/test/IsoHeapPageSharingTests.cpp:

(std::addAllTests):

  • libpas/src/test/IsoHeapPartialAndBaselineTests.cpp:

(addIsoHeapPartialAndBaselineTests):

Location:
trunk/Source/bmalloc
Files:
8 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/bmalloc/ChangeLog

    r286516 r286573  
     12021-12-06  Yusuke Suzuki  <ysuzuki@apple.com>
     2
     3        [libpas] Set pthread_setspecific with marker in TLS destructor to detect TLS is destroyed
     4        https://bugs.webkit.org/show_bug.cgi?id=233851
     5
     6        Reviewed by Mark Lam.
     7
     8        TLS has lifetime problem that,
     9
     10        1. TLS is destroyed
     11        2. The other TLS is destroyed, and the destructor is called
     12        3. The destructor touches (1)'s TLS, then revive TLS of (1) (e.g. libpas's thread local cache).
     13
     14        To handle these cases, pthread library (libc) repeatedly calls destructor PAS_THREAD_LOCAL_CACHE_DESTROYED times
     15        so that we clean up revived TLS again.
     16
     17        By using this mechanism, we can emulate pthread_self_is_exiting_np so that we can avoid reviving TLS.
     18
     19        1. When destroying TLS, we set a marker (PAS_THREAD_LOCAL_CACHE_DESTROYED in this case) in TLS.
     20        2. During the other destructor calls, we can detect that TLS is destroyed by checking pthread_getspecific(...) == PAS_THREAD_LOCAL_CACHE_DESTROYED.
     21        3. We repeatedly calls the destructor of TLS, but every time, we set PAS_THREAD_LOCAL_CACHE_DESTROYED.
     22           So after PAS_THREAD_LOCAL_CACHE_DESTROYED times, it is left, and we achieve the goal (1) offering the way to
     23           detect the destroyed TLS and (2) avoiding reviving of TLS.
     24
     25        This patch implements it when pthread_self_is_exiting_np does not exist.
     26
     27        * libpas/src/libpas/pas_segregated_page_inlines.h:
     28        (pas_segregated_page_switch_lock_with_mode):
     29        * libpas/src/libpas/pas_thread_local_cache.c:
     30        (destructor):
     31        (pas_thread_local_cache_create):
     32        (pas_thread_local_cache_destroy):
     33        (pas_thread_local_cache_get_local_allocator_slow):
     34        (pas_thread_local_cache_for_all):
     35        * libpas/src/libpas/pas_thread_local_cache.h:
     36        (pas_thread_local_cache_try_get_impl):
     37        (pas_thread_local_cache_try_get):
     38        (pas_thread_local_cache_can_set):
     39        (pas_thread_local_cache_set_impl):
     40        (pas_thread_local_cache_set):
     41        (pas_thread_local_cache_is_guaranteed_to_destruct): Deleted.
     42        * libpas/src/libpas/pas_try_reallocate.h:
     43        (pas_try_reallocate):
     44        * libpas/src/libpas/pas_utils.h:
     45        * libpas/src/test/IsoHeapPageSharingTests.cpp:
     46        (std::addAllTests):
     47        * libpas/src/test/IsoHeapPartialAndBaselineTests.cpp:
     48        (addIsoHeapPartialAndBaselineTests):
     49
    1502021-12-03  Filip Pizlo  <fpizlo@apple.com>
    251
  • trunk/Source/bmalloc/libpas/src/libpas/pas_segregated_page_inlines.h

    r286493 r286573  
    279279    } }
    280280    PAS_ASSERT(!"Should not be reached");
     281    return true;
    281282}
    282283
  • trunk/Source/bmalloc/libpas/src/libpas/pas_thread_local_cache.c

    r286493 r286573  
    9292static void destructor(void* arg)
    9393{
     94    static const bool verbose = false;
     95
    9496    pas_thread_local_cache* thread_local_cache;
    9597   
    9698    thread_local_cache = (pas_thread_local_cache*)arg;
    9799
    98     destroy(thread_local_cache, pas_lock_is_not_held);
     100#ifndef PAS_THREAD_LOCAL_CACHE_CAN_DETECT_THREAD_EXIT
     101    /* If pthread_self_is_exiting_np does not exist, we set PAS_THREAD_LOCAL_CACHE_DESTROYED in the TLS so that
     102       subsequent calls of pas_thread_local_cache_try_get() can detect whether TLS is destroyed. Since
     103       PAS_THREAD_LOCAL_CACHE_DESTROYED is a non-null value, pthread will call this destructor again (up to
     104       PTHREAD_DESTRUCTOR_ITERATIONS times). Each time it does, it will clear the TLS entry. Hence, we need to re-set
     105       PAS_THREAD_LOCAL_CACHE_DESTROYED in the TLS each time to continue to indicate that destroy() has already been called once. */
     106    pas_thread_local_cache_set_impl((pas_thread_local_cache*)PAS_THREAD_LOCAL_CACHE_DESTROYED);
     107    PAS_ASSERT(!pas_thread_local_cache_can_set());
     108#endif
     109
     110    if (((uintptr_t)thread_local_cache) != PAS_THREAD_LOCAL_CACHE_DESTROYED)
     111        destroy(thread_local_cache, pas_lock_is_not_held);
     112    else {
     113        if (verbose)
     114            pas_log("[%d] Repeated destructor call for TLS %p\n", getpid(), thread_local_cache);
     115    }
    99116}
    100117
     
    175192        pas_thread_local_cache_layout_node_construct(layout_node, thread_local_cache);
    176193
    177     pas_thread_local_cache_set_impl(thread_local_cache);
     194    pas_thread_local_cache_set(thread_local_cache);
    178195   
    179196    return thread_local_cache;
     
    195212    destroy(thread_local_cache, heap_lock_hold_mode);
    196213
    197     pas_thread_local_cache_set_impl(NULL);
     214    pas_thread_local_cache_set(NULL);
    198215}
    199216
     
    313330
    314331    if (thread_local_cache != new_thread_local_cache)
    315         pas_thread_local_cache_set_impl(new_thread_local_cache);
     332        pas_thread_local_cache_set(new_thread_local_cache);
    316333   
    317334    PAS_ASSERT(desired_allocator_index < new_thread_local_cache->allocator_index_upper_bound);
     
    567584}
    568585
    569 #ifdef PAS_THREAD_LOCAL_CACHE_CAN_DETECT_THREAD_EXIT
     586#if PAS_OS(DARWIN)
    570587
    571588static void suspend(pas_thread_local_cache* cache)
     
    741758                }
    742759
    743 #ifdef PAS_THREAD_LOCAL_CACHE_CAN_DETECT_THREAD_EXIT
    744                 if (!pas_thread_local_cache_is_guaranteed_to_destruct()) {
    745                     /* We're on a platform that can't guarantee that thread local caches are destructed.
    746                        Therefore, we might have a TLC that has a dangling thread pointer. So, we don't
    747                        attempt to do the suspend thing. */
    748                     continue;
    749                 }
    750 
     760#if PAS_OS(DARWIN)
    751761                if (verbose)
    752762                    pas_log("Need to suspend for allocator %p\n", scavenger_data);
     
    770780            }
    771781           
    772 #ifdef PAS_THREAD_LOCAL_CACHE_CAN_DETECT_THREAD_EXIT
     782#if PAS_OS(DARWIN)
    773783            if (did_suspend)
    774784                resume(cache);
  • trunk/Source/bmalloc/libpas/src/libpas/pas_thread_local_cache.h

    r286493 r286573  
    4545#endif
    4646
    47 PAS_BEGIN_EXTERN_C;
    48 
    49 struct pas_magazine;
    50 struct pas_thread_local_cache;
    51 struct pas_thread_local_cache_node;
    52 typedef struct pas_magazine pas_magazine;
    53 typedef struct pas_thread_local_cache pas_thread_local_cache;
    54 typedef struct pas_thread_local_cache_node pas_thread_local_cache_node;
    55 
    56 struct pas_thread_local_cache {
    57     uintptr_t deallocation_log[PAS_DEALLOCATION_LOG_SIZE];
    58 
    59     unsigned deallocation_log_index;
    60 
    61     /* A dirty allocation log is one that has been flushed by the thread recently. A clean one is
    62        one that hasn't been touched by the thread (except maybe by appending more things to it). */
    63     bool deallocation_log_dirty;
    64 
    65     size_t num_logged_bytes; /* This undercounts since when we append small objects we don't
    66                                 touch this. */
    67 
    68     /* This part of the TLC is allocated separately so that it does not move. */
    69     pas_thread_local_cache_node* node;
    70 
    71     unsigned* should_stop_bitvector;
    72    
    73     pthread_t thread;
    74    
    75     bool should_stop_some;
    76    
    77     unsigned allocator_index_upper_bound;
    78     unsigned allocator_index_capacity;
    79     uint64_t local_allocators[1]; /* This is variable-length. */
    80 };
    81 
    82 PAS_API extern pas_fast_tls pas_thread_local_cache_fast_tls;
    83 
    84 #define PAS_THREAD_LOCAL_KEY __PTK_FRAMEWORK_JAVASCRIPTCORE_KEY4
    85 
    86 static inline pas_thread_local_cache* pas_thread_local_cache_try_get(void)
    87 {
    88     return (pas_thread_local_cache*)PAS_FAST_TLS_GET(
    89         PAS_THREAD_LOCAL_KEY, &pas_thread_local_cache_fast_tls);
    90 }
     47#define PAS_THREAD_LOCAL_CACHE_DESTROYED 1
    9148
    9249#if PAS_HAVE_PTHREAD_PRIVATE
     
    9855#define PAS_THREAD_LOCAL_CACHE_CAN_DETECT_THREAD_EXIT 1
    9956#endif
    100 
    101 static inline bool pas_thread_local_cache_is_guaranteed_to_destruct(void)
    102 {
    103 #ifdef PAS_THREAD_LOCAL_CACHE_CAN_DETECT_THREAD_EXIT
    104     return true;
    105 #else
    106     return false;
    107 #endif
     57#endif
     58
     59PAS_BEGIN_EXTERN_C;
     60
     61struct pas_magazine;
     62struct pas_thread_local_cache;
     63struct pas_thread_local_cache_node;
     64typedef struct pas_magazine pas_magazine;
     65typedef struct pas_thread_local_cache pas_thread_local_cache;
     66typedef struct pas_thread_local_cache_node pas_thread_local_cache_node;
     67
     68struct pas_thread_local_cache {
     69    uintptr_t deallocation_log[PAS_DEALLOCATION_LOG_SIZE];
     70
     71    unsigned deallocation_log_index;
     72
     73    /* A dirty allocation log is one that has been flushed by the thread recently. A clean one is
     74       one that hasn't been touched by the thread (except maybe by appending more things to it). */
     75    bool deallocation_log_dirty;
     76
     77    size_t num_logged_bytes; /* This undercounts since when we append small objects we don't
     78                                touch this. */
     79
     80    /* This part of the TLC is allocated separately so that it does not move. */
     81    pas_thread_local_cache_node* node;
     82
     83    unsigned* should_stop_bitvector;
     84   
     85    pthread_t thread;
     86   
     87    bool should_stop_some;
     88   
     89    unsigned allocator_index_upper_bound;
     90    unsigned allocator_index_capacity;
     91    uint64_t local_allocators[1]; /* This is variable-length. */
     92};
     93
     94PAS_API extern pas_fast_tls pas_thread_local_cache_fast_tls;
     95
     96#define PAS_THREAD_LOCAL_KEY __PTK_FRAMEWORK_JAVASCRIPTCORE_KEY4
     97
     98static PAS_ALWAYS_INLINE pas_thread_local_cache* pas_thread_local_cache_try_get_impl(void)
     99{
     100    return (pas_thread_local_cache*)PAS_FAST_TLS_GET(PAS_THREAD_LOCAL_KEY, &pas_thread_local_cache_fast_tls);
     101}
     102
     103static inline pas_thread_local_cache* pas_thread_local_cache_try_get(void)
     104{
     105    pas_thread_local_cache* cache = pas_thread_local_cache_try_get_impl();
     106#ifndef PAS_THREAD_LOCAL_CACHE_CAN_DETECT_THREAD_EXIT
     107    if (((uintptr_t)cache) == PAS_THREAD_LOCAL_CACHE_DESTROYED)
     108        return NULL;
     109#endif
     110    return cache;
    108111}
    109112
     
    113116    return !pthread_self_is_exiting_np();
    114117#else
    115     return true;
    116 #endif
    117 }
    118 #else /* PAS_HAVE_PTHREAD_PRIVATE -> so !PAS_HAVE_PTHREAD_PRIVATE */
    119 static inline bool pas_thread_local_cache_is_guaranteed_to_destruct(void)
    120 {
    121     return false;
    122 }
    123 
    124 static inline bool pas_thread_local_cache_can_set(void)
    125 {
    126     return true;
    127 }
    128 #endif /* PAS_HAVE_PTHREAD_PRIVATE -> so end of !PAS_HAVE_PTHREAD_PRIVATE */
     118    return ((uintptr_t)pas_thread_local_cache_try_get_impl()) != PAS_THREAD_LOCAL_CACHE_DESTROYED;
     119#endif
     120}
    129121
    130122static inline void pas_thread_local_cache_set_impl(pas_thread_local_cache* thread_local_cache)
    131123{
     124    PAS_FAST_TLS_SET(PAS_THREAD_LOCAL_KEY, &pas_thread_local_cache_fast_tls, thread_local_cache);
     125}
     126
     127static inline void pas_thread_local_cache_set(pas_thread_local_cache* thread_local_cache)
     128{
    132129    PAS_ASSERT(pas_thread_local_cache_can_set() || pas_thread_local_cache_try_get());
    133     PAS_FAST_TLS_SET(PAS_THREAD_LOCAL_KEY, &pas_thread_local_cache_fast_tls, thread_local_cache);
     130    pas_thread_local_cache_set_impl(thread_local_cache);
    134131}
    135132
  • trunk/Source/bmalloc/libpas/src/libpas/pas_try_reallocate.h

    r286493 r286573  
    355355   
    356356    PAS_ASSERT(!"Should never be reached");
     357    return pas_allocation_result_create_failure();
    357358}
    358359
  • trunk/Source/bmalloc/libpas/src/libpas/pas_utils.h

    r286493 r286573  
    109109PAS_API PAS_NO_RETURN void pas_assertion_failed(const char* filename, int line, const char* function, const char* expression);
    110110
    111 #pragma clang diagnostic push
    112 #pragma clang diagnostic ignored "-Wmissing-noreturn"
     111PAS_IGNORE_WARNINGS_BEGIN("missing-noreturn")
    113112static inline void pas_assertion_failed_noreturn_silencer(
    114113    const char* filename, int line, const char* function, const char* expression)
     
    116115    pas_assertion_failed(filename, line, function, expression);
    117116}
    118 #pragma clang diagnostic pop
     117PAS_IGNORE_WARNINGS_END
    119118
    120119#define PAS_LIKELY(x) __PAS_LIKELY(x)
  • trunk/Source/bmalloc/libpas/src/test/IsoHeapPageSharingTests.cpp

    r285789 r286573  
    45204520        ADD_TEST(testScavengerEventuallyReturnsMemory(128, 10000));
    45214521        ADD_TEST(testScavengerEventuallyReturnsMemory(8, 10000));
    4522         if (pas_thread_local_cache_is_guaranteed_to_destruct()) {
    4523             ADD_TEST(testScavengerEventuallyReturnsMemoryEvenWithoutManualShrink(128, 1));
    4524             ADD_TEST(testScavengerEventuallyReturnsMemoryEvenWithoutManualShrink(128, 10000));
    4525             ADD_TEST(testScavengerEventuallyReturnsMemoryEvenWithoutManualShrink(8, 10000));
    4526         }
     4522        ADD_TEST(testScavengerEventuallyReturnsMemoryEvenWithoutManualShrink(128, 1));
     4523        ADD_TEST(testScavengerEventuallyReturnsMemoryEvenWithoutManualShrink(128, 10000));
     4524        ADD_TEST(testScavengerEventuallyReturnsMemoryEvenWithoutManualShrink(8, 10000));
    45274525        ADD_TEST(testScavengerShutsDownEventually(64, 10000, 1, 1));
    45284526    }
     
    45314529    ADD_TEST(testScavengerEventuallyReturnsMemory(128, 10000));
    45324530    ADD_TEST(testScavengerEventuallyReturnsMemory(8, 10000));
    4533     if (pas_thread_local_cache_is_guaranteed_to_destruct()) {
    4534         ADD_TEST(testScavengerEventuallyReturnsMemoryEvenWithoutManualShrink(128, 1));
    4535         ADD_TEST(testScavengerEventuallyReturnsMemoryEvenWithoutManualShrink(128, 10000));
    4536         ADD_TEST(testScavengerEventuallyReturnsMemoryEvenWithoutManualShrink(8, 10000));
    4537     }
     4531    ADD_TEST(testScavengerEventuallyReturnsMemoryEvenWithoutManualShrink(128, 1));
     4532    ADD_TEST(testScavengerEventuallyReturnsMemoryEvenWithoutManualShrink(128, 10000));
     4533    ADD_TEST(testScavengerEventuallyReturnsMemoryEvenWithoutManualShrink(8, 10000));
    45384534    ADD_TEST(testScavengerShutsDownEventually(64, 10000, 1, 1));
    45394535}
  • trunk/Source/bmalloc/libpas/src/test/IsoHeapPartialAndBaselineTests.cpp

    r285789 r286573  
    10831083   
    10841084#if PAS_ENABLE_ISO && PAS_ENABLE_ISO_TEST
    1085     if (pas_thread_local_cache_is_guaranteed_to_destruct()) {
     1085    {
    10861086        RunScavengerFully runScavengerFully;
    10871087        addScavengerDependentTests();
Note: See TracChangeset for help on using the changeset viewer.