2 * Sanity check of libcdecl multithread safety.
4 * Copyright © 2024 Nick Bowler
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22 #include "cdecl-internal.h"
27 * Function called from output.c but not needed for error messaging.
29 const char *cdecl__token_name(unsigned token)
31 tap_bail_out("stub cdecl__token_name called");
35 * Prior returned value from cdecl_get_error in the main thread.
37 static const struct cdecl_error *thread1_err;
40 * Check that the error code and message matches expectations (noting that
41 * this application does not call setlocale to enable translations).
43 static void check_simple_err(const struct cdecl_error *err, unsigned t,
44 unsigned exp_code, unsigned msg_id)
46 static const char errmsgs[] = STRTAB_INITIALIZER;
49 if (!tap_result(err->code == exp_code, "thread[%u] err->code", t)) {
50 tap_diag("Failed, unexpected result");
51 tap_diag(" Received: %u", err->code);
52 tap_diag(" Expected: %u", exp_code);
55 exp_msg = &errmsgs[msg_id];
56 if (!tap_result(!strcmp(err->str, exp_msg), "thread[%u] err->str", t)) {
57 tap_diag("Failed, unexpected result");
58 tap_diag(" Received: %.*s", (int)strlen(exp_msg), err->str);
59 tap_diag(" Expected: %s", exp_msg);
63 static void thread2_func(void)
65 const struct cdecl_error *err;
67 cdecl__errmsg(CDECL__ENOTYPE);
68 err = cdecl_get_error();
71 * Ensure that the error returned in this new thread is distinct from
72 * the error returned in the main thread.
74 tap_diag("thread[2] err: %p", (void *)err);
75 tap_result(thread1_err != err, "thread[2] new state");
77 check_simple_err(err, 2, CDECL_ENOPARSE, CDECL__ENOTYPE);
79 tap_diag("thread[2] exit");
82 #if USE_POSIX_THREADS || USE_ISOC_AND_POSIX_THREADS
83 #define THREAD_API "posix"
86 static void *thread2(void *p)
92 static void run_thread2(void)
97 if (!(err = pthread_create(&t, 0, thread2, 0)))
98 if (!(err = pthread_join(t, 0)))
101 tap_bail_out("run_thread2 failed: %s", strerror(err));
104 static void check_init_once(void)
106 tap_result(1, "init_once # SKIP test not implemented");
109 #elif USE_ISOC_THREADS
110 #define THREAD_API "isoc"
113 static int thread2(void *p)
119 static void run_thread2(void)
122 if (thrd_create(&t, thread2, 0) == thrd_success)
123 if (thrd_join(t, 0) == thrd_success)
126 tap_bail_out("run_thread2 failed");
129 static void check_init_once(void)
131 tap_result(1, "init_once # SKIP test not implemented");
134 #elif USE_WINDOWS_THREADS
135 #define THREAD_API "windows"
136 #define WIN32_LEAN_AND_MEAN
139 static DWORD WINAPI thread2(LPVOID p)
145 static void run_thread2(void)
150 if ((h = CreateThread(NULL, 0, thread2, NULL, 0, &rc))) {
152 if (GetExitCodeThread(h, &rc) && rc != STILL_ACTIVE) {
156 } while (WaitForSingleObject(h, INFINITE) != WAIT_FAILED);
159 tap_bail_out("run_thread2 failed (%lu)", GetLastError());
162 /* Also include init_once sanity test on Windows */
163 #include "thread-w32.h"
165 HANDLE init_semaphore;
167 static void init_once_cb(void)
169 tap_result(1, "init_once_cb start");
171 ReleaseSemaphore(init_semaphore, 2, NULL);
174 tap_diag("init_once_cb exit");
177 static DWORD WINAPI init_thread(LPVOID p)
179 WaitForSingleObject(init_semaphore, INFINITE);
181 tap_diag("init_thread start");
183 tap_diag("init_thread exit");
188 static void check_init_once(void)
194 if (!(init_semaphore = CreateSemaphore(NULL, 0, 10, NULL)))
195 tap_bail_out("check_init_once failed (%lu)", GetLastError());
197 if (!(threads[0] = CreateThread(NULL, 0, init_thread, NULL, 0, &rc)))
198 tap_bail_out("check_init_once failed (%lu)", GetLastError());
199 if (!(threads[1] = CreateThread(NULL, 0, init_thread, NULL, 0, &rc)))
200 tap_bail_out("check_init_once failed (%lu)", GetLastError());
201 if (!(threads[2] = CreateThread(NULL, 0, init_thread, NULL, 0, &rc)))
202 tap_bail_out("check_init_once failed (%lu)", GetLastError());
204 ReleaseSemaphore(init_semaphore, 1, NULL);
205 WaitForMultipleObjects(3, threads, TRUE, INFINITE);
207 for (i = 0; i < 3; i++)
208 CloseHandle(threads[i]);
209 CloseHandle(init_semaphore);
211 for (i = 0; i < 10000000; i++)
220 tap_skip_all("multithreading support disabled");
227 size_t test_live_allocations(void);
228 const struct cdecl_error *err;
230 tap_diag("using thread API: " THREAD_API);
233 /* Simulate an error in the main thread. */
234 cdecl__errmsg(CDECL__ENOMEM);
235 thread1_err = cdecl_get_error();
236 tap_diag("thread[1] err: %p", (void *)thread1_err);
237 check_simple_err(thread1_err, 1, CDECL_ENOMEM, CDECL__ENOMEM);
242 * Back in the main thread, the error previously returned by
243 * cdecl_get_error() should still be valid.
245 check_simple_err(thread1_err, 1, CDECL_ENOMEM, CDECL__ENOMEM);
248 * Moreover, cdecl_get_error should return the same pointer it did
249 * last time (undocumented implementation detail).
251 if (!tap_result((err = cdecl_get_error()) == thread1_err,
252 "thread[1] unchanged state"))
254 tap_diag("Failed, unexpected result");
255 tap_diag(" Received: %p", (void *)err);
256 tap_diag(" Expected: %p", (void *)thread1_err);
260 * Main thread allocation should be the only one left.
262 tap_result(test_live_allocations() == 1, "thread cleanup");
265 * Basic init_once sanity check for Windows.