Merge "Move /system dependency on tz_version to bionic"
diff --git a/benchmarks/Android.bp b/benchmarks/Android.bp
index 06c0ba3..370e040 100644
--- a/benchmarks/Android.bp
+++ b/benchmarks/Android.bp
@@ -61,8 +61,8 @@
 }
 
 // Build benchmarks for the device (with bionic's .so). Run with:
-//   adb shell bionic-benchmarks32
-//   adb shell bionic-benchmarks64
+//   adb shell /data/benchmarktest/bionic-benchmarks/bionic-benchmarks
+//   adb shell /data/benchmarktest64/bionic-benchmarks/bionic-benchmarks
 cc_benchmark {
     name: "bionic-benchmarks",
     defaults: ["bionic-benchmarks-defaults"],
diff --git a/benchmarks/run-on-host.sh b/benchmarks/run-on-host.sh
index af96b27..0f2aefb 100755
--- a/benchmarks/run-on-host.sh
+++ b/benchmarks/run-on-host.sh
@@ -25,7 +25,7 @@
             cd ${ANDROID_BUILD_TOP}
             export ANDROID_DATA=${TARGET_OUT_DATA}
             export ANDROID_ROOT=${TARGET_OUT}
-            ${NATIVETEST}/bionic-benchmarks/bionic-benchmarks $@
+            ${BENCHMARKS}/bionic-benchmarks/bionic-benchmarks $@
         )
     else
         echo "$0 not supported on TARGET_ARCH=$TARGET_ARCH"
diff --git a/build/run-on-host.sh b/build/run-on-host.sh
index c3a2751..85539e2 100644
--- a/build/run-on-host.sh
+++ b/build/run-on-host.sh
@@ -15,33 +15,36 @@
     BITS=$1
     shift
 
+    BENCHMARKS=${TARGET_OUT_DATA}/benchmarktest
     NATIVETEST=${TARGET_OUT_DATA}/nativetest
     if [ "${BITS}" = 64 ]; then
+        BENCHMARKS=${BENCHMARKS}64
         NATIVETEST=${NATIVETEST}64
     fi
 
-    if [ ${TARGET_ARCH} = arm -o ${TARGET_ARCH} = mips -o ${TARGET_ARCH} = x86 ]; then
-        LINKER=${TARGET_OUT_EXECUTABLES}/linker
-    else
-        LINKER="${TARGET_OUT_EXECUTABLES}/linker64 ${TARGET_OUT_EXECUTABLES}/linker"
-    fi
-
     if [ ${TARGET_ARCH} = x86 -o ${TARGET_ARCH} = x86_64 ]; then
-        m -j ${LINKER} ${TARGET_OUT}/etc/hosts ${TARGET_OUT_EXECUTABLES}/sh $@
+        m -j MODULES-IN-bionic MODULES-IN-external-icu MODULES-IN-external-mksh ${TARGET_OUT}/etc/hosts $@
 
         if [ ! -d /system ]; then
             echo "Attempting to create /system";
             sudo mkdir -p -m 0777 /system;
+            mkdir -p -m 0777 /system/bin;
+            mkdir -p -m 0777 /system/lib;
+            mkdir -p -m 0777 /system/lib64;
         fi
         (
             cd ${ANDROID_BUILD_TOP}
             mkdir -p ${TARGET_OUT_DATA}/local/tmp
-            ln -fs `realpath ${TARGET_OUT}/bin` /system/
+            for i in ${TARGET_OUT}/bin/bootstrap/* ${TARGET_OUT}/bin/*; do
+              ln -fs `realpath ${i}` /system/bin/
+            done
             ln -fs `realpath ${TARGET_OUT}/etc` /system/
-            ln -fs `realpath ${TARGET_OUT}/lib` /system/
-            if [ -d "${TARGET_OUT}/lib64" ]; then
-                ln -fs `realpath ${TARGET_OUT}/lib64` /system/;
-            fi
+            for i in ${TARGET_OUT}/lib/bootstrap/* ${TARGET_OUT}/lib/*; do
+              ln -fs `realpath ${i}` /system/lib/
+            done
+            for i in ${TARGET_OUT}/lib64/bootstrap/* ${TARGET_OUT}/lib64/*; do
+              ln -fs `realpath ${i}` /system/lib64/
+            done
         )
     fi
 }
diff --git a/docs/status.md b/docs/status.md
index d6a2f4c..e1a5d34 100644
--- a/docs/status.md
+++ b/docs/status.md
@@ -37,6 +37,9 @@
 
 Current libc symbols: https://android.googlesource.com/platform/bionic/+/master/libc/libc.map.txt
 
+New libc functions in R (API level 30):
+  * Full C11 `<threads.h>` (available as inlines for older API levels).
+
 New libc functions in Q (API level 29):
   * `timespec_get` (C11 `<time.h>` addition)
   * `reallocarray` (BSD/GNU extension in `<malloc.h>` and `<stdlib.h>`)
diff --git a/libc/Android.bp b/libc/Android.bp
index 0dfe5b8..fa1eab6 100644
--- a/libc/Android.bp
+++ b/libc/Android.bp
@@ -1154,6 +1154,7 @@
         "bionic/tdestroy.cpp",
         "bionic/termios.cpp",
         "bionic/thread_private.cpp",
+        "bionic/threads.cpp",
         "bionic/timespec_get.cpp",
         "bionic/tmpfile.cpp",
         "bionic/umount.cpp",
diff --git a/libc/bionic/grp_pwd.cpp b/libc/bionic/grp_pwd.cpp
index dadda49..ca6ea22 100644
--- a/libc/bionic/grp_pwd.cpp
+++ b/libc/bionic/grp_pwd.cpp
@@ -163,37 +163,19 @@
   return gr;
 }
 
-static passwd* android_id_to_passwd(passwd_state_t* state, unsigned id) {
+static const android_id_info* find_android_id_info(unsigned id) {
   for (size_t n = 0; n < android_id_count; ++n) {
     if (android_ids[n].aid == id) {
-      return android_iinfo_to_passwd(state, android_ids + n);
+      return &android_ids[n];
     }
   }
   return nullptr;
 }
 
-static passwd* android_name_to_passwd(passwd_state_t* state, const char* name) {
+static const android_id_info* find_android_id_info(const char* name) {
   for (size_t n = 0; n < android_id_count; ++n) {
     if (!strcmp(android_ids[n].name, name)) {
-      return android_iinfo_to_passwd(state, android_ids + n);
-    }
-  }
-  return nullptr;
-}
-
-static group* android_id_to_group(group_state_t* state, unsigned id) {
-  for (size_t n = 0; n < android_id_count; ++n) {
-    if (android_ids[n].aid == id) {
-      return android_iinfo_to_group(state, android_ids + n);
-    }
-  }
-  return nullptr;
-}
-
-static group* android_name_to_group(group_state_t* state, const char* name) {
-  for (size_t n = 0; n < android_id_count; ++n) {
-    if (!strcmp(android_ids[n].name, name)) {
-      return android_iinfo_to_group(state, android_ids + n);
+      return &android_ids[n];
     }
   }
   return nullptr;
@@ -332,15 +314,9 @@
   } else if (end[1] == 'i' && isdigit(end[2])) {
     // end will point to \0 if the strtoul below succeeds.
     appid = strtoul(end+2, &end, 10) + AID_ISOLATED_START;
-  } else {
-    for (size_t n = 0; n < android_id_count; n++) {
-      if (!strcmp(android_ids[n].name, end + 1)) {
-        appid = android_ids[n].aid;
-        // Move the end pointer to the null terminator.
-        end += strlen(android_ids[n].name) + 1;
-        break;
-      }
-    }
+  } else if (auto* android_id_info = find_android_id_info(end + 1); android_id_info != nullptr) {
+    appid = android_id_info->aid;
+    end += strlen(android_id_info->name) + 1;
   }
 
   // Check that the entire string was consumed by one of the 3 cases above.
@@ -370,11 +346,8 @@
   if (appid >= AID_ISOLATED_START) {
     snprintf(buffer, bufferlen, "u%u_i%u", userid, appid - AID_ISOLATED_START);
   } else if (appid < AID_APP_START) {
-    for (size_t n = 0; n < android_id_count; n++) {
-      if (android_ids[n].aid == appid) {
-        snprintf(buffer, bufferlen, "u%u_%s", userid, android_ids[n].name);
-        return;
-      }
+    if (auto* android_id_info = find_android_id_info(appid); android_id_info != nullptr) {
+      snprintf(buffer, bufferlen, "u%u_%s", userid, android_id_info->name);
     }
   } else {
     snprintf(buffer, bufferlen, "u%u_a%u", userid, appid - AID_APP_START);
@@ -391,11 +364,8 @@
   } else if (appid >= AID_CACHE_GID_START && appid <= AID_CACHE_GID_END) {
     snprintf(buffer, bufferlen, "u%u_a%u_cache", userid, appid - AID_CACHE_GID_START);
   } else if (appid < AID_APP_START) {
-    for (size_t n = 0; n < android_id_count; n++) {
-      if (android_ids[n].aid == appid) {
-        snprintf(buffer, bufferlen, "u%u_%s", userid, android_ids[n].name);
-        return;
-      }
+    if (auto* android_id_info = find_android_id_info(appid); android_id_info != nullptr) {
+      snprintf(buffer, bufferlen, "u%u_%s", userid, android_id_info->name);
     }
   } else {
     snprintf(buffer, bufferlen, "u%u_a%u", userid, appid - AID_APP_START);
@@ -520,12 +490,12 @@
     return nullptr;
   }
 
-  passwd* pw = android_id_to_passwd(state, uid);
-  if (pw != nullptr) {
-    return pw;
+  if (auto* android_id_info = find_android_id_info(uid); android_id_info != nullptr) {
+    return android_iinfo_to_passwd(state, android_id_info);
   }
+
   // Handle OEM range.
-  pw = oem_id_to_passwd(uid, state);
+  passwd* pw = oem_id_to_passwd(uid, state);
   if (pw != nullptr) {
     return pw;
   }
@@ -538,9 +508,8 @@
     return nullptr;
   }
 
-  passwd* pw = android_name_to_passwd(state, login);
-  if (pw != nullptr) {
-    return pw;
+  if (auto* android_id_info = find_android_id_info(login); android_id_info != nullptr) {
+    return android_iinfo_to_passwd(state, android_id_info);
   }
 
   if (vendor_passwd.FindByName(login, state)) {
@@ -550,7 +519,7 @@
   }
 
   // Handle OEM range.
-  pw = oem_id_to_passwd(oem_id_from_name(login), state);
+  passwd* pw = oem_id_to_passwd(oem_id_from_name(login), state);
   if (pw != nullptr) {
     return pw;
   }
@@ -634,12 +603,12 @@
 }
 
 static group* getgrgid_internal(gid_t gid, group_state_t* state) {
-  group* grp = android_id_to_group(state, gid);
-  if (grp != nullptr) {
-    return grp;
+  if (auto* android_id_info = find_android_id_info(gid); android_id_info != nullptr) {
+    return android_iinfo_to_group(state, android_id_info);
   }
+
   // Handle OEM range.
-  grp = oem_id_to_group(gid, state);
+  group* grp = oem_id_to_group(gid, state);
   if (grp != nullptr) {
     return grp;
   }
@@ -655,9 +624,8 @@
 }
 
 static group* getgrnam_internal(const char* name, group_state_t* state) {
-  group* grp = android_name_to_group(state, name);
-  if (grp != nullptr) {
-    return grp;
+  if (auto* android_id_info = find_android_id_info(name); android_id_info != nullptr) {
+    return android_iinfo_to_group(state, android_id_info);
   }
 
   if (vendor_group.FindByName(name, state)) {
@@ -667,7 +635,7 @@
   }
 
   // Handle OEM range.
-  grp = oem_id_to_group(oem_id_from_name(name), state);
+  group* grp = oem_id_to_group(oem_id_from_name(name), state);
   if (grp != nullptr) {
     return grp;
   }
diff --git a/libc/bionic/libc_init_common.cpp b/libc/bionic/libc_init_common.cpp
index b229cda..1f099cf 100644
--- a/libc/bionic/libc_init_common.cpp
+++ b/libc/bionic/libc_init_common.cpp
@@ -232,6 +232,7 @@
     "LD_AOUT_LIBRARY_PATH",
     "LD_AOUT_PRELOAD",
     "LD_AUDIT",
+    "LD_CONFIG_FILE",
     "LD_DEBUG",
     "LD_DEBUG_OUTPUT",
     "LD_DYNAMIC_WEAK",
@@ -242,6 +243,7 @@
     "LD_SHOW_AUXV",
     "LD_USE_LOAD_BIAS",
     "LIBC_DEBUG_MALLOC_OPTIONS",
+    "LIBC_HOOKS_ENABLE",
     "LOCALDOMAIN",
     "LOCPATH",
     "MALLOC_CHECK_",
diff --git a/libc/bionic/malloc_common.cpp b/libc/bionic/malloc_common.cpp
index e9f9db2..9dc4d12 100644
--- a/libc/bionic/malloc_common.cpp
+++ b/libc/bionic/malloc_common.cpp
@@ -32,14 +32,7 @@
 // calls and add special debugging code to attempt to catch allocation
 // errors. All of the debugging code is implemented in a separate shared
 // library that is only loaded when the property "libc.debug.malloc.options"
-// is set to a non-zero value. There are two functions exported to
-// allow ddms, or other external users to get information from the debug
-// allocation.
-//   get_malloc_leak_info: Returns information about all of the known native
-//                         allocations that are currently in use.
-//   free_malloc_leak_info: Frees the data allocated by the call to
-//                          get_malloc_leak_info.
-//   write_malloc_leak_info: Writes the leak info data to a file.
+// is set to a non-zero value.
 
 #include <errno.h>
 #include <stdint.h>
diff --git a/libc/bionic/malloc_common_dynamic.cpp b/libc/bionic/malloc_common_dynamic.cpp
index 64f9f6f..599ac6a 100644
--- a/libc/bionic/malloc_common_dynamic.cpp
+++ b/libc/bionic/malloc_common_dynamic.cpp
@@ -409,39 +409,29 @@
 // =============================================================================
 // Functions to support dumping of native heap allocations using malloc debug.
 // =============================================================================
-
-// Retrieve native heap information.
-//
-// "*info" is set to a buffer we allocate
-// "*overall_size" is set to the size of the "info" buffer
-// "*info_size" is set to the size of a single entry
-// "*total_memory" is set to the sum of all allocations we're tracking; does
-//   not include heap overhead
-// "*backtrace_size" is set to the maximum number of entries in the back trace
-extern "C" void get_malloc_leak_info(uint8_t** info, size_t* overall_size,
-    size_t* info_size, size_t* total_memory, size_t* backtrace_size) {
+bool GetMallocLeakInfo(android_mallopt_leak_info_t* leak_info) {
   void* func = gFunctions[FUNC_GET_MALLOC_LEAK_INFO];
   if (func == nullptr) {
-    return;
+    errno = ENOTSUP;
+    return false;
   }
-  reinterpret_cast<get_malloc_leak_info_func_t>(func)(info, overall_size, info_size, total_memory,
-                                                      backtrace_size);
+  reinterpret_cast<get_malloc_leak_info_func_t>(func)(
+      &leak_info->buffer, &leak_info->overall_size, &leak_info->info_size,
+      &leak_info->total_memory, &leak_info->backtrace_size);
+  return true;
 }
 
-extern "C" void free_malloc_leak_info(uint8_t* info) {
+bool FreeMallocLeakInfo(android_mallopt_leak_info_t* leak_info) {
   void* func = gFunctions[FUNC_FREE_MALLOC_LEAK_INFO];
   if (func == nullptr) {
-    return;
+    errno = ENOTSUP;
+    return false;
   }
-  reinterpret_cast<free_malloc_leak_info_func_t>(func)(info);
+  reinterpret_cast<free_malloc_leak_info_func_t>(func)(leak_info->buffer);
+  return true;
 }
 
-extern "C" void write_malloc_leak_info(FILE* fp) {
-  if (fp == nullptr) {
-    error_log("write_malloc_leak_info called with a nullptr");
-    return;
-  }
-
+bool WriteMallocLeakInfo(FILE* fp) {
   void* func = gFunctions[FUNC_WRITE_LEAK_INFO];
   bool written = false;
   if (func != nullptr) {
@@ -453,7 +443,9 @@
     fprintf(fp, "# adb shell stop\n");
     fprintf(fp, "# adb shell setprop libc.debug.malloc.options backtrace\n");
     fprintf(fp, "# adb shell start\n");
+    errno = ENOTSUP;
   }
+  return written;
 }
 // =============================================================================
 
@@ -484,6 +476,27 @@
   if (opcode == M_SET_ALLOCATION_LIMIT_BYTES) {
     return LimitEnable(arg, arg_size);
   }
+  if (opcode == M_WRITE_MALLOC_LEAK_INFO_TO_FILE) {
+    if (arg == nullptr || arg_size != sizeof(FILE*)) {
+      errno = EINVAL;
+      return false;
+    }
+    return WriteMallocLeakInfo(reinterpret_cast<FILE*>(arg));
+  }
+  if (opcode == M_GET_MALLOC_LEAK_INFO) {
+    if (arg == nullptr || arg_size != sizeof(android_mallopt_leak_info_t)) {
+      errno = EINVAL;
+      return false;
+    }
+    return GetMallocLeakInfo(reinterpret_cast<android_mallopt_leak_info_t*>(arg));
+  }
+  if (opcode == M_FREE_MALLOC_LEAK_INFO) {
+    if (arg == nullptr || arg_size != sizeof(android_mallopt_leak_info_t)) {
+      errno = EINVAL;
+      return false;
+    }
+    return FreeMallocLeakInfo(reinterpret_cast<android_mallopt_leak_info_t*>(arg));
+  }
   return HeapprofdMallopt(opcode, arg, arg_size);
 }
 // =============================================================================
diff --git a/libc/bionic/pthread_getschedparam.cpp b/libc/bionic/pthread_getschedparam.cpp
index ed1853b..765287f 100644
--- a/libc/bionic/pthread_getschedparam.cpp
+++ b/libc/bionic/pthread_getschedparam.cpp
@@ -28,9 +28,11 @@
 
 #include <errno.h>
 
+#include "private/bionic_defs.h"
 #include "private/ErrnoRestorer.h"
 #include "pthread_internal.h"
 
+__BIONIC_WEAK_FOR_NATIVE_BRIDGE
 int pthread_getschedparam(pthread_t t, int* policy, sched_param* param) {
   ErrnoRestorer errno_restorer;
 
diff --git a/libc/bionic/pthread_setschedparam.cpp b/libc/bionic/pthread_setschedparam.cpp
index 8a02728..656fe32 100644
--- a/libc/bionic/pthread_setschedparam.cpp
+++ b/libc/bionic/pthread_setschedparam.cpp
@@ -30,9 +30,11 @@
 #include <pthread.h>
 #include <sched.h>
 
+#include "private/bionic_defs.h"
 #include "private/ErrnoRestorer.h"
 #include "pthread_internal.h"
 
+__BIONIC_WEAK_FOR_NATIVE_BRIDGE
 int pthread_setschedparam(pthread_t t, int policy, const sched_param* param) {
   ErrnoRestorer errno_restorer;
 
@@ -42,6 +44,7 @@
   return (sched_setscheduler(tid, policy, param) == -1) ? errno : 0;
 }
 
+__BIONIC_WEAK_FOR_NATIVE_BRIDGE
 int pthread_setschedprio(pthread_t t, int priority) {
   ErrnoRestorer errno_restorer;
 
diff --git a/libc/bionic/threads.cpp b/libc/bionic/threads.cpp
new file mode 100644
index 0000000..f597580
--- /dev/null
+++ b/libc/bionic/threads.cpp
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <threads.h>
+
+#define __BIONIC_THREADS_INLINE /* Out of line. */
+#include <bits/threads_inlines.h>
diff --git a/libc/include/android/api-level.h b/libc/include/android/api-level.h
index a175857..af7cde1 100644
--- a/libc/include/android/api-level.h
+++ b/libc/include/android/api-level.h
@@ -100,6 +100,9 @@
 /** Names the "Q" API level (29), for comparisons against __ANDROID_API__. */
 #define __ANDROID_API_Q__ 29
 
+/** Names the "R" API level (30), for comparisons against __ANDROID_API__. */
+#define __ANDROID_API_R__ 30
+
 /**
  * Returns the `targetSdkVersion` of the caller, or `__ANDROID_API_FUTURE__`
  * if there is no known target SDK version (for code not running in the
diff --git a/libc/include/android/legacy_threads_inlines.h b/libc/include/android/legacy_threads_inlines.h
new file mode 100644
index 0000000..e73ef37
--- /dev/null
+++ b/libc/include/android/legacy_threads_inlines.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+
+#if __ANDROID_API__ < __ANDROID_API_R__
+
+#define __BIONIC_THREADS_INLINE static __inline
+#include <bits/threads_inlines.h>
+
+#endif
diff --git a/libc/include/bits/threads_inlines.h b/libc/include/bits/threads_inlines.h
new file mode 100644
index 0000000..1130b3a
--- /dev/null
+++ b/libc/include/bits/threads_inlines.h
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <threads.h>
+
+#include <errno.h>
+#include <sched.h>
+#include <stdlib.h>
+
+#if !defined(__BIONIC_THREADS_INLINE)
+#define __BIONIC_THREADS_INLINE static __inline
+#endif
+
+__BEGIN_DECLS
+
+static __inline int __bionic_thrd_error(int __pthread_code) {
+  switch (__pthread_code) {
+    case 0: return 0;
+    case ENOMEM: return thrd_nomem;
+    case ETIMEDOUT: return thrd_timedout;
+    case EBUSY: return thrd_busy;
+    default: return thrd_error;
+  }
+}
+
+__BIONIC_THREADS_INLINE void call_once(once_flag* __flag,
+                                       void (*__function)(void)) {
+  pthread_once(__flag, __function);
+}
+
+
+
+__BIONIC_THREADS_INLINE int cnd_broadcast(cnd_t* __cnd) {
+  return __bionic_thrd_error(pthread_cond_broadcast(__cnd));
+}
+
+__BIONIC_THREADS_INLINE void cnd_destroy(cnd_t* __cnd) {
+  pthread_cond_destroy(__cnd);
+}
+
+__BIONIC_THREADS_INLINE int cnd_init(cnd_t* __cnd) {
+  return __bionic_thrd_error(pthread_cond_init(__cnd, NULL));
+}
+
+__BIONIC_THREADS_INLINE int cnd_signal(cnd_t* __cnd) {
+  return __bionic_thrd_error(pthread_cond_signal(__cnd));
+}
+
+__BIONIC_THREADS_INLINE int cnd_timedwait(cnd_t* __cnd,
+                                          mtx_t* __mtx,
+                                          const struct timespec* __timeout) {
+  return __bionic_thrd_error(pthread_cond_timedwait(__cnd, __mtx, __timeout));
+}
+
+__BIONIC_THREADS_INLINE int cnd_wait(cnd_t* __cnd, mtx_t* __mtx) {
+  return __bionic_thrd_error(pthread_cond_wait(__cnd, __mtx));
+}
+
+
+
+__BIONIC_THREADS_INLINE void mtx_destroy(mtx_t* __mtx) {
+  pthread_mutex_destroy(__mtx);
+}
+
+__BIONIC_THREADS_INLINE int mtx_init(mtx_t* __mtx, int __type) {
+  int __pthread_type = (__type & mtx_recursive) ? PTHREAD_MUTEX_RECURSIVE
+                                                : PTHREAD_MUTEX_NORMAL;
+  __type &= ~mtx_recursive;
+  if (__type != mtx_plain && __type != mtx_timed) return thrd_error;
+
+  pthread_mutexattr_t __attr;
+  pthread_mutexattr_init(&__attr);
+  pthread_mutexattr_settype(&__attr, __pthread_type);
+  return __bionic_thrd_error(pthread_mutex_init(__mtx, &__attr));
+}
+
+__BIONIC_THREADS_INLINE int mtx_lock(mtx_t* __mtx) {
+  return __bionic_thrd_error(pthread_mutex_lock(__mtx));
+}
+
+__BIONIC_THREADS_INLINE int mtx_timedlock(mtx_t* __mtx,
+                                          const struct timespec* __timeout) {
+  return __bionic_thrd_error(pthread_mutex_timedlock(__mtx, __timeout));
+}
+
+__BIONIC_THREADS_INLINE int mtx_trylock(mtx_t* __mtx) {
+  return __bionic_thrd_error(pthread_mutex_trylock(__mtx));
+}
+
+__BIONIC_THREADS_INLINE int mtx_unlock(mtx_t* __mtx) {
+  return __bionic_thrd_error(pthread_mutex_unlock(__mtx));
+}
+
+
+
+struct __bionic_thrd_data {
+  thrd_start_t __func;
+  void* __arg;
+};
+
+static inline void* __bionic_thrd_trampoline(void* __arg) {
+  struct __bionic_thrd_data __data =
+      *__BIONIC_CAST(static_cast, struct __bionic_thrd_data*, __arg);
+  free(__arg);
+  int __result = __data.__func(__data.__arg);
+  return __BIONIC_CAST(reinterpret_cast, void*,
+                       __BIONIC_CAST(static_cast, uintptr_t, __result));
+}
+
+__BIONIC_THREADS_INLINE int thrd_create(thrd_t* __thrd,
+                                        thrd_start_t __func,
+                                        void* __arg) {
+  struct __bionic_thrd_data* __pthread_arg =
+      __BIONIC_CAST(static_cast, struct __bionic_thrd_data*,
+                    malloc(sizeof(struct __bionic_thrd_data)));
+  __pthread_arg->__func = __func;
+  __pthread_arg->__arg = __arg;
+  int __result = __bionic_thrd_error(pthread_create(__thrd, NULL,
+                                                    __bionic_thrd_trampoline,
+                                                    __pthread_arg));
+  if (__result != thrd_success) free(__pthread_arg);
+  return __result;
+}
+
+__BIONIC_THREADS_INLINE thrd_t thrd_current(void) {
+  return pthread_self();
+}
+
+__BIONIC_THREADS_INLINE int thrd_detach(thrd_t __thrd) {
+  return __bionic_thrd_error(pthread_detach(__thrd));
+}
+
+__BIONIC_THREADS_INLINE int thrd_equal(thrd_t __lhs, thrd_t __rhs) {
+  return pthread_equal(__lhs, __rhs);
+}
+
+__BIONIC_THREADS_INLINE void thrd_exit(int __result) {
+  pthread_exit(__BIONIC_CAST(reinterpret_cast, void*,
+                             __BIONIC_CAST(static_cast, uintptr_t, __result)));
+}
+
+__BIONIC_THREADS_INLINE int thrd_join(thrd_t __thrd, int* __result) {
+  void* __pthread_result;
+  if (pthread_join(__thrd, &__pthread_result) != 0) return thrd_error;
+  if (__result) {
+    *__result = __BIONIC_CAST(reinterpret_cast, intptr_t, __pthread_result);
+  }
+  return thrd_success;
+}
+
+__BIONIC_THREADS_INLINE int thrd_sleep(const struct timespec* __duration,
+                                       struct timespec* __remaining) {
+  int __rc = nanosleep(__duration, __remaining);
+  if (__rc == 0) return 0;
+  return (errno == EINTR) ? -1 : -2;
+}
+
+__BIONIC_THREADS_INLINE void thrd_yield(void) {
+  sched_yield();
+}
+
+
+
+__BIONIC_THREADS_INLINE int tss_create(tss_t* __key, tss_dtor_t __dtor) {
+  return __bionic_thrd_error(pthread_key_create(__key, __dtor));
+}
+
+__BIONIC_THREADS_INLINE void tss_delete(tss_t __key) {
+  pthread_key_delete(__key);
+}
+
+__BIONIC_THREADS_INLINE void* tss_get(tss_t __key) {
+  return pthread_getspecific(__key);
+}
+
+__BIONIC_THREADS_INLINE int tss_set(tss_t __key, void* __value) {
+  return __bionic_thrd_error(pthread_setspecific(__key, __value));
+}
+
+__END_DECLS
diff --git a/libc/include/threads.h b/libc/include/threads.h
new file mode 100644
index 0000000..2c43b0b
--- /dev/null
+++ b/libc/include/threads.h
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#pragma once
+
+/**
+ * @file threads.h
+ * @brief C11 threads.
+ */
+
+#include <sys/cdefs.h>
+
+#include <pthread.h>
+#include <time.h>
+
+#define ONCE_FLAG_INIT PTHREAD_ONCE_INIT
+#define TSS_DTOR_ITERATIONS PTHREAD_DESTRUCTOR_ITERATIONS
+
+/** The type for a condition variable. */
+typedef pthread_cond_t cnd_t;
+/** The type for a thread. */
+typedef pthread_t thrd_t;
+/** The type for a thread-specific storage key. */
+typedef pthread_key_t tss_t;
+/** The type for a mutex. */
+typedef pthread_mutex_t mtx_t;
+
+/** The type for a thread-specific storage destructor. */
+typedef void (*tss_dtor_t)(void*);
+/** The type of the function passed to thrd_create() to create a new thread. */
+typedef int (*thrd_start_t)(void*);
+
+/** The type used by call_once(). */
+typedef pthread_once_t once_flag;
+
+enum {
+  mtx_plain = 0x1,
+  mtx_recursive = 0x2,
+  mtx_timed = 0x4,
+};
+
+enum {
+  thrd_success = 0,
+  thrd_busy = 1,
+  thrd_error = 2,
+  thrd_nomem = 3,
+  thrd_timedout = 4,
+};
+
+#if !defined(__cplusplus)
+#define thread_local _Thread_local
+#endif
+
+__BEGIN_DECLS
+
+#if __ANDROID_API__ >= __ANDROID_API_R__
+// This file is implemented as static inlines before API level 30.
+
+/** Uses `__flag` to ensure that `__function` is called exactly once. */
+void call_once(once_flag* __flag, void (*__function)(void));
+
+
+
+/**
+ * Unblocks all threads blocked on `__cond`.
+ */
+int cnd_broadcast(cnd_t* __cond);
+
+/**
+ * Destroys a condition variable.
+ */
+void cnd_destroy(cnd_t* __cond);
+
+/**
+ * Creates a condition variable.
+ */
+int cnd_init(cnd_t* __cond);
+
+/**
+ * Unblocks one thread blocked on `__cond`.
+ */
+int cnd_signal(cnd_t* __cond);
+
+/**
+ * Unlocks `__mutex` and blocks until `__cond` is signaled or `__timeout` occurs.
+ */
+int cnd_timedwait(cnd_t* __cond, mtx_t* __mutex, const struct timespec* __timeout);
+
+/**
+ * Unlocks `__mutex` and blocks until `__cond` is signaled.
+ */
+int cnd_wait(cnd_t* __cond, mtx_t* __mutex);
+
+
+
+/**
+ * Destroys a mutex.
+ */
+void mtx_destroy(mtx_t* __mutex);
+
+/**
+ * Creates a mutex.
+ */
+int mtx_init(mtx_t* __mutex, int __type);
+
+/**
+ * Blocks until `__mutex` is acquired.
+ */
+int mtx_lock(mtx_t* __mutex);
+
+/**
+ * Blocks until `__mutex` is acquired or `__timeout` expires.
+ */
+int mtx_timedlock(mtx_t* __mutex, const struct timespec* __timeout);
+
+/**
+ * Acquires `__mutex` or returns `thrd_busy`.
+ */
+int mtx_trylock(mtx_t* __mutex);
+
+/**
+ * Unlocks `__mutex`.
+ */
+int mtx_unlock(mtx_t* __mutex);
+
+
+
+/**
+ * Creates a new thread running `__function(__arg)`, and sets `*__thrd` to
+ * the new thread.
+ */
+int thrd_create(thrd_t* __thrd, thrd_start_t __function, void* __arg);
+
+/**
+ * Returns the `thrd_t` corresponding to the caller.
+ */
+thrd_t thrd_current(void);
+
+/**
+ * Tells the OS to automatically dispose of `__thrd` when it exits.
+ */
+int thrd_detach(thrd_t __thrd);
+
+/**
+ * Tests whether two threads are the same thread.
+ */
+int thrd_equal(thrd_t __lhs, thrd_t __rhs);
+
+/**
+ * Terminates the calling thread, setting its result to `__result`.
+ */
+void thrd_exit(int __result) __noreturn;
+
+/**
+ * Blocks until `__thrd` terminates. If `__result` is not null, `*__result`
+ * is set to the exiting thread's result.
+ */
+int thrd_join(thrd_t __thrd, int* __result);
+
+/**
+ * Blocks the caller for at least `__duration` unless a signal is delivered.
+ * If a signal causes the sleep to end early and `__remaining` is not null,
+ * `*__remaining` is set to the time remaining.
+ *
+ * Returns 0 on success, or -1 if a signal was delivered.
+ */
+int thrd_sleep(const struct timespec* __duration, struct timespec* __remaining);
+
+/**
+ * Request that other threads should be scheduled.
+ */
+void thrd_yield(void);
+
+
+
+/**
+ * Creates a thread-specific storage key with the associated destructor (which
+ * may be null).
+ */
+int tss_create(tss_t* __key, tss_dtor_t __dtor);
+
+/**
+ * Destroys a thread-specific storage key.
+ */
+void tss_delete(tss_t __key);
+
+/**
+ * Returns the value for the current thread held in the thread-specific storage
+ * identified by `__key`.
+ */
+void* tss_get(tss_t __key);
+
+/**
+ * Sets the current thread's value for the thread-specific storage identified
+ * by `__key` to `__value`.
+ */
+int tss_set(tss_t __key, void* __value);
+
+#endif
+
+__END_DECLS
+
+#include <android/legacy_threads_inlines.h>
diff --git a/libc/libc.map.txt b/libc/libc.map.txt
index 55ca9dc..4a734fc 100644
--- a/libc/libc.map.txt
+++ b/libc/libc.map.txt
@@ -1473,18 +1473,42 @@
     malloc_enable; # apex
     malloc_iterate; # apex
 
-    # Used by libmediautils
-    write_malloc_leak_info; # apex
-    free_malloc_leak_info; # apex
-    get_malloc_leak_info; # apex
-
     # Used by libandroid_net
     android_getaddrinfofornet; # apex
 
-    # Used by libandroid_runtime and libmedia
+    # Used by libandroid_runtime, libmedia and libmediautils
     android_mallopt; # apex
 } LIBC_P;
 
+LIBC_R { # introduced=R
+  global:
+    call_once;
+    cnd_broadcast;
+    cnd_destroy;
+    cnd_init;
+    cnd_signal;
+    cnd_timedwait;
+    cnd_wait;
+    mtx_destroy;
+    mtx_init;
+    mtx_lock;
+    mtx_timedlock;
+    mtx_trylock;
+    mtx_unlock;
+    thrd_create;
+    thrd_current;
+    thrd_detach;
+    thrd_equal;
+    thrd_exit;
+    thrd_join;
+    thrd_sleep;
+    thrd_yield;
+    tss_create;
+    tss_delete;
+    tss_get;
+    tss_set;
+} LIBC_Q;
+
 LIBC_PRIVATE {
   global:
     ___Unwind_Backtrace; # arm
diff --git a/libc/malloc_hooks/tests/malloc_hooks_tests.cpp b/libc/malloc_hooks/tests/malloc_hooks_tests.cpp
index 0d23a6a..86e20ea 100644
--- a/libc/malloc_hooks/tests/malloc_hooks_tests.cpp
+++ b/libc/malloc_hooks/tests/malloc_hooks_tests.cpp
@@ -38,6 +38,7 @@
 
 #include <gtest/gtest.h>
 
+#include <private/bionic_malloc.h>
 #include <private/bionic_malloc_dispatch.h>
 #include <tests/utils.h>
 
@@ -197,19 +198,15 @@
 }
 
 TEST_F(MallocHooksTest, DISABLED_extended_functions) {
-  uint8_t* info = nullptr;
-  size_t overall_size = 100;
-  size_t info_size = 200;
-  size_t total_memory = 300;
-  size_t backtrace_size = 400;
-  get_malloc_leak_info(&info, &overall_size, &info_size, &total_memory, &backtrace_size);
-  EXPECT_EQ(nullptr, info);
-  EXPECT_EQ(0U, overall_size);
-  EXPECT_EQ(0U, info_size);
-  EXPECT_EQ(0U, total_memory);
-  EXPECT_EQ(0U, backtrace_size);
+  android_mallopt_leak_info_t leak_info;
+  ASSERT_TRUE(android_mallopt(M_GET_MALLOC_LEAK_INFO, &leak_info, sizeof(leak_info)));
+  EXPECT_EQ(nullptr, leak_info.buffer);
+  EXPECT_EQ(0U, leak_info.overall_size);
+  EXPECT_EQ(0U, leak_info.info_size);
+  EXPECT_EQ(0U, leak_info.total_memory);
+  EXPECT_EQ(0U, leak_info.backtrace_size);
 
-  free_malloc_leak_info(info);
+  ASSERT_TRUE(android_mallopt(M_FREE_MALLOC_LEAK_INFO, &leak_info, sizeof(leak_info)));
 
   malloc_enable();
   malloc_disable();
diff --git a/libc/private/bionic_malloc.h b/libc/private/bionic_malloc.h
index e8a6f6e..9c602ea 100644
--- a/libc/private/bionic_malloc.h
+++ b/libc/private/bionic_malloc.h
@@ -30,6 +30,22 @@
 
 #include <stdbool.h>
 
+// Structures for android_mallopt.
+
+typedef struct {
+  // Pointer to the buffer allocated by a call to M_GET_MALLOC_LEAK_INFO.
+  uint8_t* buffer;
+  // The size of the "info" buffer.
+  size_t overall_size;
+  // The size of a single entry.
+  size_t info_size;
+  // The sum of all allocations that have been tracked. Does not include
+  // any heap overhead.
+  size_t total_memory;
+  // The maximum number of backtrace entries.
+  size_t backtrace_size;
+} android_mallopt_leak_info_t;
+
 // Opcodes for android_mallopt.
 
 enum {
@@ -48,6 +64,26 @@
   // Called after the zygote forks to indicate this is a child.
   M_SET_ZYGOTE_CHILD = 4,
 #define M_SET_ZYGOTE_CHILD M_SET_ZYGOTE_CHILD
+
+  // Options to dump backtraces of allocations. These options only
+  // work when malloc debug has been enabled.
+
+  // Writes the backtrace information of all current allocations to a file.
+  // NOTE: arg_size has to be sizeof(FILE*) because FILE is an opaque type.
+  //   arg = FILE*
+  //   arg_size = sizeof(FILE*)
+  M_WRITE_MALLOC_LEAK_INFO_TO_FILE = 5,
+#define M_WRITE_MALLOC_LEAK_INFO_TO_FILE M_WRITE_MALLOC_LEAK_INFO_TO_FILE
+  // Get information about the backtraces of all
+  //   arg = android_mallopt_leak_info_t*
+  //   arg_size = sizeof(android_mallopt_leak_info_t)
+  M_GET_MALLOC_LEAK_INFO = 6,
+#define M_GET_MALLOC_LEAK_INFO M_GET_MALLOC_LEAK_INFO
+  // Free the memory allocated and returned by M_GET_MALLOC_LEAK_INFO.
+  //   arg = android_mallopt_leak_info_t*
+  //   arg_size = sizeof(android_mallopt_leak_info_t)
+  M_FREE_MALLOC_LEAK_INFO = 7,
+#define M_FREE_MALLOC_LEAK_INFO M_FREE_MALLOC_LEAK_INFO
 };
 
 // Manipulates bionic-specific handling of memory allocation APIs such as
diff --git a/libc/stdlib/atexit.c b/libc/stdlib/atexit.c
index bd6ac3d..692e0c0 100644
--- a/libc/stdlib/atexit.c
+++ b/libc/stdlib/atexit.c
@@ -186,7 +186,10 @@
 	}
 	_ATEXIT_UNLOCK();
 
-  fflush(NULL);
+	/* If called via exit(), flush output of all open files. */
+	if (dso == NULL) {
+		fflush(NULL);
+	}
 
   /* BEGIN android-changed: call __unregister_atfork if dso is not null */
   if (dso != NULL) {
diff --git a/libdl/libdl_static.cpp b/libdl/libdl_static.cpp
index 7146762..0a36e6f 100644
--- a/libdl/libdl_static.cpp
+++ b/libdl/libdl_static.cpp
@@ -23,7 +23,7 @@
 }
 
 char* dlerror() {
-  return nullptr;
+  return const_cast<char*>("libdl.a is a stub --- use libdl.so instead");
 }
 
 void* dlsym(void* /*handle*/, const char* /*symbol*/) {
diff --git a/tests/Android.bp b/tests/Android.bp
index 71cf8a6..85bb29a 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -184,6 +184,7 @@
         "system_properties_test2.cpp",
         "termios_test.cpp",
         "tgmath_test.c",
+        "threads_test.cpp",
         "time_test.cpp",
         "uchar_test.cpp",
         "unistd_nofortify_test.cpp",
diff --git a/tests/headers/posix/threads_h.c b/tests/headers/posix/threads_h.c
new file mode 100644
index 0000000..c9329f4
--- /dev/null
+++ b/tests/headers/posix/threads_h.c
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if __has_include(<threads.h>)
+
+#include <threads.h>
+
+#include "header_checks.h"
+
+thread_local int t;
+
+static void threads_h() {
+  MACRO(ONCE_FLAG_INIT);
+  MACRO(TSS_DTOR_ITERATIONS);
+
+  TYPE(cnd_t);
+  TYPE(thrd_t);
+  TYPE(tss_t);
+  TYPE(mtx_t);
+
+  TYPE(tss_dtor_t);
+  TYPE(thrd_start_t);
+
+  TYPE(once_flag);
+
+  int enumeration_constants = mtx_plain | mtx_recursive | mtx_timed |
+      thrd_timedout | thrd_success | thrd_busy | thrd_error | thrd_nomem;
+
+  FUNCTION(call_once, void (*f)(once_flag*, void (*)(void)));
+
+  FUNCTION(cnd_broadcast, int (*f)(cnd_t*));
+  FUNCTION(cnd_destroy, void (*f)(cnd_t*));
+  FUNCTION(cnd_init, int (*f)(cnd_t*));
+  FUNCTION(cnd_signal, int (*f)(cnd_t*));
+  FUNCTION(cnd_timedwait, int (*f)(cnd_t*, mtx_t*, const struct timespec*));
+  FUNCTION(cnd_wait, int (*f)(cnd_t*, mtx_t*));
+
+  FUNCTION(mtx_destroy, void (*f)(mtx_t*));
+  FUNCTION(mtx_init, int (*f)(mtx_t*, int));
+  FUNCTION(mtx_lock, int (*f)(mtx_t*));
+  FUNCTION(mtx_timedlock, int (*f)(mtx_t*, const struct timespec*));
+  FUNCTION(mtx_trylock, int (*f)(mtx_t*));
+  FUNCTION(mtx_unlock, int (*f)(mtx_t*));
+
+  FUNCTION(thrd_create, int (*f)(thrd_t*, thrd_start_t, void*));
+  FUNCTION(thrd_current, thrd_t (*f)(void));
+  FUNCTION(thrd_detach, int (*f)(thrd_t));
+  FUNCTION(thrd_equal, int (*f)(thrd_t, thrd_t));
+  FUNCTION(thrd_exit, void (*f)(int));
+  FUNCTION(thrd_join, int (*f)(thrd_t, int*));
+  FUNCTION(thrd_sleep, int (*f)(const struct timespec*, struct timespec*));
+  FUNCTION(thrd_yield, void (*f)(void));
+
+  FUNCTION(tss_create, int (*f)(tss_t*, tss_dtor_t));
+  FUNCTION(tss_delete, void (*f)(tss_t));
+  FUNCTION(tss_get, void* (*f)(tss_t));
+  FUNCTION(tss_set, int (*f)(tss_t, void*));
+}
+
+#define DO_NOT_INCLUDE_TIME_H
+#include "time_h.c"
+
+#endif
diff --git a/tests/run-on-host.sh b/tests/run-on-host.sh
index c8a30f5..fe2c25a 100755
--- a/tests/run-on-host.sh
+++ b/tests/run-on-host.sh
@@ -20,6 +20,7 @@
 if [ ${HOST_OS}-${HOST_ARCH} = linux-x86 -o ${HOST_OS}-${HOST_ARCH} = linux-x86_64 ]; then
 
   prepare $1 bionic-unit-tests
+  shift
 
   if [ ${TARGET_ARCH} = x86 -o ${TARGET_ARCH} = x86_64 ]; then
     (
diff --git a/tests/threads_test.cpp b/tests/threads_test.cpp
new file mode 100644
index 0000000..5fafff3
--- /dev/null
+++ b/tests/threads_test.cpp
@@ -0,0 +1,515 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <gtest/gtest.h>
+
+#if __has_include(<threads.h>)
+
+#define HAVE_THREADS_H
+#include <threads.h>
+
+static int g_call_once_call_count;
+
+static void increment_call_count() {
+  ++g_call_once_call_count;
+}
+
+static int g_dtor_call_count;
+
+static void tss_dtor(void* ptr) {
+  ++g_dtor_call_count;
+  free(ptr);
+}
+
+static int return_arg(void* arg) {
+  return static_cast<int>(reinterpret_cast<uintptr_t>(arg));
+}
+
+static int exit_arg(void* arg) {
+  thrd_exit(static_cast<int>(reinterpret_cast<uintptr_t>(arg)));
+}
+
+#endif
+
+#include <time.h>
+
+#include <thread>
+
+#include "BionicDeathTest.h"
+#include "SignalUtils.h"
+
+TEST(threads, call_once) {
+#if !defined(HAVE_THREADS_H)
+  GTEST_SKIP() << "<threads.h> unavailable";
+#else
+  once_flag flag = ONCE_FLAG_INIT;
+  call_once(&flag, increment_call_count);
+  call_once(&flag, increment_call_count);
+  std::thread([&flag] {
+    call_once(&flag, increment_call_count);
+  }).join();
+  ASSERT_EQ(1, g_call_once_call_count);
+#endif
+}
+
+TEST(threads, cnd_broadcast__cnd_wait) {
+#if !defined(HAVE_THREADS_H)
+  GTEST_SKIP() << "<threads.h> unavailable";
+#else
+  mtx_t m;
+  ASSERT_EQ(thrd_success, mtx_init(&m, mtx_plain));
+
+  cnd_t c;
+  ASSERT_EQ(thrd_success, cnd_init(&c));
+
+  std::atomic_int i = 0;
+
+  auto waiter = [&c, &i, &m] {
+    ASSERT_EQ(thrd_success, mtx_lock(&m));
+    while (i != 1) ASSERT_EQ(thrd_success, cnd_wait(&c, &m));
+    ASSERT_EQ(thrd_success, mtx_unlock(&m));
+  };
+  std::thread t1(waiter);
+  std::thread t2(waiter);
+  std::thread t3(waiter);
+
+  ASSERT_EQ(thrd_success, mtx_lock(&m));
+  i = 1;
+  ASSERT_EQ(thrd_success, mtx_unlock(&m));
+
+  ASSERT_EQ(thrd_success, cnd_broadcast(&c));
+
+  t1.join();
+  t2.join();
+  t3.join();
+
+  mtx_destroy(&m);
+  cnd_destroy(&c);
+#endif
+}
+
+TEST(threads, cnd_init__cnd_destroy) {
+#if !defined(HAVE_THREADS_H)
+  GTEST_SKIP() << "<threads.h> unavailable";
+#else
+  cnd_t c;
+  ASSERT_EQ(thrd_success, cnd_init(&c));
+  cnd_destroy(&c);
+#endif
+}
+
+TEST(threads, cnd_signal__cnd_wait) {
+#if !defined(HAVE_THREADS_H)
+  GTEST_SKIP() << "<threads.h> unavailable";
+#else
+  mtx_t m;
+  ASSERT_EQ(thrd_success, mtx_init(&m, mtx_plain));
+  cnd_t c;
+  ASSERT_EQ(thrd_success, cnd_init(&c));
+
+  std::atomic_int count = 0;
+  auto waiter = [&c, &m, &count] {
+    ASSERT_EQ(thrd_success, mtx_lock(&m));
+    ASSERT_EQ(thrd_success, cnd_wait(&c, &m));
+    ASSERT_EQ(thrd_success, mtx_unlock(&m));
+    ++count;
+  };
+  std::thread t1(waiter);
+  std::thread t2(waiter);
+  std::thread t3(waiter);
+
+  // This is inherently racy, but attempts to distinguish between cnd_signal and
+  // cnd_broadcast.
+  usleep(100000);
+  ASSERT_EQ(thrd_success, cnd_signal(&c));
+  while (count == 0) {
+  }
+  usleep(100000);
+  ASSERT_EQ(1, count);
+
+  ASSERT_EQ(thrd_success, cnd_signal(&c));
+  while (count == 1) {
+  }
+  usleep(100000);
+  ASSERT_EQ(2, count);
+
+  ASSERT_EQ(thrd_success, cnd_signal(&c));
+  while (count == 2) {
+  }
+  usleep(100000);
+  ASSERT_EQ(3, count);
+
+  t1.join();
+  t2.join();
+  t3.join();
+
+  mtx_destroy(&m);
+  cnd_destroy(&c);
+#endif
+}
+
+TEST(threads, cnd_timedwait_timedout) {
+#if !defined(HAVE_THREADS_H)
+  GTEST_SKIP() << "<threads.h> unavailable";
+#else
+  mtx_t m;
+  ASSERT_EQ(thrd_success, mtx_init(&m, mtx_timed));
+  ASSERT_EQ(thrd_success, mtx_lock(&m));
+
+  cnd_t c;
+  ASSERT_EQ(thrd_success, cnd_init(&c));
+
+  timespec ts = {};
+  ASSERT_EQ(thrd_timedout, cnd_timedwait(&c, &m, &ts));
+#endif
+}
+
+TEST(threads, cnd_timedwait) {
+#if !defined(HAVE_THREADS_H)
+  GTEST_SKIP() << "<threads.h> unavailable";
+#else
+  mtx_t m;
+  ASSERT_EQ(thrd_success, mtx_init(&m, mtx_timed));
+
+  cnd_t c;
+  ASSERT_EQ(thrd_success, cnd_init(&c));
+
+  std::atomic_bool done = false;
+  std::thread t([&c, &m, &done] {
+    ASSERT_EQ(thrd_success, mtx_lock(&m));
+
+    // cnd_timewait's time is *absolute*.
+    timespec ts;
+    ASSERT_EQ(TIME_UTC, timespec_get(&ts, TIME_UTC));
+    ts.tv_sec += 666;
+
+    ASSERT_EQ(thrd_success, cnd_timedwait(&c, &m, &ts));
+    done = true;
+    ASSERT_EQ(thrd_success, mtx_unlock(&m));
+  });
+
+  while (!done) ASSERT_EQ(thrd_success, cnd_signal(&c));
+
+  t.join();
+#endif
+}
+
+TEST(threads, mtx_init) {
+#if !defined(HAVE_THREADS_H)
+  GTEST_SKIP() << "<threads.h> unavailable";
+#else
+  mtx_t m;
+  ASSERT_EQ(thrd_success, mtx_init(&m, mtx_plain));
+  ASSERT_EQ(thrd_success, mtx_init(&m, mtx_timed));
+  ASSERT_EQ(thrd_success, mtx_init(&m, mtx_plain | mtx_recursive));
+  ASSERT_EQ(thrd_success, mtx_init(&m, mtx_timed | mtx_recursive));
+  ASSERT_EQ(thrd_error, mtx_init(&m, 123));
+  ASSERT_EQ(thrd_error, mtx_init(&m, mtx_recursive));
+#endif
+}
+
+TEST(threads, mtx_destroy) {
+#if !defined(HAVE_THREADS_H)
+  GTEST_SKIP() << "<threads.h> unavailable";
+#else
+  mtx_t m;
+  ASSERT_EQ(thrd_success, mtx_init(&m, mtx_plain));
+  mtx_destroy(&m);
+#endif
+}
+
+TEST(threads, mtx_lock_plain) {
+#if !defined(HAVE_THREADS_H)
+  GTEST_SKIP() << "<threads.h> unavailable";
+#else
+  mtx_t m;
+  ASSERT_EQ(thrd_success, mtx_init(&m, mtx_plain));
+
+  ASSERT_EQ(thrd_success, mtx_lock(&m));
+  ASSERT_EQ(thrd_busy, mtx_trylock(&m));
+  ASSERT_EQ(thrd_success, mtx_unlock(&m));
+
+  mtx_destroy(&m);
+#endif
+}
+
+TEST(threads, mtx_lock_recursive) {
+#if !defined(HAVE_THREADS_H)
+  GTEST_SKIP() << "<threads.h> unavailable";
+#else
+  mtx_t m;
+  ASSERT_EQ(thrd_success, mtx_init(&m, mtx_plain | mtx_recursive));
+
+  ASSERT_EQ(thrd_success, mtx_lock(&m));
+  ASSERT_EQ(thrd_success, mtx_trylock(&m));
+  ASSERT_EQ(thrd_success, mtx_unlock(&m));
+  ASSERT_EQ(thrd_success, mtx_unlock(&m));
+
+  mtx_destroy(&m);
+#endif
+}
+
+TEST(threads, mtx_timedlock) {
+#if !defined(HAVE_THREADS_H)
+  GTEST_SKIP() << "<threads.h> unavailable";
+#else
+  mtx_t m;
+  ASSERT_EQ(thrd_success, mtx_init(&m, mtx_timed));
+
+  timespec ts = {};
+  ASSERT_EQ(thrd_success, mtx_timedlock(&m, &ts));
+
+  std::thread([&m] {
+    timespec ts = { .tv_nsec = 500000 };
+    ASSERT_EQ(thrd_timedout, mtx_timedlock(&m, &ts));
+  }).join();
+
+  ASSERT_EQ(thrd_success, mtx_unlock(&m));
+  mtx_destroy(&m);
+#endif
+}
+
+
+TEST(threads, mtx_unlock) {
+#if !defined(HAVE_THREADS_H)
+  GTEST_SKIP() << "<threads.h> unavailable";
+#else
+  mtx_t m;
+  ASSERT_EQ(thrd_success, mtx_init(&m, mtx_plain));
+  ASSERT_EQ(thrd_success, mtx_lock(&m));
+  std::thread([&m] {
+    ASSERT_EQ(thrd_busy, mtx_trylock(&m));
+  }).join();
+  ASSERT_EQ(thrd_success, mtx_unlock(&m));
+  std::thread([&m] {
+    ASSERT_EQ(thrd_success, mtx_trylock(&m));
+  }).join();
+#endif
+}
+
+TEST(threads, thrd_current__thrd_equal) {
+#if !defined(HAVE_THREADS_H)
+  GTEST_SKIP() << "<threads.h> unavailable";
+#else
+  thrd_t t1 = thrd_current();
+  // (As a side-effect, this demonstrates interoperability with std::thread.)
+  std::thread([&t1] {
+    thrd_t t2 = thrd_current();
+    ASSERT_FALSE(thrd_equal(t1, t2));
+    thrd_t t2_2 = thrd_current();
+    ASSERT_TRUE(thrd_equal(t2, t2_2));
+  }).join();
+  thrd_t t1_2 = thrd_current();
+  ASSERT_TRUE(thrd_equal(t1, t1_2));
+#endif
+}
+
+TEST(threads, thrd_create__thrd_detach) {
+#if !defined(HAVE_THREADS_H)
+  GTEST_SKIP() << "<threads.h> unavailable";
+#else
+  thrd_t t;
+  ASSERT_EQ(thrd_success, thrd_create(&t, exit_arg, reinterpret_cast<void*>(1)));
+  ASSERT_EQ(thrd_success, thrd_detach(t));
+#endif
+}
+
+TEST(threads, thrd_create__thrd_exit) {
+#if !defined(HAVE_THREADS_H)
+  GTEST_SKIP() << "<threads.h> unavailable";
+#else
+  // Similar to the thrd_join test, but with a function that calls thrd_exit
+  // instead.
+  thrd_t t;
+  int result;
+  ASSERT_EQ(thrd_success, thrd_create(&t, exit_arg, reinterpret_cast<void*>(1)));
+  ASSERT_EQ(thrd_success, thrd_join(t, &result));
+  ASSERT_EQ(1, result);
+
+  ASSERT_EQ(thrd_success, thrd_create(&t, exit_arg, reinterpret_cast<void*>(2)));
+  ASSERT_EQ(thrd_success, thrd_join(t, &result));
+  ASSERT_EQ(2, result);
+
+  // The `result` argument can be null if you don't care...
+  ASSERT_EQ(thrd_success, thrd_create(&t, exit_arg, reinterpret_cast<void*>(3)));
+  ASSERT_EQ(thrd_success, thrd_join(t, nullptr));
+#endif
+}
+
+class threads_DeathTest : public BionicDeathTest {};
+
+TEST(threads_DeathTest, thrd_exit_main_thread) {
+#if !defined(HAVE_THREADS_H)
+  GTEST_SKIP() << "<threads.h> unavailable";
+#else
+  // "The program terminates normally after the last thread has been terminated.
+  // The behavior is as if the program called the exit function with the status
+  // EXIT_SUCCESS at thread termination time." (ISO/IEC 9899:2018)
+  ASSERT_EXIT(thrd_exit(12), ::testing::ExitedWithCode(EXIT_SUCCESS), "");
+#endif
+}
+
+TEST(threads, thrd_create__thrd_join) {
+#if !defined(HAVE_THREADS_H)
+  GTEST_SKIP() << "<threads.h> unavailable";
+#else
+  // Similar to the thrd_exit test, but with a function that calls return
+  // instead.
+  thrd_t t;
+  int result;
+  ASSERT_EQ(thrd_success, thrd_create(&t, return_arg, reinterpret_cast<void*>(1)));
+  ASSERT_EQ(thrd_success, thrd_join(t, &result));
+  ASSERT_EQ(1, result);
+
+  ASSERT_EQ(thrd_success, thrd_create(&t, return_arg, reinterpret_cast<void*>(2)));
+  ASSERT_EQ(thrd_success, thrd_join(t, &result));
+  ASSERT_EQ(2, result);
+
+  // The `result` argument can be null if you don't care...
+  ASSERT_EQ(thrd_success, thrd_create(&t, return_arg, reinterpret_cast<void*>(3)));
+  ASSERT_EQ(thrd_success, thrd_join(t, nullptr));
+#endif
+}
+
+TEST(threads, thrd_sleep_signal) {
+#if !defined(HAVE_THREADS_H)
+  GTEST_SKIP() << "<threads.h> unavailable";
+#else
+  ScopedSignalHandler ssh{SIGALRM, [](int) {}};
+  std::thread t([] {
+    timespec long_time = { .tv_sec = 666 };
+    timespec remaining = {};
+    ASSERT_EQ(-1, thrd_sleep(&long_time, &remaining));
+    uint64_t t = remaining.tv_sec * 1000000000 + remaining.tv_nsec;
+    ASSERT_LE(t, 666ULL * 1000000000);
+  });
+  usleep(100000); // 0.1s
+  pthread_kill(t.native_handle(), SIGALRM);
+  t.join();
+#endif
+}
+
+TEST(threads, thrd_sleep_signal_nullptr) {
+#if !defined(HAVE_THREADS_H)
+  GTEST_SKIP() << "<threads.h> unavailable";
+#else
+  ScopedSignalHandler ssh{SIGALRM, [](int) {}};
+  std::thread t([] {
+    timespec long_time = { .tv_sec = 666 };
+    ASSERT_EQ(-1, thrd_sleep(&long_time, nullptr));
+  });
+  usleep(100000); // 0.1s
+  pthread_kill(t.native_handle(), SIGALRM);
+  t.join();
+#endif
+}
+
+TEST(threads, thrd_sleep_error) {
+#if !defined(HAVE_THREADS_H)
+  GTEST_SKIP() << "<threads.h> unavailable";
+#else
+  timespec invalid = { .tv_sec = -1 };
+  ASSERT_EQ(-2, thrd_sleep(&invalid, nullptr));
+#endif
+}
+
+TEST(threads, thrd_yield) {
+#if !defined(HAVE_THREADS_H)
+  GTEST_SKIP() << "<threads.h> unavailable";
+#else
+  thrd_yield();
+#endif
+}
+
+TEST(threads, TSS_DTOR_ITERATIONS_macro) {
+#if !defined(HAVE_THREADS_H)
+  GTEST_SKIP() << "<threads.h> unavailable";
+#else
+  ASSERT_EQ(PTHREAD_DESTRUCTOR_ITERATIONS, TSS_DTOR_ITERATIONS);
+#endif
+}
+
+TEST(threads, tss_create) {
+#if !defined(HAVE_THREADS_H)
+  GTEST_SKIP() << "<threads.h> unavailable";
+#else
+  tss_t key;
+  ASSERT_EQ(thrd_success, tss_create(&key, nullptr));
+  tss_delete(key);
+#endif
+}
+
+TEST(threads, tss_create_dtor) {
+#if !defined(HAVE_THREADS_H)
+  GTEST_SKIP() << "<threads.h> unavailable";
+#else
+  tss_dtor_t dtor = tss_dtor;
+  tss_t key;
+  ASSERT_EQ(thrd_success, tss_create(&key, dtor));
+
+  ASSERT_EQ(thrd_success, tss_set(key, strdup("hello")));
+  std::thread([&key] {
+    ASSERT_EQ(thrd_success, tss_set(key, strdup("world")));
+  }).join();
+  // Thread exit calls the destructor...
+  ASSERT_EQ(1, g_dtor_call_count);
+
+  // "[A call to tss_set] will not invoke the destructor associated with the
+  // key on the value being replaced" (ISO/IEC 9899:2018).
+  g_dtor_call_count = 0;
+  ASSERT_EQ(thrd_success, tss_set(key, strdup("hello")));
+  ASSERT_EQ(0, g_dtor_call_count);
+
+  // "Calling tss_delete will not result in the invocation of any
+  // destructors" (ISO/IEC 9899:2018).
+  // The destructor for "hello" won't be called until *this* thread exits.
+  g_dtor_call_count = 0;
+  tss_delete(key);
+  ASSERT_EQ(0, g_dtor_call_count);
+#endif
+}
+
+TEST(threads, tss_get__tss_set) {
+#if !defined(HAVE_THREADS_H)
+  GTEST_SKIP() << "<threads.h> unavailable";
+#else
+  tss_t key;
+  ASSERT_EQ(thrd_success, tss_create(&key, nullptr));
+
+  ASSERT_EQ(thrd_success, tss_set(key, const_cast<char*>("hello")));
+  ASSERT_STREQ("hello", reinterpret_cast<char*>(tss_get(key)));
+  std::thread([&key] {
+      ASSERT_EQ(nullptr, tss_get(key));
+      ASSERT_EQ(thrd_success, tss_set(key, const_cast<char*>("world")));
+      ASSERT_STREQ("world", reinterpret_cast<char*>(tss_get(key)));
+  }).join();
+  ASSERT_STREQ("hello", reinterpret_cast<char*>(tss_get(key)));
+
+  tss_delete(key);
+#endif
+}