aboutsummaryrefslogtreecommitdiff
path: root/malloc/tst-mallocfork3.c
blob: 8240702dbe5d9c279195d3c0e5983d5774d64ccf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
/* Test case for async-signal-safe _Fork (with respect to malloc).
   Copyright (C) 2021-2024 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; see the file COPYING.LIB.  If
   not, see <https://www.gnu.org/licenses/>.  */

/* This test is similar to tst-mallocfork2.c, but specifically stress
   the async-signal-safeness of _Fork on multithread environment.  */

#include <array_length.h>
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <support/check.h>
#include <support/support.h>
#include <support/xsignal.h>
#include <support/xthread.h>
#include <support/xunistd.h>
#include <sys/wait.h>

/* How many malloc objects to keep arond.  */
enum { malloc_objects = 1009 };

/* The maximum size of an object.  */
enum { malloc_maximum_size = 70000 };

/* How many iterations the test performs before exiting.  */
enum { iterations = 10000 };

/* Barrier for synchronization with the threads sending SIGUSR1
   signals, to make it more likely that the signals arrive during a
   fork/free/malloc call.  */
static pthread_barrier_t barrier;

/* Set to 1 if SIGUSR1 is received.  Used to detect a signal during
   fork/free/malloc.  */
static volatile sig_atomic_t sigusr1_received;

/* Periodically set to 1, to indicate that the thread is making
   progress.  Checked by liveness_signal_handler.  */
static volatile sig_atomic_t progress_indicator = 1;

/* Set to 1 if an error occurs in the signal handler.  */
static volatile sig_atomic_t error_indicator = 0;

static void
sigusr1_handler (int signo)
{
  sigusr1_received = 1;

  /* Perform a fork with a trivial subprocess.  */
  pid_t pid = _Fork ();
  if (pid == -1)
    {
      write_message ("error: fork\n");
      error_indicator = 1;
      return;
    }
  if (pid == 0)
    _exit (0);
  int status;
  int ret = TEMP_FAILURE_RETRY (waitpid (pid, &status, 0));
  if (ret < 0)
    {
      write_message ("error: waitpid\n");
      error_indicator = 1;
      return;
    }
  if (status != 0)
    {
      write_message ("error: unexpected exit status from subprocess\n");
      error_indicator = 1;
      return;
    }
}

static void
liveness_signal_handler (int signo)
{
  if (progress_indicator)
    progress_indicator = 0;
  else
    write_message ("warning: thread seems to be stuck\n");
}

struct signal_send_args
{
  pthread_t target;
  int signo;
  bool sleep;
};
#define SIGNAL_SEND_GET_ARG(arg, field) \
  (((struct signal_send_args *)(arg))->field)

/* Send SIGNO to the parent thread.  If SLEEP, wait a second between
   signals, otherwise use barriers to delay sending signals.  */
static void *
signal_sender (void *args)
{
  int signo = SIGNAL_SEND_GET_ARG (args, signo);
  bool sleep = SIGNAL_SEND_GET_ARG (args, sleep);

  pthread_t target = SIGNAL_SEND_GET_ARG (args, target);
  while (true)
    {
      if (!sleep)
        xpthread_barrier_wait (&barrier);
      xpthread_kill (target, signo);
      if (sleep)
        usleep (1 * 1000 * 1000);
      else
        xpthread_barrier_wait (&barrier);
    }
  return NULL;
}

static pthread_t sigusr1_sender[5];
static pthread_t sigusr2_sender;

static int
do_test (void)
{
  xsignal (SIGUSR1, sigusr1_handler);
  xsignal (SIGUSR2, liveness_signal_handler);

  pthread_t self = pthread_self ();

  struct signal_send_args sigusr2_args = { self, SIGUSR2, true };
  sigusr2_sender = xpthread_create (NULL, signal_sender, &sigusr2_args);

  /* Send SIGUSR1 signals from several threads.  Hopefully, one
     signal will hit one of the critical functions.  Use a barrier to
     avoid sending signals while not running fork/free/malloc.  */
  struct signal_send_args sigusr1_args = { self, SIGUSR1, false };
  xpthread_barrier_init (&barrier, NULL,
                         array_length (sigusr1_sender) + 1);
  for (size_t i = 0; i < array_length (sigusr1_sender); ++i)
    sigusr1_sender[i] = xpthread_create (NULL, signal_sender, &sigusr1_args);

  void *objects[malloc_objects] = {};
  unsigned int fork_signals = 0;
  unsigned int free_signals = 0;
  unsigned int malloc_signals = 0;
  unsigned int seed = 1;
  for (int i = 0; i < iterations; ++i)
    {
      progress_indicator = 1;
      int slot = rand_r (&seed) % malloc_objects;
      size_t size = rand_r (&seed) % malloc_maximum_size;

      /* Occasionally do a fork first, to catch deadlocks there as
         well (see bug 24161).  */
      bool do_fork = (rand_r (&seed) % 7) == 0;

      xpthread_barrier_wait (&barrier);
      if (do_fork)
        {
          sigusr1_received = 0;
          pid_t pid = _Fork ();
          TEST_VERIFY_EXIT (pid != -1);
          if (sigusr1_received)
            ++fork_signals;
          if (pid == 0)
            _exit (0);
          int status;
          int ret = TEMP_FAILURE_RETRY (waitpid (pid, &status, 0));
          if (ret < 0)
            FAIL_EXIT1 ("waitpid: %m");
          TEST_COMPARE (status, 0);
        }
      sigusr1_received = 0;
      free (objects[slot]);
      if (sigusr1_received)
        ++free_signals;
      sigusr1_received = 0;
      objects[slot] = malloc (size);
      if (sigusr1_received)
        ++malloc_signals;
      xpthread_barrier_wait (&barrier);

      if (objects[slot] == NULL || error_indicator != 0)
        {
          printf ("error: malloc: %m\n");
          return 1;
        }
    }

  /* Clean up allocations.  */
  for (int slot = 0; slot < malloc_objects; ++slot)
    free (objects[slot]);

  printf ("info: signals received during fork: %u\n", fork_signals);
  printf ("info: signals received during free: %u\n", free_signals);
  printf ("info: signals received during malloc: %u\n", malloc_signals);

  return 0;
}

#define TIMEOUT 100
#include <support/test-driver.c>