[libc-commits] [libc] [llvm] [libc] Implement pthread_cleanup_push and pthread_cleanup_pop (PR #223427)

Schrodinger ZHU Yifan via libc-commits libc-commits at lists.llvm.org
Mon Sep 14 10:52:01 PDT 2026


================
@@ -0,0 +1,219 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Tests for pthread_cleanup_push and pthread_cleanup_pop.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/pthread/__pthread_cleanup_pop.h"
+#include "src/pthread/__pthread_cleanup_push.h"
+#include "src/pthread/pthread_create.h"
+#include "src/pthread/pthread_exit.h"
+#include "src/pthread/pthread_getspecific.h"
+#include "src/pthread/pthread_join.h"
+#include "src/pthread/pthread_key_create.h"
+#include "src/pthread/pthread_key_delete.h"
+#include "src/pthread/pthread_setspecific.h"
+#include "test/IntegrationTest/test.h"
+
+#include "hdr/pthread_macros.h"
+
+// To make pthread_cleanup_push/pop work outside of LIBC_NAMESPACE
+#define __pthread_cleanup_push LIBC_NAMESPACE::__pthread_cleanup_push
+#define __pthread_cleanup_pop LIBC_NAMESPACE::__pthread_cleanup_pop
+
+static int call_order = 0;
+static void record_routine(void *arg) {
+  *reinterpret_cast<int *>(arg) = ++call_order;
+}
+
+// 1. Test direct pop with execute = 0 and execute = 1 on the main thread.
+static void test_direct_pop() {
+  int val = 0;
+  call_order = 0;
+  pthread_cleanup_push(record_routine, &val);
+  pthread_cleanup_pop(0);
+  ASSERT_EQ(val, 0);
+
+  pthread_cleanup_push(record_routine, &val);
+  pthread_cleanup_pop(1);
+  ASSERT_EQ(val, 1);
+}
+
+// 2. Test nested push / pop blocks in LIFO order on the main thread.
+static void test_nested_pop() {
+  int order[2] = {0, 0};
+  call_order = 0;
+
+  pthread_cleanup_push(record_routine, &order[1]);
+  pthread_cleanup_push(record_routine, &order[0]);
+  pthread_cleanup_pop(1);
+  pthread_cleanup_pop(1);
+
+  ASSERT_EQ(order[0], 1);
+  ASSERT_EQ(order[1], 2);
+}
+
+// 3. Test sequential push / pop blocks in the same scope.
+static void test_sequential_pop() {
+  int first = 0;
+  int second = 0;
+  call_order = 0;
+
+  pthread_cleanup_push(record_routine, &first);
+  pthread_cleanup_pop(1);
+  pthread_cleanup_push(record_routine, &second);
+  pthread_cleanup_pop(1);
+
+  ASSERT_EQ(first, 1);
+  ASSERT_EQ(second, 2);
+}
+
+// 4. Test pthread_exit running handlers in LIFO order.
+static int exit_order[3] = {0, 0, 0};
+
+static void *thread_exit_lifo_func(void *) {
+  pthread_cleanup_push(record_routine, &exit_order[2]);
+  pthread_cleanup_push(record_routine, &exit_order[1]);
+  pthread_cleanup_push(record_routine, &exit_order[0]);
+
+  LIBC_NAMESPACE::pthread_exit(nullptr);
+
+  pthread_cleanup_pop(0);
+  pthread_cleanup_pop(0);
+  pthread_cleanup_pop(0);
+  return nullptr;
+}
+
+static void test_exit_lifo() {
+  pthread_t th;
+  void *retval = nullptr;
+
+  call_order = 0;
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_create(&th, nullptr, thread_exit_lifo_func,
+                                           nullptr),
+            0);
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_join(th, &retval), 0);
+
+  ASSERT_EQ(exit_order[0], 1);
+  ASSERT_EQ(exit_order[1], 2);
+  ASSERT_EQ(exit_order[2], 3);
+}
+
+// 5. Test that handlers popped prior to pthread_exit are not executed.
+static int exit_called1 = 0;
+static int exit_called2 = 0;
+
+static void *thread_popped_func(void *) {
+  pthread_cleanup_push(record_routine, &exit_called1);
+  pthread_cleanup_push(record_routine, &exit_called2);
+  pthread_cleanup_pop(0); // Pop handler2 without executing
+
+  LIBC_NAMESPACE::pthread_exit(nullptr);
+
+  pthread_cleanup_pop(0);
+  return nullptr;
+}
+
+static void test_exit_popped_not_called() {
+  pthread_t th;
+  void *retval = nullptr;
+
+  call_order = 0;
+  ASSERT_EQ(
+      LIBC_NAMESPACE::pthread_create(&th, nullptr, thread_popped_func, nullptr),
+      0);
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_join(th, &retval), 0);
+
+  ASSERT_EQ(exit_called1, 1);
+  ASSERT_EQ(exit_called2, 0);
+}
+
+// 6. Test that cleanup handlers run before TSS destructors.
+static pthread_key_t tss_key;
+static int global_sequence = 0;
+static int cleanup_sequence = 0;
+static int tss_sequence = 0;
+static int dummy_tss_val = 99;
+
+static void tss_destructor(void *) { tss_sequence = ++global_sequence; }
+
+static void tss_cleanup_handler(void *) {
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_getspecific(tss_key), &dummy_tss_val);
+  cleanup_sequence = ++global_sequence;
+}
+
+static void *thread_tss_order_func(void *) {
+  LIBC_NAMESPACE::pthread_setspecific(tss_key, &dummy_tss_val);
+  pthread_cleanup_push(tss_cleanup_handler, nullptr);
+
+  LIBC_NAMESPACE::pthread_exit(nullptr);
+
+  pthread_cleanup_pop(0);
+  return nullptr;
+}
+
+static void test_exit_order_before_tss() {
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_key_create(&tss_key, tss_destructor), 0);
+
+  pthread_t th;
+  void *retval = nullptr;
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_create(&th, nullptr, thread_tss_order_func,
+                                           nullptr),
+            0);
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_join(th, &retval), 0);
+
+  ASSERT_EQ(cleanup_sequence, 1);
+  ASSERT_EQ(tss_sequence, 2);
+
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_key_delete(tss_key), 0);
+}
+
+// 7. Test re-entrancy: a cleanup handler pushes and pops an inner handler.
+static bool outer_called = false;
+static bool inner_called = false;
+
+static void inner_handler(void *) { inner_called = true; }
+
+static void outer_handler(void *) {
+  outer_called = true;
+  pthread_cleanup_push(inner_handler, nullptr);
+  pthread_cleanup_pop(1);
+}
+
+static void *thread_reentrancy_func(void *) {
+  pthread_cleanup_push(outer_handler, nullptr);
+  LIBC_NAMESPACE::pthread_exit(nullptr);
+
+  pthread_cleanup_pop(0);
+  return nullptr;
+}
+
+static void test_reentrancy() {
+  pthread_t th;
+  void *retval = nullptr;
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_create(&th, nullptr, thread_reentrancy_func,
+                                           nullptr),
+            0);
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_join(th, &retval), 0);
+
+  ASSERT_TRUE(outer_called);
+  ASSERT_TRUE(inner_called);
+}
+
+TEST_MAIN() {
----------------
SchrodingerZhu wrote:

Could you add a clean up test for releasing mutexes?

https://github.com/llvm/llvm-project/pull/223427


More information about the libc-commits mailing list