]> git.draconx.ca Git - cdecl99.git/blob - t/errthread.c
Port to use getline.h from dxcommon.
[cdecl99.git] / t / errthread.c
1 /*
2  * Sanity check of libcdecl multithread safety.
3  *
4  * Copyright © 2024 Nick Bowler
5  *
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.
10  *
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.
15  *
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/>.
18  */
19
20 #include <config.h>
21 #include <assert.h>
22 #include "cdecl-internal.h"
23 #include "errmsg.h"
24 #include "tap.h"
25
26 /*
27  * Prior returned value from cdecl_get_error in the main thread.
28  */
29 static const struct cdecl_error *thread1_err;
30
31 /*
32  * Check that the error code and message matches expectations (noting that
33  * this application does not call setlocale to enable translations).
34  */
35 static void check_simple_err(const struct cdecl_error *err, unsigned t,
36                              unsigned exp_code, unsigned msg_id)
37 {
38         static const char errmsgs[] = STRTAB_INITIALIZER;
39         const char *exp_msg;
40
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);
45         }
46
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);
52         }
53 }
54
55 #if USE_POSIX_THREADS || USE_ISOC_AND_POSIX_THREADS
56 #define THREAD_API "posix"
57 #include <pthread.h>
58
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) \
62         { \
63                 pthread_t t; int err; \
64                 if (!(err = pthread_create(&t, 0, run_func##_, 0))) \
65                         if (!(err = pthread_join(t, 0))) \
66                                 return; \
67                 tap_bail_out("%s failed: %s", #run_func, strerror(err)); \
68         }
69
70 static void check_init_once(void)
71 {
72         tap_result(1, "init_once # SKIP test not implemented");
73 }
74
75 #elif USE_ISOC_THREADS
76 #define THREAD_API "isoc"
77 #include <threads.h>
78
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) \
82         { \
83                 thrd_t t; \
84                 if (thrd_create(&t, run_func##_, 0) == thrd_success) \
85                         if (thrd_join(t, 0) == thrd_success) \
86                                 return; \
87                 tap_bail_out("%s failed", #run_func); \
88         }
89
90 static void check_init_once(void)
91 {
92         tap_result(1, "init_once # SKIP test not implemented");
93 }
94
95 #elif USE_WINDOWS_THREADS
96 #define THREAD_API "windows"
97 #define WIN32_LEAN_AND_MEAN
98 #include <windows.h>
99
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) \
104         { \
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) { \
108                                 CloseHandle(h); \
109                                 return; \
110                         } \
111                 } while (WaitForSingleObject(h, INFINITE) != WAIT_FAILED); \
112                 tap_bail_out("%s failed (%lu)", #run_func, GetLastError()); \
113         }
114
115 /* Also include init_once sanity test on Windows */
116 #define TEST_W32_NO_DLLMAIN 1
117 #include "thread-w32.h"
118
119 HANDLE init_semaphore;
120
121 static void init_once_cb(void)
122 {
123         tap_result(1, "init_once_cb start");
124
125         ReleaseSemaphore(init_semaphore, 2, NULL);
126         Sleep(1000);
127
128         tap_diag("init_once_cb exit");
129 }
130
131 static DWORD WINAPI init_thread(LPVOID p)
132 {
133         WaitForSingleObject(init_semaphore, INFINITE);
134
135         tap_diag("init_thread start");
136         init_once();
137         tap_diag("init_thread exit");
138
139         return 0;
140 }
141
142 static void check_init_once(void)
143 {
144         HANDLE threads[3];
145         DWORD rc;
146         int i;
147
148         if (!(init_semaphore = CreateSemaphore(NULL, 0, 10, NULL)))
149                 tap_bail_out("check_init_once failed (%lu)", GetLastError());
150
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());
157
158         ReleaseSemaphore(init_semaphore, 1, NULL);
159         WaitForMultipleObjects(3, threads, TRUE, INFINITE);
160
161         for (i = 0; i < 3; i++)
162                 CloseHandle(threads[i]);
163         CloseHandle(init_semaphore);
164
165         for (i = 0; i < 10000000; i++)
166                 init_once();
167 }
168
169
170 #else
171 #undef THREAD_API
172 int main(void)
173 {
174         tap_skip_all("multithreading support disabled");
175 }
176 #endif
177
178 #ifdef THREAD_API
179
180 static void thread_result(void)
181 {
182         tap_result(1, "thread runs");
183 }
184 DEFINE_SIMPLE_THREAD_TEST(check_thread_create, thread_result)
185
186 static void err_in_thread(void)
187 {
188         const struct cdecl_error *err;
189
190         cdecl__errmsg(CDECL__ENOTYPE);
191         err = cdecl_get_error();
192
193         /*
194          * Ensure that the error returned in this new thread is distinct from
195          * the error returned in the main thread.
196          */
197         tap_diag("thread[2] err: %p", (void *)err);
198         tap_result(thread1_err != err, "thread[2] new state");
199
200         check_simple_err(err, 2, CDECL_ENOPARSE, CDECL__ENOTYPE);
201
202         tap_diag("thread[2] exit");
203 }
204 DEFINE_SIMPLE_THREAD_TEST(check_err_in_thread, err_in_thread)
205
206 int main(void)
207 {
208         size_t test_live_allocations(void);
209         const struct cdecl_error *err;
210
211         tap_diag("using thread API: " THREAD_API);
212         tap_plan(11);
213
214         /* Create/join a thread to tickle DllMain on Windows. */
215         check_thread_create();
216
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);
222
223         check_err_in_thread();
224
225         /*
226          * Back in the main thread, the error previously returned by
227          * cdecl_get_error() should still be valid.
228          */
229         check_simple_err(thread1_err, 1, CDECL_ENOMEM, CDECL__ENOMEM);
230
231         /*
232          * Moreover, cdecl_get_error should return the same pointer it did
233          * last time (undocumented implementation detail).
234          */
235         if (!tap_result((err = cdecl_get_error()) == thread1_err,
236                         "thread[1] unchanged state"))
237         {
238                 tap_diag("Failed, unexpected result");
239                 tap_diag("   Received: %p", (void *)err);
240                 tap_diag("   Expected: %p", (void *)thread1_err);
241         }
242
243         /*
244          * Main thread allocation should be the only one left.
245          */
246         tap_result(test_live_allocations() == 1, "thread cleanup");
247
248         /*
249          * Basic init_once sanity check for Windows.
250          */
251         check_init_once();
252
253         tap_done();
254 }
255 #endif