From patchwork Fri Aug 20 16:12:49 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Florian Weimer X-Patchwork-Id: 44727 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 142B039B90A1 for ; Fri, 20 Aug 2021 16:13:31 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 142B039B90A1 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=sourceware.org; s=default; t=1629476011; bh=zAfRpY0yDy2FUoq2bfpLEElwH0kgWj0BbhpA+zzHHZ4=; h=To:Subject:In-Reply-To:References:Date:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=hgzFaRHelR2JHGSP5BuuL+jV8kzXX+P5Z84a8AJZog1k2KHkplbPiBkUImB1Eh3Wi lg1da8mj5ksL0HL1QC+3/VjotUyflVp8RECdtGnF03YcEVN8SN6n8lWeWS+nraLMym Zo4RkDPJ0aCV81/tWB8OUxcvwsPnBpAV3wnVzlyc= X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTP id 1B96339C0005 for ; Fri, 20 Aug 2021 16:12:55 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.1 sourceware.org 1B96339C0005 Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-518-GPKC2OmQOpiR_iu0mRJWPw-1; Fri, 20 Aug 2021 12:12:53 -0400 X-MC-Unique: GPKC2OmQOpiR_iu0mRJWPw-1 Received: from smtp.corp.redhat.com (int-mx07.intmail.prod.int.phx2.redhat.com [10.5.11.22]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 4726B5F9EB for ; Fri, 20 Aug 2021 16:12:52 +0000 (UTC) Received: from oldenburg.str.redhat.com (unknown [10.39.194.2]) by smtp.corp.redhat.com (Postfix) with ESMTPS id A117A10190AA for ; Fri, 20 Aug 2021 16:12:51 +0000 (UTC) To: libc-alpha@sourceware.org Subject: [PATCH v2 1/4] support: Add support_wait_for_thread_exit In-Reply-To: References: X-From-Line: 59c56a95c559ed9b855965050fe4d1d9f3ad74ba Mon Sep 17 00:00:00 2001 Message-Id: <59c56a95c559ed9b855965050fe4d1d9f3ad74ba.1629475813.git.fweimer@redhat.com> Date: Fri, 20 Aug 2021 18:12:49 +0200 User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/27.2 (gnu/linux) MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.84 on 10.5.11.22 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-12.9 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_LOW, RCVD_IN_MSPIKE_H2, SPF_HELO_NONE, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.4 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on server2.sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Florian Weimer via Libc-alpha From: Florian Weimer Reply-To: Florian Weimer Errors-To: libc-alpha-bounces+patchwork=sourceware.org@sourceware.org Sender: "Libc-alpha" --- v2: Makefile update, make it clearer where the wait operation with pidfd_open would be put (it would replace the short usleep call). support/Makefile | 3 +- support/support.h | 4 ++ support/support_wait_for_thread_exit.c | 72 ++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 support/support_wait_for_thread_exit.c diff --git a/support/Makefile b/support/Makefile index a462781718..ef2b1a980a 100644 --- a/support/Makefile +++ b/support/Makefile @@ -82,9 +82,10 @@ libsupport-routines = \ support_test_compare_blob \ support_test_compare_failure \ support_test_compare_string \ - support_write_file_string \ support_test_main \ support_test_verify_impl \ + support_wait_for_thread_exit \ + support_write_file_string \ temp_file \ timespec \ timespec-time64 \ diff --git a/support/support.h b/support/support.h index 834dba9097..a5978b939a 100644 --- a/support/support.h +++ b/support/support.h @@ -174,6 +174,10 @@ timer_t support_create_timer (uint64_t sec, long int nsec, bool repeat, /* Disable the timer TIMER. */ void support_delete_timer (timer_t timer); +/* Wait until all threads except the current thread have exited (as + far as the kernel is concerned). */ +void support_wait_for_thread_exit (void); + struct support_stack { void *stack; diff --git a/support/support_wait_for_thread_exit.c b/support/support_wait_for_thread_exit.c new file mode 100644 index 0000000000..658a813810 --- /dev/null +++ b/support/support_wait_for_thread_exit.c @@ -0,0 +1,72 @@ +/* Wait until all threads except the current thread has exited. + Copyright (C) 2021 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include +#include +#include +#include +#include + +void +support_wait_for_thread_exit (void) +{ +#ifdef __linux__ + DIR *proc_self_task = opendir ("/proc/self/task"); + TEST_VERIFY_EXIT (proc_self_task != NULL); + + while (true) + { + errno = 0; + struct dirent *e = readdir (proc_self_task); + if (e == NULL && errno != 0) + FAIL_EXIT1 ("readdir: %m"); + if (e == NULL) + { + /* Only the main thread remains. Testing may continue. */ + closedir (proc_self_task); + return; + } + + if (strcmp (e->d_name, ".") == 0 || strcmp (e->d_name, "..") == 0) + continue; + + int task_tid = atoi (e->d_name); + if (task_tid <= 0) + FAIL_EXIT1 ("Invalid /proc/self/task entry: %s", e->d_name); + + if (task_tid == gettid ()) + /* The current thread. Keep scanning for other + threads. */ + continue; + + /* task_tid does not refer to this thread here, i.e., there is + another running thread. */ + + /* Small timeout to give the thread a chance to exit. */ + usleep (50 * 1000); + + /* Start scanning the directory from the start. */ + rewinddir (proc_self_task); + } +#else + /* Use a large timeout because we cannot verify that the thread has + exited. */ + usleep (5 * 1000 * 1000); +#endif +} From patchwork Fri Aug 20 16:13:06 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Florian Weimer X-Patchwork-Id: 44728 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id D274839B90A1 for ; Fri, 20 Aug 2021 16:14:20 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org D274839B90A1 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=sourceware.org; s=default; t=1629476060; bh=vbi1QLlnGuErYXeLPpPUqtLOrNi1CzzZTKGnIBsG5C4=; h=To:Subject:In-Reply-To:References:Date:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=M861mvOd0RH5gLeZ9C7tp0yhfH+G3oWCpmenH7vFuR+NlUr8fwXKlgu3hMxema8/0 yXzUCn3FjMHVO/wsBGCtWWzAa8ycxRnSVJMb27x9Skycn9jHwt2FpIkG+rq6zLpbq7 77cc3vKPSn2lCvzZ5OeUY8f1OznmksvyOwHRmGDY= X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTP id CEAFC3896C25 for ; Fri, 20 Aug 2021 16:13:11 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.1 sourceware.org CEAFC3896C25 Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-284-iEKzhMdkOfKnn4oxh6l_tA-1; Fri, 20 Aug 2021 12:13:10 -0400 X-MC-Unique: iEKzhMdkOfKnn4oxh6l_tA-1 Received: from smtp.corp.redhat.com (int-mx04.intmail.prod.int.phx2.redhat.com [10.5.11.14]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 48C596974E for ; Fri, 20 Aug 2021 16:13:09 +0000 (UTC) Received: from oldenburg.str.redhat.com (unknown [10.39.194.2]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 6CCE95D9FC for ; Fri, 20 Aug 2021 16:13:08 +0000 (UTC) To: libc-alpha@sourceware.org Subject: [PATCH v2 2/4] nptl: pthread_kill, pthread_cancel should fail after exit (bug 19193) In-Reply-To: References: X-From-Line: 620f289d7fca28892875f5ea587952cb41a69aef Mon Sep 17 00:00:00 2001 Message-Id: <620f289d7fca28892875f5ea587952cb41a69aef.1629475813.git.fweimer@redhat.com> Date: Fri, 20 Aug 2021 18:13:06 +0200 User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/27.2 (gnu/linux) MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.14 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-12.9 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_LOW, RCVD_IN_MSPIKE_H2, SPF_HELO_NONE, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.4 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on server2.sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Florian Weimer via Libc-alpha From: Florian Weimer Reply-To: Florian Weimer Errors-To: libc-alpha-bounces+patchwork=sourceware.org@sourceware.org Sender: "Libc-alpha" This closes one remaining race condition related to bug 12889: if the thread already exited on the kernel side, returning ESRCH is not correct because that error is reserved for the thread IDs (pthread_t values) whose lifetime has ended. In case of a kernel-side exit and a valid thread ID, no signal needs to be sent and cancellation does not have an effect, so just return 0. sysdeps/pthread/tst-kill4.c triggers undefined behavior and is removed with this commit. --- v2: Actually unchanged. nptl/pthread_cancel.c | 9 ++-- nptl/pthread_kill.c | 7 +++- sysdeps/pthread/Makefile | 5 ++- sysdeps/pthread/tst-pthread_cancel-exited.c | 45 ++++++++++++++++++++ sysdeps/pthread/tst-pthread_kill-exited.c | 46 +++++++++++++++++++++ 5 files changed, 106 insertions(+), 6 deletions(-) create mode 100644 sysdeps/pthread/tst-pthread_cancel-exited.c create mode 100644 sysdeps/pthread/tst-pthread_kill-exited.c diff --git a/nptl/pthread_cancel.c b/nptl/pthread_cancel.c index cc25ff21f3..9bac6e3b76 100644 --- a/nptl/pthread_cancel.c +++ b/nptl/pthread_cancel.c @@ -62,10 +62,11 @@ __pthread_cancel (pthread_t th) { volatile struct pthread *pd = (volatile struct pthread *) th; - /* Make sure the descriptor is valid. */ - if (INVALID_TD_P (pd)) - /* Not a valid thread handle. */ - return ESRCH; + if (pd->tid == 0) + /* The thread has already exited on the kernel side. Its outcome + (regular exit, other cancelation) has already been + determined. */ + return 0; static int init_sigcancel = 0; if (atomic_load_relaxed (&init_sigcancel) == 0) diff --git a/nptl/pthread_kill.c b/nptl/pthread_kill.c index f79a2b26fc..5d4c86f920 100644 --- a/nptl/pthread_kill.c +++ b/nptl/pthread_kill.c @@ -46,7 +46,12 @@ __pthread_kill_internal (pthread_t threadid, int signo) ? INTERNAL_SYSCALL_ERRNO (val) : 0); } else - val = ESRCH; + /* The kernel reports that the thread has exited. POSIX specifies + the ESRCH error only for the case when the lifetime of a thread + ID has ended, but calling pthread_kill on such a thread ID is + undefined in glibc. Therefore, do not treat kernel thread exit + as an error. */ + val = 0; return val; } diff --git a/sysdeps/pthread/Makefile b/sysdeps/pthread/Makefile index 42f9fc5072..dedfa0d290 100644 --- a/sysdeps/pthread/Makefile +++ b/sysdeps/pthread/Makefile @@ -89,7 +89,7 @@ tests += tst-cnd-basic tst-mtx-trylock tst-cnd-broadcast \ tst-join8 tst-join9 tst-join10 tst-join11 tst-join12 tst-join13 \ tst-join14 tst-join15 \ tst-key1 tst-key2 tst-key3 tst-key4 \ - tst-kill1 tst-kill2 tst-kill3 tst-kill4 tst-kill5 tst-kill6 \ + tst-kill1 tst-kill2 tst-kill3 tst-kill5 tst-kill6 \ tst-locale1 tst-locale2 \ tst-memstream \ tst-mutex-errorcheck tst-mutex1 tst-mutex2 tst-mutex3 tst-mutex4 \ @@ -118,6 +118,9 @@ tests += tst-cnd-basic tst-mtx-trylock tst-cnd-broadcast \ tst-unload \ tst-unwind-thread \ tst-pt-vfork1 tst-pt-vfork2 tst-vfork1x tst-vfork2x \ + tst-pthread_cancel-exited \ + tst-pthread_kill-exited \ + # tests tests-time64 := \ tst-abstime-time64 \ diff --git a/sysdeps/pthread/tst-pthread_cancel-exited.c b/sysdeps/pthread/tst-pthread_cancel-exited.c new file mode 100644 index 0000000000..811c9bee07 --- /dev/null +++ b/sysdeps/pthread/tst-pthread_cancel-exited.c @@ -0,0 +1,45 @@ +/* Test that pthread_kill succeeds for an exited thread. + Copyright (C) 2021 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +/* This test verifies that pthread_kill returns 0 (and not ESRCH) for + a thread that has exited on the kernel side. */ + +#include +#include +#include + +static void * +noop_thread (void *closure) +{ + return NULL; +} + +static int +do_test (void) +{ + pthread_t thr = xpthread_create (NULL, noop_thread, NULL); + + support_wait_for_thread_exit (); + + xpthread_cancel (thr); + xpthread_join (thr); + + return 0; +} + +#include diff --git a/sysdeps/pthread/tst-pthread_kill-exited.c b/sysdeps/pthread/tst-pthread_kill-exited.c new file mode 100644 index 0000000000..7575fb6d58 --- /dev/null +++ b/sysdeps/pthread/tst-pthread_kill-exited.c @@ -0,0 +1,46 @@ +/* Test that pthread_kill succeeds for an exited thread. + Copyright (C) 2021 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +/* This test verifies that pthread_kill returns 0 (and not ESRCH) for + a thread that has exited on the kernel side. */ + +#include +#include +#include +#include + +static void * +noop_thread (void *closure) +{ + return NULL; +} + +static int +do_test (void) +{ + pthread_t thr = xpthread_create (NULL, noop_thread, NULL); + + support_wait_for_thread_exit (); + + xpthread_kill (thr, SIGUSR1); + xpthread_join (thr); + + return 0; +} + +#include From patchwork Fri Aug 20 16:13:58 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Florian Weimer X-Patchwork-Id: 44729 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id BEE2F39C0005 for ; Fri, 20 Aug 2021 16:15:03 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org BEE2F39C0005 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=sourceware.org; s=default; t=1629476103; bh=hkrcdLXOf4YmpaC3wWkcr00mWUSSKSvFJfFLdTQd9V4=; h=To:Subject:In-Reply-To:References:Date:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=B7gjmYVdARl74tEtX3btFPq95Kb8wwKXgNElpK1p0xfqF59E7VNRa0lqb+sHb0LJY xjcsI6hmnRf9NYkWC8284xgVaLN2sMIPp4YJw2FqVbvutuHNVLIiY2wqjf9kB9Y53r UGDKWxYnSCnCQ67VLicUdGjM8Lk7KY7u004+fwvo= X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [216.205.24.124]) by sourceware.org (Postfix) with ESMTP id AE7C23898023 for ; Fri, 20 Aug 2021 16:14:05 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.1 sourceware.org AE7C23898023 Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-469-Tg74QQ4uN0CpJIhz9eAw_A-1; Fri, 20 Aug 2021 12:14:02 -0400 X-MC-Unique: Tg74QQ4uN0CpJIhz9eAw_A-1 Received: from smtp.corp.redhat.com (int-mx04.intmail.prod.int.phx2.redhat.com [10.5.11.14]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 5CE5E760C0 for ; Fri, 20 Aug 2021 16:14:01 +0000 (UTC) Received: from oldenburg.str.redhat.com (unknown [10.39.194.2]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 58D265D9FC for ; Fri, 20 Aug 2021 16:14:00 +0000 (UTC) To: libc-alpha@sourceware.org Subject: [PATCH v2 3/4] nptl: Add internal __pthread_call_with_tid function In-Reply-To: References: X-From-Line: 39f7f2474c4c9e8ae63fc00c60574ee5a657e12d Mon Sep 17 00:00:00 2001 Message-Id: <39f7f2474c4c9e8ae63fc00c60574ee5a657e12d.1629475813.git.fweimer@redhat.com> Date: Fri, 20 Aug 2021 18:13:58 +0200 User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/27.2 (gnu/linux) MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.14 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-12.2 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_LOW, RCVD_IN_MSPIKE_H2, SPF_HELO_NONE, SPF_NONE, TXREP, URIBL_BLACK autolearn=ham autolearn_force=no version=3.4.4 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on server2.sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Florian Weimer via Libc-alpha From: Florian Weimer Reply-To: Florian Weimer Errors-To: libc-alpha-bounces+patchwork=sourceware.org@sourceware.org Sender: "Libc-alpha" It will be used to address race conditions during thread exit due to TID reuse. nptl/tst-pthread_call_with_tid has to be a static test because __pthread_call_with_tid is not exported from libc.so. --- v2: New patch, extracted from the previous 3/3 patch. Added signal blocking (but not for the self-thread case), which is required for correctness. nptl/Makefile | 5 +- nptl/allocatestack.c | 1 + nptl/descr.h | 11 +++ nptl/pthread_call_with_tid.c | 66 +++++++++++++ nptl/pthread_create.c | 18 ++++ nptl/tst-pthread_call_with_tid.c | 165 +++++++++++++++++++++++++++++++ sysdeps/nptl/pthreadP.h | 20 ++++ 7 files changed, 285 insertions(+), 1 deletion(-) create mode 100644 nptl/pthread_call_with_tid.c create mode 100644 nptl/tst-pthread_call_with_tid.c diff --git a/nptl/Makefile b/nptl/Makefile index ff4d590f11..f6167dcd54 100644 --- a/nptl/Makefile +++ b/nptl/Makefile @@ -93,6 +93,7 @@ routines = \ pthread_barrierattr_getpshared \ pthread_barrierattr_init \ pthread_barrierattr_setpshared \ + pthread_call_with_tid \ pthread_cancel \ pthread_cleanup_upto \ pthread_clockjoin \ @@ -436,7 +437,9 @@ tests-static += tst-stackguard1-static \ tst-mutex8-static tst-mutexpi8-static tst-sem11-static \ tst-sem12-static tst-cond11-static \ tst-pthread-gdb-attach-static \ - tst-pthread_exit-nothreads-static + tst-pthread_exit-nothreads-static \ + tst-pthread_call_with_tid \ + # tests-static tests += tst-cancel24-static diff --git a/nptl/allocatestack.c b/nptl/allocatestack.c index cfe37a3443..dd9e0b2c6b 100644 --- a/nptl/allocatestack.c +++ b/nptl/allocatestack.c @@ -127,6 +127,7 @@ get_cached_stack (size_t *sizep, void **memp) /* No pending event. */ result->nextevent = NULL; + result->exit_futex = 0; result->tls_state = (struct tls_internal_t) { 0 }; /* Clear the DTV. */ diff --git a/nptl/descr.h b/nptl/descr.h index c85778d449..b848b9ecd4 100644 --- a/nptl/descr.h +++ b/nptl/descr.h @@ -396,6 +396,17 @@ struct pthread PTHREAD_CANCEL_ASYNCHRONOUS). */ unsigned char canceltype; + /* Futex to prevent thread exit during __pthread_call_with_tid. The + least-significant bit is an exit flag. The remaining bits count + the number of __pthread_call_with_tid calls in progress. + + An exiting thread sets the LSB, and waits until exit_futex is 1. + + If the thread is not yet exiting (as indicated by the LSB), + __pthread_call_with_tid atomically adds 2 to the futex variable + before the callback, and subtracts 2 after it. */ + unsigned int exit_futex; + /* Used on strsignal. */ struct tls_internal_t tls_state; diff --git a/nptl/pthread_call_with_tid.c b/nptl/pthread_call_with_tid.c new file mode 100644 index 0000000000..ea3e801e22 --- /dev/null +++ b/nptl/pthread_call_with_tid.c @@ -0,0 +1,66 @@ +/* Invoke a callback with the TID of a specific thread. + Copyright (C) 2021 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include +#include +#include +#include + +int +__pthread_call_with_tid (pthread_t thr, + int (*callback) (struct pthread *thr, pid_t tid, + void *closure), + void *closure) +{ + struct pthread *pd = (struct pthread *) thr; + + if (pd == THREAD_SELF) + { + /* Use the actual TID from the kernel, so that it refers to the + current thread even if called after vfork. No signal + blocking in this case, so that the signal is delivered + immediately. */ + pid_t tid = INLINE_SYSCALL_CALL (gettid); + return callback (pd, tid, closure); + } + + /* Block all signals. The counter increment/decrement is not + reentrant. */ + sigset_t old_mask; + __libc_signal_block_all (&old_mask); + + pid_t tid; + if (atomic_fetch_add_acq_rel (&pd->exit_futex, 2) & 1) + /* The target thread has exited or is about to. There is no + stable TID value anymore. */ + tid = 0; + else + tid = pd->tid; + + int ret = callback (pd, tid, closure); + + /* A value 3 before the update indicates that the thread is exiting, + and this is the last remaining concurrent operation. */ + if (atomic_fetch_add_acq_rel (&pd->exit_futex, -2) == 3) + futex_wake (&pd->exit_futex, 1, FUTEX_PRIVATE); + + __libc_signal_restore_set (&old_mask); + + return ret; +} diff --git a/nptl/pthread_create.c b/nptl/pthread_create.c index d8ec299cb1..e012d89ecb 100644 --- a/nptl/pthread_create.c +++ b/nptl/pthread_create.c @@ -37,6 +37,7 @@ #include #include #include +#include #include @@ -485,6 +486,23 @@ start_thread (void *arg) /* This was the last thread. */ exit (0); + /* This prevents sending a signal from this thread to itself during + its final stages. This must come after the exit call above + because atexit handlers must not run with signals blocked. */ + __libc_signal_block_all (NULL); + + /* Delay exiting this thread until there are no concurrent + __pthread_call_with_tid operations in progress. */ + { + /* Indicate that no further signals should be sent. */ + atomic_fetch_or_release (&pd->exit_futex, 1); + + /* Wait until no __pthread_call_with_tid operations are left. */ + unsigned int val; + while ((val = atomic_load_acquire (&pd->exit_futex)) != 1) + futex_wait (&pd->exit_futex, val, FUTEX_PRIVATE); + } + #ifndef __ASSUME_SET_ROBUST_LIST /* If this thread has any robust mutexes locked, handle them now. */ # if __PTHREAD_MUTEX_HAVE_PREV diff --git a/nptl/tst-pthread_call_with_tid.c b/nptl/tst-pthread_call_with_tid.c new file mode 100644 index 0000000000..07f8e7f0fa --- /dev/null +++ b/nptl/tst-pthread_call_with_tid.c @@ -0,0 +1,165 @@ +/* Whitebox test for __pthread_call_with_tid. + Copyright (C) 2021 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Check if SIGUSR1 is BLOCKED. */ +static void +check_sigusr1 (bool blocked) +{ + sigset_t signals; + xpthread_sigmask (SIG_BLOCK, NULL, &signals); + TEST_COMPARE (!sigismember (&signals, SIGUSR1), !blocked); +} + +/* Direct call to this function. */ +static int +callback_self_direct (struct pthread *pd, pid_t tid, void *closure) +{ + TEST_VERIFY (pd == THREAD_SELF); + TEST_COMPARE (getpid (), gettid ()); /* No other threads. */ + TEST_COMPARE (tid, gettid()); + TEST_COMPARE (tid, pd->tid); + + check_sigusr1 (false); + + return *(int *) closure; +} + +/* Callback for call with pthread_self after vfork. */ +static int +callback_self_vfork (struct pthread *pd, pid_t tid, void *closure) +{ + TEST_VERIFY (pd == THREAD_SELF); + + /* The stored TID refers to the parent process. */ + TEST_COMPARE (getppid (), pd->tid); + + TEST_COMPARE (getpid (), gettid ()); /* No other threads. */ + TEST_COMPARE (tid, gettid()); + + check_sigusr1 (false); + return *(int *) closure; +} + +/* Callback for call on other thread that is running. Returns TID of + the other thread. */ +static int +callback_other_running (struct pthread *pd, pid_t tid, void *closure) +{ + TEST_COMPARE (tid, pd->tid); + TEST_VERIFY (closure == NULL); + check_sigusr1 (true); + return tid; +} + +/* Thread function that goes with callback_other_running. Returns + TID. */ +static void * +threadfunc_other_running (void *closure) +{ + pthread_barrier_t *barrier = closure; + + /* Do not exit until main thread says so. */ + xpthread_barrier_wait (barrier); + + return (void *) (intptr_t) gettid (); +} + +/* Callback for call on other thread that has exited. */ +static int +callback_other_exited (struct pthread *pd, pid_t tid, void *closure) +{ + TEST_COMPARE (tid, 0); /* No TID available. */ + check_sigusr1 (true); + return *(int *) closure; +} + +/* Thread function that goes with callback_other_exited. */ +static void * +threadfunc_other_exited (void *closure) +{ + return NULL; +} + +static int +do_test (void) +{ + check_sigusr1 (false); + + /* Same thread, direct call. */ + int ret = 5; + TEST_COMPARE (5, __pthread_call_with_tid (pthread_self (), + callback_self_direct, &ret)); + check_sigusr1 (false); + + /* Same thread, after vfork. */ + { + pid_t pid = vfork (); + if (pid == 0) + { + ret = 6; + TEST_COMPARE (6, __pthread_call_with_tid (pthread_self (), + callback_self_vfork, &ret)); + check_sigusr1 (false); + _exit (0); + } + if (pid < 0) + FAIL_EXIT1 ("vfork: %m"); + int status; + xwaitpid (pid, &status, 0); + TEST_COMPARE (status, 0); + } + + /* Other thread, still running. */ + { + pthread_barrier_t barrier; + xpthread_barrier_init (&barrier, NULL, 2); + pthread_t thr = xpthread_create (NULL, threadfunc_other_running, &barrier); + pid_t tid1 = __pthread_call_with_tid (thr, + callback_other_running, NULL); + check_sigusr1 (false); + xpthread_barrier_wait (&barrier); + pid_t tid2 = (intptr_t) xpthread_join (thr); + xpthread_barrier_destroy (&barrier); + TEST_COMPARE (tid1, tid2); + } + + /* Other thread, exited. */ + { + pthread_t thr = xpthread_create (NULL, threadfunc_other_exited, NULL); + support_wait_for_thread_exit (); + ret = 7; + TEST_COMPARE (7, __pthread_call_with_tid (thr, + callback_other_exited, &ret)); + check_sigusr1 (false); + xpthread_join (thr); + } + + return 0; +} + +#include diff --git a/sysdeps/nptl/pthreadP.h b/sysdeps/nptl/pthreadP.h index 374657a2fd..fe28389163 100644 --- a/sysdeps/nptl/pthreadP.h +++ b/sysdeps/nptl/pthreadP.h @@ -673,6 +673,26 @@ extern void __wait_lookup_done (void) attribute_hidden; int __pthread_attr_extension (struct pthread_attr *attr) attribute_hidden __attribute_warn_unused_result__; +/* __pthread_call_with_tid CALLBACK (THR, TID, CLOSURE), where TID is + either the current TID of THR, or zero if THR has exited or is + about to exit. If TID is not zero, the thread denoted by it is + guaranteed not to exit before CALLBACK returns. + + __pthread_call_with_tid returns the result of CALLBACK. + + If THR refers to the current thread, the actual TID is used (to + support vfork). CALLBACK may unwind in this case (e.g., siglongjmp + from user-supplied signal handler). + + If THR is not the current thread, then all signals are blocked + until CALLBACK returns. CALLBACK is assumed to return normally (no + unwinding). This makes the __pthread_call_with_tid + async-signal-safe as long as CALLBACK is async-signal-safe. */ +int __pthread_call_with_tid (pthread_t thr, + int (*callback) (struct pthread *thr, pid_t tid, + void *closure), + void *closure) attribute_hidden; + #ifdef SHARED # define PTHREAD_STATIC_FN_REQUIRE(name) #else From patchwork Fri Aug 20 16:14:14 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Florian Weimer X-Patchwork-Id: 44730 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id CDEB239C001F for ; Fri, 20 Aug 2021 16:15:46 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org CDEB239C001F DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=sourceware.org; s=default; t=1629476146; bh=olGdGS/QmtlIc9ms311PHmjV2CYd/RoqJrX9koc54Ow=; h=To:Subject:In-Reply-To:References:Date:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=xkOBW/bjaEI4rp6PulyCi2LKYY3ug00s3jozzRJD7I8dT8q94u8NgB3vR1iWBi3PH 2ypFLagRGztVn3QaSdSW8lYtOCTwzJ8C4LgVhY9lhRcyAtbaA0LANpQEBVfHOuGM3+ FjzNarGSITwvTROrO0Lffq15UAsIZSdT5lRAkZBY= X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [216.205.24.124]) by sourceware.org (Postfix) with ESMTP id EE2063969C07 for ; Fri, 20 Aug 2021 16:14:19 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.1 sourceware.org EE2063969C07 Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-185-_gAXcarsOcGEfuK1S8sy7A-1; Fri, 20 Aug 2021 12:14:18 -0400 X-MC-Unique: _gAXcarsOcGEfuK1S8sy7A-1 Received: from smtp.corp.redhat.com (int-mx01.intmail.prod.int.phx2.redhat.com [10.5.11.11]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 4465C760C6 for ; Fri, 20 Aug 2021 16:14:17 +0000 (UTC) Received: from oldenburg.str.redhat.com (unknown [10.39.194.2]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 3969218B42 for ; Fri, 20 Aug 2021 16:14:15 +0000 (UTC) To: libc-alpha@sourceware.org Subject: [PATCH v2 4/4] nptl: Fix race between pthread_kill and thread exit (bug 12889) In-Reply-To: References: X-From-Line: 5880c14d1512630b28c1f91bdb03457bf09c3324 Mon Sep 17 00:00:00 2001 Message-Id: <5880c14d1512630b28c1f91bdb03457bf09c3324.1629475813.git.fweimer@redhat.com> Date: Fri, 20 Aug 2021 18:14:14 +0200 User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/27.2 (gnu/linux) MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.11 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-12.9 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_LOW, RCVD_IN_MSPIKE_H2, SPF_HELO_NONE, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.4 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on server2.sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Florian Weimer via Libc-alpha From: Florian Weimer Reply-To: Florian Weimer Errors-To: libc-alpha-bounces+patchwork=sourceware.org@sourceware.org Sender: "Libc-alpha" Thread exit is delayed using __pthread_call_with_tid until all pending pthread_kill operations on the thread have completed. This avoids sending signals to the wrong thread or a spurious ESRCH error. The test sysdeps/pthread/tst-pthread_cancel-select-loop.c is derived from a downstream test originally written by Marek Polacek. --- v2: Use __pthread_call_with_tid. nptl/pthread_kill.c | 49 ++++---- sysdeps/pthread/Makefile | 2 + sysdeps/pthread/tst-kill4.c | 90 -------------- .../pthread/tst-pthread_cancel-select-loop.c | 87 ++++++++++++++ sysdeps/pthread/tst-pthread_kill-exiting.c | 110 ++++++++++++++++++ 5 files changed, 219 insertions(+), 119 deletions(-) delete mode 100644 sysdeps/pthread/tst-kill4.c create mode 100644 sysdeps/pthread/tst-pthread_cancel-select-loop.c create mode 100644 sysdeps/pthread/tst-pthread_kill-exiting.c diff --git a/nptl/pthread_kill.c b/nptl/pthread_kill.c index 5d4c86f920..4bec49a312 100644 --- a/nptl/pthread_kill.c +++ b/nptl/pthread_kill.c @@ -20,40 +20,31 @@ #include #include -int -__pthread_kill_internal (pthread_t threadid, int signo) +static int +tgkill_callback (struct pthread *unused, pid_t tid, void *closure) { - pid_t tid; - struct pthread *pd = (struct pthread *) threadid; - - if (pd == THREAD_SELF) - /* It is a special case to handle raise() implementation after a vfork - call (which does not update the PD tid field). */ - tid = INLINE_SYSCALL_CALL (gettid); + int signo = (intptr_t) closure; + if (tid == 0) + /* Thread is exiting or has exited. The signal is not observable + either way, so do not send it. POSIX specifies the ESRCH error + only for the case when the lifetime of a thread ID has ended, + but calling pthread_kill on such a thread ID is undefined in + glibc. Therefore, do not treat kernel thread exit as an error. */ + return 0; else - /* Force load of pd->tid into local variable or register. Otherwise - if a thread exits between ESRCH test and tgkill, we might return - EINVAL, because pd->tid would be cleared by the kernel. */ - tid = atomic_forced_read (pd->tid); - - int val; - if (__glibc_likely (tid > 0)) { - pid_t pid = __getpid (); - - val = INTERNAL_SYSCALL_CALL (tgkill, pid, tid, signo); - val = (INTERNAL_SYSCALL_ERROR_P (val) - ? INTERNAL_SYSCALL_ERRNO (val) : 0); + /* Using tgkill is a safety measure. __pthread_call_with_tid + ensures that the TID is still live at the point of the call. */ + int ret = INTERNAL_SYSCALL_CALL (tgkill, __getpid (), tid, signo); + return INTERNAL_SYSCALL_ERROR_P (ret) ? INTERNAL_SYSCALL_ERRNO (ret) : 0; } - else - /* The kernel reports that the thread has exited. POSIX specifies - the ESRCH error only for the case when the lifetime of a thread - ID has ended, but calling pthread_kill on such a thread ID is - undefined in glibc. Therefore, do not treat kernel thread exit - as an error. */ - val = 0; +} - return val; +int +__pthread_kill_internal (pthread_t threadid, int signo) +{ + return __pthread_call_with_tid (threadid, tgkill_callback, + (void *) (intptr_t) signo); } int diff --git a/sysdeps/pthread/Makefile b/sysdeps/pthread/Makefile index dedfa0d290..48dba717a1 100644 --- a/sysdeps/pthread/Makefile +++ b/sysdeps/pthread/Makefile @@ -119,7 +119,9 @@ tests += tst-cnd-basic tst-mtx-trylock tst-cnd-broadcast \ tst-unwind-thread \ tst-pt-vfork1 tst-pt-vfork2 tst-vfork1x tst-vfork2x \ tst-pthread_cancel-exited \ + tst-pthread_cancel-select-loop \ tst-pthread_kill-exited \ + tst-pthread_kill-exiting \ # tests tests-time64 := \ diff --git a/sysdeps/pthread/tst-kill4.c b/sysdeps/pthread/tst-kill4.c deleted file mode 100644 index 9563939792..0000000000 --- a/sysdeps/pthread/tst-kill4.c +++ /dev/null @@ -1,90 +0,0 @@ -/* Copyright (C) 2003-2021 Free Software Foundation, Inc. - This file is part of the GNU C Library. - Contributed by Ulrich Drepper , 2003. - - The GNU C Library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - The GNU C Library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with the GNU C Library; if not, see - . */ - -#include -#include -#include -#include -#include -#include - - -static void * -tf (void *a) -{ - return NULL; -} - - -int -do_test (void) -{ - pthread_attr_t at; - if (pthread_attr_init (&at) != 0) - { - puts ("attr_create failed"); - exit (1); - } - - /* Limit thread stack size, because if it is too large, pthread_join - will free it immediately rather than put it into stack cache. */ - if (pthread_attr_setstacksize (&at, 2 * 1024 * 1024) != 0) - { - puts ("setstacksize failed"); - exit (1); - } - - pthread_t th; - if (pthread_create (&th, &at, tf, NULL) != 0) - { - puts ("create failed"); - exit (1); - } - - pthread_attr_destroy (&at); - - if (pthread_join (th, NULL) != 0) - { - puts ("join failed"); - exit (1); - } - - /* The following only works because we assume here something about - the implementation. Namely, that the memory allocated for the - thread descriptor is not going away, that the TID field is - cleared and therefore the signal is sent to process 0, and that - we can savely assume there is no other process with this ID at - that time. */ - int e = pthread_kill (th, 0); - if (e == 0) - { - puts ("pthread_kill succeeded"); - exit (1); - } - if (e != ESRCH) - { - puts ("pthread_kill didn't return ESRCH"); - exit (1); - } - - return 0; -} - - -#define TEST_FUNCTION do_test () -#include "../test-skeleton.c" diff --git a/sysdeps/pthread/tst-pthread_cancel-select-loop.c b/sysdeps/pthread/tst-pthread_cancel-select-loop.c new file mode 100644 index 0000000000..a62087589c --- /dev/null +++ b/sysdeps/pthread/tst-pthread_cancel-select-loop.c @@ -0,0 +1,87 @@ +/* Test that pthread_cancel succeeds during thread exit. + Copyright (C) 2021 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +/* This test tries to trigger an internal race condition in + pthread_cancel, where the cancellation signal is sent after the + thread has begun the cancellation process. This can result in a + spurious ESRCH error. For the original bug 12889, the window is + quite small, so the bug was not reproduced in every run. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Set to true by timeout_thread_function when the test should + terminate. */ +static bool timeout; + +static void * +timeout_thread_function (void *unused) +{ + usleep (5 * 1000 * 1000); + __atomic_store_n (&timeout, true, __ATOMIC_RELAXED); + return NULL; +} + +/* Used for blocking the select function below. */ +static int pipe_fds[2]; + +static void * +canceled_thread_function (void *unused) +{ + while (true) + { + fd_set rfs; + fd_set wfs; + fd_set efs; + FD_ZERO (&rfs); + FD_ZERO (&wfs); + FD_ZERO (&efs); + FD_SET (pipe_fds[0], &rfs); + + /* If the cancellation request is recognized early, the thread + begins exiting while the cancellation signal arrives. */ + select (FD_SETSIZE, &rfs, &wfs, &efs, NULL); + } + return NULL; +} + +static int +do_test (void) +{ + xpipe (pipe_fds); + pthread_t thr_timeout = xpthread_create (NULL, timeout_thread_function, NULL); + + while (!__atomic_load_n (&timeout, __ATOMIC_RELAXED)) + { + pthread_t thr = xpthread_create (NULL, canceled_thread_function, NULL); + xpthread_cancel (thr); + TEST_VERIFY (xpthread_join (thr) == PTHREAD_CANCELED); + } + + xpthread_join (thr_timeout); + xclose (pipe_fds[0]); + xclose (pipe_fds[1]); + return 0; +} + +#include diff --git a/sysdeps/pthread/tst-pthread_kill-exiting.c b/sysdeps/pthread/tst-pthread_kill-exiting.c new file mode 100644 index 0000000000..15550d2310 --- /dev/null +++ b/sysdeps/pthread/tst-pthread_kill-exiting.c @@ -0,0 +1,110 @@ +/* Test that pthread_kill succeeds during thread exit. + Copyright (C) 2021 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +/* This test verifies that pthread_kill for a thread that is exiting + succeeds (with or without actually delivering the signal). */ + +#include +#include +#include +#include +#include +#include + +/* Set to true by timeout_thread_function when the test should + terminate. */ +static bool timeout; + +static void * +timeout_thread_function (void *unused) +{ + usleep (1000 * 1000); + __atomic_store_n (&timeout, true, __ATOMIC_RELAXED); + return NULL; +} + +/* Used to synchronize the sending threads with the target thread and + main thread. */ +static pthread_barrier_t barrier_1; +static pthread_barrier_t barrier_2; + +/* The target thread to which signals are to be sent. */ +static pthread_t target_thread; + +static void * +sender_thread_function (void *unused) +{ + while (!__atomic_load_n (&timeout, __ATOMIC_RELAXED)) + { + /* Wait until target_thread has been initialized. The target + thread and main thread participate in this barrier. */ + xpthread_barrier_wait (&barrier_1); + + xpthread_kill (target_thread, SIGUSR1); + + /* Communicate that the signal has been sent. The main thread + participates in this barrier. */ + xpthread_barrier_wait (&barrier_2); + } + return NULL; +} + +static void * +target_thread_function (void *unused) +{ + target_thread = pthread_self (); + xpthread_barrier_wait (&barrier_1); + return NULL; +} + +static int +do_test (void) +{ + xsignal (SIGUSR1, SIG_IGN); + + pthread_t thr_timeout = xpthread_create (NULL, timeout_thread_function, NULL); + + pthread_t threads[4]; + xpthread_barrier_init (&barrier_1, NULL, array_length (threads) + 2); + xpthread_barrier_init (&barrier_2, NULL, array_length (threads) + 1); + + for (int i = 0; i < array_length (threads); ++i) + threads[i] = xpthread_create (NULL, sender_thread_function, NULL); + + while (!__atomic_load_n (&timeout, __ATOMIC_RELAXED)) + { + xpthread_create (NULL, target_thread_function, NULL); + + /* Wait for the target thread to be set up and signal sending to + start. */ + xpthread_barrier_wait (&barrier_1); + + /* Wait for signal sending to complete. */ + xpthread_barrier_wait (&barrier_2); + + xpthread_join (target_thread); + } + + for (int i = 0; i < array_length (threads); ++i) + xpthread_join (threads[i]); + xpthread_join (thr_timeout); + + return 0; +} + +#include