/* This testcase is part of GDB, the GNU debugger.

   Copyright 2021-2022 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>

#define THREADS 20

static volatile unsigned int global_var = 123;

/* Wrapper around pthread_create.   */

static void
create_thread (pthread_t *child,
	       void *(*start_routine) (void *), void *arg)
{
  int rc;

  while ((rc = pthread_create (child, NULL, start_routine, arg)) != 0)
    {
      fprintf (stderr, "unexpected error from pthread_create: %s (%d)\n",
	       strerror (rc), rc);
      sleep (1);
    }
}

/* Data passed to threads on creation.  This is allocated on the heap
   and ownership transferred from parent to child.  */

struct thread_arg
{
  /* The thread's parent.  */
  pthread_t parent;

  /* Whether to call pthread_join on the parent.  */
  int join_parent;
};

/* Entry point for threads.  */

static void *
thread_fn (void *arg)
{
  struct thread_arg *p = arg;

  /* Passing no argument makes the thread exit immediately.  */
  if (p == NULL)
    return NULL;

  if (p->join_parent)
    assert (pthread_join (p->parent, NULL) == 0);

  /* Spawn a number of threads that exit immediately, and then join
     them.  The idea is to maximize the time window when we mostly
     have threads exiting.  */
  {
    pthread_t child[THREADS];
    int i;

    /* Passing no argument makes the thread exit immediately.  */
    for (i = 0; i < THREADS; i++)
      create_thread (&child[i], thread_fn, NULL);

    for (i = 0; i < THREADS; i++)
      pthread_join (child[i], NULL);
  }

  /* Spawn a new thread that joins us, and exit.  The idea here is to
     not have any thread that stays around forever.  */
  {
    pthread_t child;

    p->parent = pthread_self ();
    p->join_parent = 1;
    create_thread (&child, thread_fn, p);
  }

  return NULL;
}

int
main (void)
{
  int i;

  for (i = 0; i < 4; i++)
    {
      struct thread_arg *p;
      pthread_t child;

      p = malloc (sizeof *p);
      p->parent = pthread_self ();
      /* Only join the parent once.  */
      if (i == 0)
	p->join_parent = 1;
      else
	p->join_parent = 0;
      create_thread (&child, thread_fn, p);
    }

  /* Exit the leader to make sure that we can access memory with the
     leader gone.  */
  pthread_exit (NULL);
}
