/* * Sanity check of libcdecl multithread safety. * * Copyright © 2024 Nick Bowler * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include "cdecl-internal.h" #include "errmsg.h" #include "tap.h" /* * Function called from output.c but not needed for error messaging. */ const char *cdecl__token_name(unsigned token) { tap_bail_out("stub cdecl__token_name called"); } /* * Prior returned value from cdecl_get_error in the main thread. */ static const struct cdecl_error *thread1_err; /* * Check that the error code and message matches expectations (noting that * this application does not call setlocale to enable translations). */ static void check_simple_err(const struct cdecl_error *err, unsigned t, unsigned exp_code, unsigned msg_id) { static const char errmsgs[] = STRTAB_INITIALIZER; const char *exp_msg; if (!tap_result(err->code == exp_code, "thread[%u] err->code", t)) { tap_diag("Failed, unexpected result"); tap_diag(" Received: %u", err->code); tap_diag(" Expected: %u", exp_code); } exp_msg = &errmsgs[msg_id]; if (!tap_result(!strcmp(err->str, exp_msg), "thread[%u] err->str", t)) { tap_diag("Failed, unexpected result"); tap_diag(" Received: %.*s", (int)strlen(exp_msg), err->str); tap_diag(" Expected: %s", exp_msg); } } static void thread2_func(void) { const struct cdecl_error *err; cdecl__errmsg(CDECL__ENOTYPE); err = cdecl_get_error(); /* * Ensure that the error returned in this new thread is distinct from * the error returned in the main thread. */ tap_diag("thread[2] err: %p", (void *)err); tap_result(thread1_err != err, "thread[2] new state"); check_simple_err(err, 2, CDECL_ENOPARSE, CDECL__ENOTYPE); tap_diag("thread[2] exit"); } #if USE_POSIX_THREADS || USE_ISOC_AND_POSIX_THREADS #define THREAD_API "posix" #include static void *thread2(void *p) { thread2_func(); return 0; } static void run_thread2(void) { pthread_t t; int err; if (!(err = pthread_create(&t, 0, thread2, 0))) if (!(err = pthread_join(t, 0))) return; tap_bail_out("run_thread2 failed: %s", strerror(err)); } #elif USE_ISOC_THREADS #define THREAD_API "isoc" #include static int thread2(void *p) { thread2_func(); return 0; } static void run_thread2(void) { thrd_t t; if (thrd_create(&t, thread2, 0) == thrd_success) if (thrd_join(t, 0) == thrd_success) return; tap_bail_out("run_thread2 failed"); } #elif USE_WINDOWS_THREADS #define THREAD_API "windows" #define WIN32_LEAN_AND_MEAN #include static DWORD WINAPI thread2(LPVOID p) { thread2_func(); return 0; } static void run_thread2(void) { HANDLE h; DWORD rc; if ((h = CreateThread(NULL, 0, thread2, NULL, 0, &rc))) { do { if (GetExitCodeThread(h, &rc) && rc != STILL_ACTIVE) { CloseHandle(h); return; } } while (WaitForSingleObject(h, INFINITE) != WAIT_FAILED); } tap_bail_out("run_thread2 failed (%lu)", GetLastError()); } #else #undef THREAD_API int main(void) { tap_skip_all("multithreading support disabled"); } #endif #ifdef THREAD_API int main(void) { size_t test_live_allocations(void); const struct cdecl_error *err; tap_diag("using thread API: " THREAD_API); tap_plan(9); /* Simulate an error in the main thread. */ cdecl__errmsg(CDECL__ENOMEM); thread1_err = cdecl_get_error(); tap_diag("thread[1] err: %p", (void *)thread1_err); check_simple_err(thread1_err, 1, CDECL_ENOMEM, CDECL__ENOMEM); run_thread2(); /* * Back in the main thread, the error previously returned by * cdecl_get_error() should still be valid. */ check_simple_err(thread1_err, 1, CDECL_ENOMEM, CDECL__ENOMEM); /* * Moreover, cdecl_get_error should return the same pointer it did * last time (undocumented implementation detail). */ if (!tap_result((err = cdecl_get_error()) == thread1_err, "thread[1] unchanged state")) { tap_diag("Failed, unexpected result"); tap_diag(" Received: %p", (void *)err); tap_diag(" Expected: %p", (void *)thread1_err); } /* * Main thread allocation should be the only one left. */ tap_result(test_live_allocations() == 1, "thread cleanup"); tap_done(); } #endif