1/* Test case for async-signal-safe _Fork (with respect to malloc).
2 Copyright (C) 2021-2024 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
4
5 The GNU C Library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public License as
7 published by the Free Software Foundation; either version 2.1 of the
8 License, or (at your option) any later version.
9
10 The GNU C Library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with the GNU C Library; see the file COPYING.LIB. If
17 not, see <https://www.gnu.org/licenses/>. */
18
19/* This test is similar to tst-mallocfork2.c, but specifically stress
20 the async-signal-safeness of _Fork on multithread environment. */
21
22#include <array_length.h>
23#include <errno.h>
24#include <stdbool.h>
25#include <stdio.h>
26#include <stdlib.h>
27#include <support/check.h>
28#include <support/support.h>
29#include <support/xsignal.h>
30#include <support/xthread.h>
31#include <support/xunistd.h>
32#include <sys/wait.h>
33
34/* How many malloc objects to keep arond. */
35enum { malloc_objects = 1009 };
36
37/* The maximum size of an object. */
38enum { malloc_maximum_size = 70000 };
39
40/* How many iterations the test performs before exiting. */
41enum { iterations = 10000 };
42
43/* Barrier for synchronization with the threads sending SIGUSR1
44 signals, to make it more likely that the signals arrive during a
45 fork/free/malloc call. */
46static pthread_barrier_t barrier;
47
48/* Set to 1 if SIGUSR1 is received. Used to detect a signal during
49 fork/free/malloc. */
50static volatile sig_atomic_t sigusr1_received;
51
52/* Periodically set to 1, to indicate that the thread is making
53 progress. Checked by liveness_signal_handler. */
54static volatile sig_atomic_t progress_indicator = 1;
55
56/* Set to 1 if an error occurs in the signal handler. */
57static volatile sig_atomic_t error_indicator = 0;
58
59static void
60sigusr1_handler (int signo)
61{
62 sigusr1_received = 1;
63
64 /* Perform a fork with a trivial subprocess. */
65 pid_t pid = _Fork ();
66 if (pid == -1)
67 {
68 write_message (message: "error: fork\n");
69 error_indicator = 1;
70 return;
71 }
72 if (pid == 0)
73 _exit (0);
74 int status;
75 int ret = TEMP_FAILURE_RETRY (waitpid (pid, &status, 0));
76 if (ret < 0)
77 {
78 write_message (message: "error: waitpid\n");
79 error_indicator = 1;
80 return;
81 }
82 if (status != 0)
83 {
84 write_message (message: "error: unexpected exit status from subprocess\n");
85 error_indicator = 1;
86 return;
87 }
88}
89
90static void
91liveness_signal_handler (int signo)
92{
93 if (progress_indicator)
94 progress_indicator = 0;
95 else
96 write_message (message: "warning: thread seems to be stuck\n");
97}
98
99struct signal_send_args
100{
101 pthread_t target;
102 int signo;
103 bool sleep;
104};
105#define SIGNAL_SEND_GET_ARG(arg, field) \
106 (((struct signal_send_args *)(arg))->field)
107
108/* Send SIGNO to the parent thread. If SLEEP, wait a second between
109 signals, otherwise use barriers to delay sending signals. */
110static void *
111signal_sender (void *args)
112{
113 int signo = SIGNAL_SEND_GET_ARG (args, signo);
114 bool sleep = SIGNAL_SEND_GET_ARG (args, sleep);
115
116 pthread_t target = SIGNAL_SEND_GET_ARG (args, target);
117 while (true)
118 {
119 if (!sleep)
120 xpthread_barrier_wait (barrier: &barrier);
121 xpthread_kill (thr: target, signo);
122 if (sleep)
123 usleep (useconds: 1 * 1000 * 1000);
124 else
125 xpthread_barrier_wait (barrier: &barrier);
126 }
127 return NULL;
128}
129
130static pthread_t sigusr1_sender[5];
131static pthread_t sigusr2_sender;
132
133static int
134do_test (void)
135{
136 xsignal (SIGUSR1, handler: sigusr1_handler);
137 xsignal (SIGUSR2, handler: liveness_signal_handler);
138
139 pthread_t self = pthread_self ();
140
141 struct signal_send_args sigusr2_args = { self, SIGUSR2, true };
142 sigusr2_sender = xpthread_create (NULL, thread_func: signal_sender, closure: &sigusr2_args);
143
144 /* Send SIGUSR1 signals from several threads. Hopefully, one
145 signal will hit one of the critical functions. Use a barrier to
146 avoid sending signals while not running fork/free/malloc. */
147 struct signal_send_args sigusr1_args = { self, SIGUSR1, false };
148 xpthread_barrier_init (barrier: &barrier, NULL,
149 array_length (sigusr1_sender) + 1);
150 for (size_t i = 0; i < array_length (sigusr1_sender); ++i)
151 sigusr1_sender[i] = xpthread_create (NULL, thread_func: signal_sender, closure: &sigusr1_args);
152
153 void *objects[malloc_objects] = {};
154 unsigned int fork_signals = 0;
155 unsigned int free_signals = 0;
156 unsigned int malloc_signals = 0;
157 unsigned int seed = 1;
158 for (int i = 0; i < iterations; ++i)
159 {
160 progress_indicator = 1;
161 int slot = rand_r (seed: &seed) % malloc_objects;
162 size_t size = rand_r (seed: &seed) % malloc_maximum_size;
163
164 /* Occasionally do a fork first, to catch deadlocks there as
165 well (see bug 24161). */
166 bool do_fork = (rand_r (seed: &seed) % 7) == 0;
167
168 xpthread_barrier_wait (barrier: &barrier);
169 if (do_fork)
170 {
171 sigusr1_received = 0;
172 pid_t pid = _Fork ();
173 TEST_VERIFY_EXIT (pid != -1);
174 if (sigusr1_received)
175 ++fork_signals;
176 if (pid == 0)
177 _exit (0);
178 int status;
179 int ret = TEMP_FAILURE_RETRY (waitpid (pid, &status, 0));
180 if (ret < 0)
181 FAIL_EXIT1 ("waitpid: %m");
182 TEST_COMPARE (status, 0);
183 }
184 sigusr1_received = 0;
185 free (ptr: objects[slot]);
186 if (sigusr1_received)
187 ++free_signals;
188 sigusr1_received = 0;
189 objects[slot] = malloc (size: size);
190 if (sigusr1_received)
191 ++malloc_signals;
192 xpthread_barrier_wait (barrier: &barrier);
193
194 if (objects[slot] == NULL || error_indicator != 0)
195 {
196 printf (format: "error: malloc: %m\n");
197 return 1;
198 }
199 }
200
201 /* Clean up allocations. */
202 for (int slot = 0; slot < malloc_objects; ++slot)
203 free (ptr: objects[slot]);
204
205 printf (format: "info: signals received during fork: %u\n", fork_signals);
206 printf (format: "info: signals received during free: %u\n", free_signals);
207 printf (format: "info: signals received during malloc: %u\n", malloc_signals);
208
209 return 0;
210}
211
212#define TIMEOUT 100
213#include <support/test-driver.c>
214

source code of glibc/malloc/tst-mallocfork3.c