/* This testcase is part of GDB, the GNU debugger.
Copyright 2014-2020 Free Software Foundation, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
#define _GNU_SOURCE
#include <assert.h>
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
pthread_t main_thread;
pthread_attr_t detached_attr;
pthread_attr_t joinable_attr;
/* Number of threads we'll create of each variant
(joinable/detached). */
int n_threads = 50;
/* Mutex used to hold creating detached threads. */
pthread_mutex_t dthrds_create_mutex;
/* Wrapper for pthread_create. */
void
create_thread (pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg)
{
pthread_t child;
int rc;
while ((rc = pthread_create (&child, attr, start_routine, arg)) != 0)
{
fprintf (stderr, "unexpected error from pthread_create: %s (%d)\n",
strerror (rc), rc);
sleep (1);
}
}
void
break_fn (void)
{
}
/* Data passed to joinable threads on creation. This is allocated on
the heap and ownership transferred from parent to child. (We do
this because it's not portable to cast pthread_t to pointer.) */
struct thread_arg
{
pthread_t parent;
};
/* Entry point for joinable threads. These threads first join their
parent before spawning a new child (and exiting). The parent's tid
is passed as pthread_create argument, encapsulated in a struct
thread_arg object. */
void *
joinable_fn (void *arg)
{
struct thread_arg *p = arg;
pthread_setname_np (pthread_self (), "joinable");
if (p->parent != main_thread)
assert (pthread_join (p->parent, NULL) == 0);
p->parent = pthread_self ();
create_thread (&joinable_attr, joinable_fn, p);
break_fn ();
return NULL;
}
/* Entry point for detached threads. */
void *
detached_fn (void *arg)
{
pthread_setname_np (pthread_self (), "detached");
/* This should throttle threads a bit in case we manage to spawn
threads faster than they exit. */
pthread_mutex_lock (&dthrds_create_mutex);
create_thread (&detached_attr, detached_fn, NULL);
/* Note this is called before the mutex is unlocked otherwise in
non-stop mode, when the breakpoint is hit we'd keep spawning more
threads forever while the old threads stay alive (stopped in the
breakpoint). */
break_fn ();
pthread_mutex_unlock (&dthrds_create_mutex);
return NULL;
}
/* Allow for as much timeout as DejaGnu wants, plus a bit of
slack. */
#define SECONDS (TIMEOUT + 20)
/* We'll exit after this many seconds. */
unsigned int seconds_left = SECONDS;
/* GDB sets this whenever it's about to start a new detach/attach
sequence. We react by resetting the seconds left counter. */
volatile int again = 0;
int
main (int argc, char *argv[])
{
int i;
if (argc > 1)
n_threads = atoi (argv[1]);
pthread_mutex_init (&dthrds_create_mutex, NULL);
pthread_attr_init (&detached_attr);
pthread_attr_setdetachstate (&detached_attr, PTHREAD_CREATE_DETACHED);
pthread_attr_init (&joinable_attr);
pthread_attr_setdetachstate (&joinable_attr, PTHREAD_CREATE_JOINABLE);
main_thread = pthread_self ();
/* Spawn the initial set of test threads. Some threads are
joinable, others are detached. This exercises different code
paths in the runtime. */
for (i = 0; i < n_threads; ++i)
{
struct thread_arg *p;
p = malloc (sizeof *p);
p->parent = main_thread;
create_thread (&joinable_attr, joinable_fn, p);
create_thread (&detached_attr, detached_fn, NULL);
}
/* Exit after a while if GDB is gone/crashes. But wait long enough
for one attach/detach sequence done by the .exp file. */
while (--seconds_left > 0)
{
sleep (1);
if (again)
{
/* GDB should be reattaching soon. Restart the timer. */
again = 0;
seconds_left = SECONDS;
}
}
printf ("timeout, exiting\n");
return 0;
}