]> git.draconx.ca Git - cdecl99.git/blob - t/errthread.c
0f1fdf1de0bee3abc6db8d0a7c9cb962815eddce
[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  * Function called from output.c but not needed for error messaging.
28  */
29 const char *cdecl__token_name(unsigned token)
30 {
31         tap_bail_out("stub cdecl__token_name called");
32 }
33
34 /*
35  * Prior returned value from cdecl_get_error in the main thread.
36  */
37 static const struct cdecl_error *thread1_err;
38
39 /*
40  * Check that the error code and message matches expectations (noting that
41  * this application does not call setlocale to enable translations).
42  */
43 static void check_simple_err(const struct cdecl_error *err, unsigned t,
44                              unsigned exp_code, unsigned msg_id)
45 {
46         static const char errmsgs[] = STRTAB_INITIALIZER;
47         const char *exp_msg;
48
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);
53         }
54
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);
60         }
61 }
62
63 static void thread2_func(void)
64 {
65         const struct cdecl_error *err;
66
67         cdecl__errmsg(CDECL__ENOTYPE);
68         err = cdecl_get_error();
69
70         /*
71          * Ensure that the error returned in this new thread is distinct from
72          * the error returned in the main thread.
73          */
74         tap_diag("thread[2] err: %p", (void *)err);
75         tap_result(thread1_err != err, "thread[2] new state");
76
77         check_simple_err(err, 2, CDECL_ENOPARSE, CDECL__ENOTYPE);
78
79         tap_diag("thread[2] exit");
80 }
81
82 #if USE_POSIX_THREADS || USE_ISOC_AND_POSIX_THREADS
83 #define THREAD_API "posix"
84 #include <pthread.h>
85
86 static void *thread2(void *p)
87 {
88         thread2_func();
89         return 0;
90 }
91
92 static void run_thread2(void)
93 {
94         pthread_t t;
95         int err;
96
97         if (!(err = pthread_create(&t, 0, thread2, 0)))
98                 if (!(err = pthread_join(t, 0)))
99                         return;
100
101         tap_bail_out("run_thread2 failed: %s", strerror(err));
102 }
103
104 static void check_init_once(void)
105 {
106         tap_result(1, "init_once # SKIP test not implemented");
107 }
108
109 #elif USE_ISOC_THREADS
110 #define THREAD_API "isoc"
111 #include <threads.h>
112
113 static int thread2(void *p)
114 {
115         thread2_func();
116         return 0;
117 }
118
119 static void run_thread2(void)
120 {
121         thrd_t t;
122         if (thrd_create(&t, thread2, 0) == thrd_success)
123                 if (thrd_join(t, 0) == thrd_success)
124                         return;
125
126         tap_bail_out("run_thread2 failed");
127 }
128
129 static void check_init_once(void)
130 {
131         tap_result(1, "init_once # SKIP test not implemented");
132 }
133
134 #elif USE_WINDOWS_THREADS
135 #define THREAD_API "windows"
136 #define WIN32_LEAN_AND_MEAN
137 #include <windows.h>
138
139 static DWORD WINAPI thread2(LPVOID p)
140 {
141         thread2_func();
142         return 0;
143 }
144
145 static void run_thread2(void)
146 {
147         HANDLE h;
148         DWORD rc;
149
150         if ((h = CreateThread(NULL, 0, thread2, NULL, 0, &rc))) {
151                 do {
152                         if (GetExitCodeThread(h, &rc) && rc != STILL_ACTIVE) {
153                                 CloseHandle(h);
154                                 return;
155                         }
156                 } while (WaitForSingleObject(h, INFINITE) != WAIT_FAILED);
157         }
158
159         tap_bail_out("run_thread2 failed (%lu)", GetLastError());
160 }
161
162 /* Also include init_once sanity test on Windows */
163 #include "thread-w32.h"
164
165 HANDLE init_semaphore;
166
167 static void init_once_cb(void)
168 {
169         tap_result(1, "init_once_cb start");
170
171         ReleaseSemaphore(init_semaphore, 2, NULL);
172         Sleep(1000);
173
174         tap_diag("init_once_cb exit");
175 }
176
177 static DWORD WINAPI init_thread(LPVOID p)
178 {
179         WaitForSingleObject(init_semaphore, INFINITE);
180
181         tap_diag("init_thread start");
182         init_once();
183         tap_diag("init_thread exit");
184
185         return 0;
186 }
187
188 static void check_init_once(void)
189 {
190         HANDLE threads[3];
191         DWORD rc;
192         int i;
193
194         if (!(init_semaphore = CreateSemaphore(NULL, 0, 10, NULL)))
195                 tap_bail_out("check_init_once failed (%lu)", GetLastError());
196
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());
203
204         ReleaseSemaphore(init_semaphore, 1, NULL);
205         WaitForMultipleObjects(3, threads, TRUE, INFINITE);
206
207         for (i = 0; i < 3; i++)
208                 CloseHandle(threads[i]);
209         CloseHandle(init_semaphore);
210
211         for (i = 0; i < 10000000; i++)
212                 init_once();
213 }
214
215
216 #else
217 #undef THREAD_API
218 int main(void)
219 {
220         tap_skip_all("multithreading support disabled");
221 }
222 #endif
223
224 #ifdef THREAD_API
225 int main(void)
226 {
227         size_t test_live_allocations(void);
228         const struct cdecl_error *err;
229
230         tap_diag("using thread API: " THREAD_API);
231         tap_plan(10);
232
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);
238
239         run_thread2();
240
241         /*
242          * Back in the main thread, the error previously returned by
243          * cdecl_get_error() should still be valid.
244          */
245         check_simple_err(thread1_err, 1, CDECL_ENOMEM, CDECL__ENOMEM);
246
247         /*
248          * Moreover, cdecl_get_error should return the same pointer it did
249          * last time (undocumented implementation detail).
250          */
251         if (!tap_result((err = cdecl_get_error()) == thread1_err,
252                         "thread[1] unchanged state"))
253         {
254                 tap_diag("Failed, unexpected result");
255                 tap_diag("   Received: %p", (void *)err);
256                 tap_diag("   Expected: %p", (void *)thread1_err);
257         }
258
259         /*
260          * Main thread allocation should be the only one left.
261          */
262         tap_result(test_live_allocations() == 1, "thread cleanup");
263
264         /*
265          * Basic init_once sanity check for Windows.
266          */
267         check_init_once();
268
269         tap_done();
270 }
271 #endif