From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: 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 D6A7D383F40B for ; Tue, 8 Jun 2021 10:56:54 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.1 sourceware.org D6A7D383F40B 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-205-NO1vSpN9NQi5NmsKEcptDQ-1; Tue, 08 Jun 2021 06:56:52 -0400 X-MC-Unique: NO1vSpN9NQi5NmsKEcptDQ-1 Received: from smtp.corp.redhat.com (int-mx08.intmail.prod.int.phx2.redhat.com [10.5.11.23]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 986C92D0; Tue, 8 Jun 2021 10:56:51 +0000 (UTC) Received: from oldenburg.str.redhat.com (ovpn-115-60.ams2.redhat.com [10.36.115.60]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 5A9E319C66; Tue, 8 Jun 2021 10:56:50 +0000 (UTC) From: Florian Weimer To: Adhemerval Zanella via Libc-alpha Subject: Re: [PATCH v3] nptl: Deallocate the thread stack on setup failure (BZ #19511) References: <20210527172823.3461314-3-adhemerval.zanella@linaro.org> <20210602125644.3725112-1-adhemerval.zanella@linaro.org> Date: Tue, 08 Jun 2021 12:56:48 +0200 In-Reply-To: <20210602125644.3725112-1-adhemerval.zanella@linaro.org> (Adhemerval Zanella via Libc-alpha's message of "Wed, 2 Jun 2021 09:56:44 -0300") Message-ID: <87lf7kbp5b.fsf@oldenburg.str.redhat.com> 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.23 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-Spam-Status: No, score=-12.6 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, RCVD_IN_DNSWL_LOW, RCVD_IN_MSPIKE_H4, RCVD_IN_MSPIKE_WL, SPF_HELO_NONE, SPF_PASS, TXREP autolearn=ham autolearn_force=no version=3.4.2 X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) 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-List-Received-Date: Tue, 08 Jun 2021 10:56:57 -0000 * Adhemerval Zanella via Libc-alpha: > To setup either the thread scheduling parameters or affinity, > pthread_create enforce synchronization on created thread to wait until > its parent either release PD ownership or send a cancellation signal if > a failure occurs. > > However, cancelling the thread does not deallocate the newly created > stack since cancellation expects that a pthread_join to deallocate any > allocated thread resouces (threads stack or TLS). > > This patch changes on how the thread resource is deallocate in case of > failure to be synchrnous, where the creating thread will signal the =E2=80=9Csynchronous=E2=80=9D > created thread to early exit so it could be joined. The creating thread =E2=80=9Cto exit early=E2=80=9D > will be reponsible for the resource cleanup before return to caller. =E2=80=9Creturning to the caller=E2=80=9D > To signal the creating thread a failure has occured, an unused =E2=80=9Cthat a failure has occured=E2=80=9D > 'struct pthread' member, parent_cancelhandling_unsed, now indicates (okay, existing typo) > whether the setup has failed so creating thread can proper exit. > > This strategy also simplifies by not using thread cancellation and > thus not running libgcc_so load in the signal handler (which is > avoided in thread cancellation since 'pthread_cancel' is the one > responsible to dlopen libgcc_s). Another advantage is since the > early exit is move to first step at thread creation, the signal > mask is not already set and thus it can not act on change ID setxid > handler. > > Checked on x86_64-linux-gnu and aarch64-linux-gnu. > --- > nptl/allocatestack.c | 1 + > nptl/descr.h | 5 +- > nptl/pthread_create.c | 140 ++++++++++++++++++++---------------------- > 3 files changed, 71 insertions(+), 75 deletions(-) > > diff --git a/nptl/allocatestack.c b/nptl/allocatestack.c > index dc81a2ca73..2114bd2e27 100644 > --- a/nptl/allocatestack.c > +++ b/nptl/allocatestack.c > @@ -161,6 +161,7 @@ get_cached_stack (size_t *sizep, void **memp) > /* Cancellation handling is back to the default. */ > result->cancelhandling =3D 0; > result->cleanup =3D NULL; > + result->setup_failed =3D 0; > =20 > /* No pending event. */ > result->nextevent =3D NULL; > diff --git a/nptl/descr.h b/nptl/descr.h > index 3de9535449..9d8297b45f 100644 > --- a/nptl/descr.h > +++ b/nptl/descr.h > @@ -340,8 +340,9 @@ struct pthread > /* True if thread must stop at startup time. */ > bool stopped_start; > =20 > - /* Formerly used for dealing with cancellation. */ > - int parent_cancelhandling_unsed; > + /* Indicate that a thread creation setup has failed (for instance the > + scheduler or affinity). */ > + int setup_failed; > =20 > /* Lock to synchronize access to the descriptor. */ > int lock; > diff --git a/nptl/pthread_create.c b/nptl/pthread_create.c > index 52d6738f7f..4b143a5016 100644 > --- a/nptl/pthread_create.c > +++ b/nptl/pthread_create.c > @@ -167,19 +167,19 @@ late_init (void) > without error, then the created thread owns PD; otherwise, see > (c) and (e) below. > =20 > - (c) If the detached thread setup failed and THREAD_RAN is true, then > - the creating thread releases ownership to the new thread by > - sending a cancellation signal. All threads set THREAD_RAN to > - true as quickly as possible after returning from the OS kernel's > - thread creation routine. > + (c) If the detached thread setup failed and THREAD_RAN is true, then = the > + creating thread releases ownership to the new thread, the created > + thread see the failed setup through a PD field and releases the > + PD ownership and early exit. The creating thread will be one > + responsible for cleanup state. All threads set THREAD_RAN to tru= e as > + quickly as possible after returning from the OS kernel's thread > + creation routine. =E2=80=9Cthe created thread see[s]=E2=80=9D, =E2=80=9Cand [exits early]=E2= =80=9D, =E2=80=9Cwill be [] responsible=E2=80=9D. I don't think (c) is no longer described correctly. THREAD_RAN is set even before user code runs, and ownership is not released in this case. (I think this shows the limits of describing what happens in terms of ownership of PD.) Perhaps add a note that the distinction between (c) and (d) only matters briefly within create_thread? > (d) If the joinable thread setup failed and THREAD_RAN is true, then > - then the creating thread retains ownership of PD and must cleanup > + the creating thread retains ownership of PD and must cleanup > state. Ownership cannot be released to the process via the > return of pthread_create since a non-zero result entails PD is > undefined and therefore cannot be joined to free the resources. > - We privately call pthread_join on the thread to finish handling > - the resource shutdown (Or at least we should, see bug 19511). > =20 > (e) If the thread creation failed and THREAD_RAN is false, then the > creating thread retains ownership of PD and must cleanup state. And I'm not sure if THREAD_RAN matters for the distinction between (d) and (e), either. This suggests to me that we should keep THREAD_RAN local to create_thread, and handle all setup errors there. > @@ -239,8 +239,8 @@ late_init (void) > The return value is zero for success or an errno code for failure. > If the return value is ENOMEM, that will be translated to EAGAIN, > so create_thread need not do that. On failure, *THREAD_RAN should > - be set to true iff the thread actually started up and then got > - canceled before calling user code (*PD->start_routine). */ > + be set to true iff the thread actually started up but before calling > + the user code (*PD->start_routine). */ > =20 > static int _Noreturn start_thread (void *arg); > =20 > @@ -308,35 +308,23 @@ static int create_thread (struct pthread *pd, const= struct pthread_attr *attr, > =09=09=09=3D=3D -1)) > return errno; > =20 > - /* It's started now, so if we fail below, we'll have to cancel it > - and let it clean itself up. */ > + /* It's started now, so if we fail below, we'll have to let it clean i= tself > + up. */ > *thread_ran =3D true; > =20 > /* Now we have the possibility to set scheduling parameters etc. */ > if (attr !=3D NULL) > { > - int res; > - > /* Set the affinity mask if necessary. */ > if (need_setaffinity) > =09{ > =09 assert (*stopped_start); > =20 > -=09 res =3D INTERNAL_SYSCALL_CALL (sched_setaffinity, pd->tid, > -=09=09=09=09 attr->extension->cpusetsize, > -=09=09=09=09 attr->extension->cpuset); > - > +=09 int res =3D INTERNAL_SYSCALL_CALL (sched_setaffinity, pd->tid, > +=09=09=09=09=09 attr->extension->cpusetsize, > +=09=09=09=09=09 attr->extension->cpuset); > =09 if (__glibc_unlikely (INTERNAL_SYSCALL_ERROR_P (res))) > -=09 err_out: > -=09 { > -=09 /* The operation failed. We have to kill the thread. > -=09=09 We let the normal cancellation mechanism do the work. */ > - > -=09 pid_t pid =3D __getpid (); > -=09 INTERNAL_SYSCALL_CALL (tgkill, pid, pd->tid, SIGCANCEL); > - > -=09 return INTERNAL_SYSCALL_ERRNO (res); > -=09 } > +=09 return INTERNAL_SYSCALL_ERRNO (res); > =09} > =20 > /* Set the scheduling parameters. */ > @@ -344,11 +332,10 @@ static int create_thread (struct pthread *pd, const= struct pthread_attr *attr, > =09{ > =09 assert (*stopped_start); > =20 > -=09 res =3D INTERNAL_SYSCALL_CALL (sched_setscheduler, pd->tid, > -=09=09=09=09 pd->schedpolicy, &pd->schedparam); > - > +=09 int res =3D INTERNAL_SYSCALL_CALL (sched_setscheduler, pd->tid, > +=09=09=09=09=09 pd->schedpolicy, &pd->schedparam); > =09 if (__glibc_unlikely (INTERNAL_SYSCALL_ERROR_P (res))) > -=09 goto err_out; > +=09 return INTERNAL_SYSCALL_ERRNO (res); > =09} > } > =20 > @@ -361,6 +348,29 @@ start_thread (void *arg) > { > struct pthread *pd =3D arg; > =20 > + /* We are either in (a) or (b), and in either case we either own PD al= ready > + (2) or are about to own PD (1), and so our only restriction would b= e that > + we can't free PD until we know we have ownership (see CONCURRENCY N= OTES > + above). */ > + bool setup_failed =3D false; > + if (__glibc_unlikely (pd->stopped_start)) I think we should remove __glibc_unlikely for cases which can always be true due to particular application use cases. > + { > + /* Get the lock the parent locked to force synchronization. */ > + lll_lock (pd->lock, LLL_PRIVATE); > + > + /* We have ownership of PD now, for detached threads with setup fa= ilure > +=09 we set it as joinable so the creating thread could synchronous join > + and free any resource prior return to the pthread_create caller= . */ > + setup_failed =3D pd->setup_failed =3D=3D 1; > + if (setup_failed) > +=09pd->joinid =3D NULL; > + > + /* And give it up right away. */ > + lll_unlock (pd->lock, LLL_PRIVATE); > + } > + if (setup_failed) > + goto out; I think it would be clearer if that goto were nested within the previous if block. > @@ -814,30 +805,33 @@ __pthread_create_2_1 (pthread_t *newthread, const p= thread_attr_t *attr, > if (__glibc_unlikely (retval !=3D 0)) > { > if (thread_ran) > -=09/* State (c) or (d) and we may not have PD ownership (see > -=09 CONCURRENCY NOTES above). We can assert that STOPPED_START > -=09 must have been true because thread creation didn't fail, but > -=09 thread attribute setting did. */ > -=09/* See bug 19511 which explains why doing nothing here is a > -=09 resource leak for a joinable thread. */ > -=09assert (stopped_start); > - else > -=09{ > -=09 /* State (e) and we have ownership of PD (see CONCURRENCY > -=09 NOTES above). */ > +=09/* State (c) and we not have PD ownership (see CONCURRENCY NOTES > +=09 above). We can assert that STOPPED_START must have been true > +=09 because thread creation didn't fail, but thread attribute setting > +=09 did. */ > + { > +=09 assert (stopped_start); > +=09 /* Signal the creating thread to release PD ownership and early > +=09 exit so it could be joined. */ > +=09 pd->setup_failed =3D 1; > +=09 lll_unlock (pd->lock, LLL_PRIVATE); =E2=80=9CSignal the creat[ed] thread=E2=80=9D (this code runs on the creati= ng thread). > +=09 /* Similar to pthread_join, but since thread creation has failed at > +=09 startup there is no need to handle all the steps. */ > +=09 pid_t tid; > +=09 while ((tid =3D atomic_load_acquire (&pd->tid)) !=3D 0) > +=09 __futex_abstimed_wait_cancelable64 ((unsigned int *) &pd->tid, > +=09=09=09=09=09=09tid, 0, NULL, LLL_SHARED); > + } > =20 > + /* State (c), (d), or (e) and we have ownership of PD (see CONCURR= ENCY > +=09 NOTES above). */ > =20 > + /* Oops, we lied for a second. */ > + atomic_decrement (&__nptl_nthreads); > + > + /* Free the resources. */ > + __nptl_deallocate_stack (pd); > =20 > /* We have to translate error codes. */ > if (retval =3D=3D ENOMEM) What's missing further below is pd->setup_failed handling if there is an early failure on the created thread (I have my sigaltstack changes in this mind). No such code exits in your patch, so this is consistent in this regard. The actual code changes are fine, I think. Thanks, Florian