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 * Prior returned value from cdecl_get_error in the main thread.
29 static const struct cdecl_error *thread1_err;
32 * Check that the error code and message matches expectations (noting that
33 * this application does not call setlocale to enable translations).
35 static void check_simple_err(const struct cdecl_error *err, unsigned t,
36 unsigned exp_code, unsigned msg_id)
38 static const char errmsgs[] = STRTAB_INITIALIZER;
41 if (!tap_result(err->code == exp_code, "thread[%u] err->code", t)) {
42 tap_diag("Failed, unexpected result");
43 tap_diag(" Received: %u", err->code);
44 tap_diag(" Expected: %u", exp_code);
47 exp_msg = &errmsgs[msg_id];
48 if (!tap_result(!strcmp(err->str, exp_msg), "thread[%u] err->str", t)) {
49 tap_diag("Failed, unexpected result");
50 tap_diag(" Received: %.*s", (int)strlen(exp_msg), err->str);
51 tap_diag(" Expected: %s", exp_msg);
55 #if USE_POSIX_THREADS || USE_ISOC_AND_POSIX_THREADS
56 #define THREAD_API "posix"
59 #define DEFINE_SIMPLE_THREAD_TEST(run_func, thread_entry_func) \
60 static void *run_func##_(void *p) { thread_entry_func(); return 0; } \
61 static void run_func(void) \
63 pthread_t t; int err; \
64 if (!(err = pthread_create(&t, 0, run_func##_, 0))) \
65 if (!(err = pthread_join(t, 0))) \
67 tap_bail_out("%s failed: %s", #run_func, strerror(err)); \
70 static void check_init_once(void)
72 tap_result(1, "init_once # SKIP test not implemented");
75 #elif USE_ISOC_THREADS
76 #define THREAD_API "isoc"
79 #define DEFINE_SIMPLE_THREAD_TEST(run_func, thread_entry_func) \
80 static int run_func##_(void *p) { thread_entry_func(); return 0; } \
81 static void run_func(void) \
84 if (thrd_create(&t, run_func##_, 0) == thrd_success) \
85 if (thrd_join(t, 0) == thrd_success) \
87 tap_bail_out("%s failed", #run_func); \
90 static void check_init_once(void)
92 tap_result(1, "init_once # SKIP test not implemented");
95 #elif USE_WINDOWS_THREADS
96 #define THREAD_API "windows"
97 #define WIN32_LEAN_AND_MEAN
100 #define DEFINE_SIMPLE_THREAD_TEST(run_func, thread_entry_func) \
101 static DWORD WINAPI run_func##_(LPVOID p) \
102 { thread_entry_func(); return 0; } \
103 static void run_func(void) \
105 HANDLE h; DWORD rc; \
106 if ((h = CreateThread(0, 0, run_func##_, 0, 0, &rc))) do { \
107 if (GetExitCodeThread(h, &rc) && rc != STILL_ACTIVE) { \
111 } while (WaitForSingleObject(h, INFINITE) != WAIT_FAILED); \
112 tap_bail_out("%s failed (%lu)", #run_func, GetLastError()); \
115 /* Also include init_once sanity test on Windows */
116 #define TEST_W32_NO_DLLMAIN 1
117 #include "thread-w32.h"
119 HANDLE init_semaphore;
121 static void init_once_cb(void)
123 tap_result(1, "init_once_cb start");
125 ReleaseSemaphore(init_semaphore, 2, NULL);
128 tap_diag("init_once_cb exit");
131 static DWORD WINAPI init_thread(LPVOID p)
133 WaitForSingleObject(init_semaphore, INFINITE);
135 tap_diag("init_thread start");
137 tap_diag("init_thread exit");
142 static void check_init_once(void)
148 if (!(init_semaphore = CreateSemaphore(NULL, 0, 10, NULL)))
149 tap_bail_out("check_init_once failed (%lu)", GetLastError());
151 if (!(threads[0] = CreateThread(NULL, 0, init_thread, NULL, 0, &rc)))
152 tap_bail_out("check_init_once failed (%lu)", GetLastError());
153 if (!(threads[1] = CreateThread(NULL, 0, init_thread, NULL, 0, &rc)))
154 tap_bail_out("check_init_once failed (%lu)", GetLastError());
155 if (!(threads[2] = CreateThread(NULL, 0, init_thread, NULL, 0, &rc)))
156 tap_bail_out("check_init_once failed (%lu)", GetLastError());
158 ReleaseSemaphore(init_semaphore, 1, NULL);
159 WaitForMultipleObjects(3, threads, TRUE, INFINITE);
161 for (i = 0; i < 3; i++)
162 CloseHandle(threads[i]);
163 CloseHandle(init_semaphore);
165 for (i = 0; i < 10000000; i++)
174 tap_skip_all("multithreading support disabled");
180 static void thread_result(void)
182 tap_result(1, "thread runs");
184 DEFINE_SIMPLE_THREAD_TEST(check_thread_create, thread_result)
186 static void err_in_thread(void)
188 const struct cdecl_error *err;
190 cdecl__errmsg(CDECL__ENOTYPE);
191 err = cdecl_get_error();
194 * Ensure that the error returned in this new thread is distinct from
195 * the error returned in the main thread.
197 tap_diag("thread[2] err: %p", (void *)err);
198 tap_result(thread1_err != err, "thread[2] new state");
200 check_simple_err(err, 2, CDECL_ENOPARSE, CDECL__ENOTYPE);
202 tap_diag("thread[2] exit");
204 DEFINE_SIMPLE_THREAD_TEST(check_err_in_thread, err_in_thread)
208 size_t test_live_allocations(void);
209 const struct cdecl_error *err;
211 tap_diag("using thread API: " THREAD_API);
214 /* Create/join a thread to tickle DllMain on Windows. */
215 check_thread_create();
217 /* Simulate an error in the main thread. */
218 cdecl__errmsg(CDECL__ENOMEM);
219 thread1_err = cdecl_get_error();
220 tap_diag("thread[1] err: %p", (void *)thread1_err);
221 check_simple_err(thread1_err, 1, CDECL_ENOMEM, CDECL__ENOMEM);
223 check_err_in_thread();
226 * Back in the main thread, the error previously returned by
227 * cdecl_get_error() should still be valid.
229 check_simple_err(thread1_err, 1, CDECL_ENOMEM, CDECL__ENOMEM);
232 * Moreover, cdecl_get_error should return the same pointer it did
233 * last time (undocumented implementation detail).
235 if (!tap_result((err = cdecl_get_error()) == thread1_err,
236 "thread[1] unchanged state"))
238 tap_diag("Failed, unexpected result");
239 tap_diag(" Received: %p", (void *)err);
240 tap_diag(" Expected: %p", (void *)thread1_err);
244 * Main thread allocation should be the only one left.
246 tap_result(test_live_allocations() == 1, "thread cleanup");
249 * Basic init_once sanity check for Windows.