diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index 2ed26d3b2..e45f22e34 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -678,6 +678,20 @@ static void test_iothread() { max_achieved_thread_count); } +static void test_pthread() { + say(L"Testing pthreads"); + pthread_t result = {}; + int val = 3; + bool made = make_pthread(&result, [&val](){ + val += 2; + }); + do_test(made); + void *ignore = nullptr; + int ret = pthread_join(result, &ignore); + do_test(ret == 0); + do_test(val == 5); +} + static parser_test_error_bits_t detect_argument_errors(const wcstring &src) { parse_node_tree_t tree; if (!parse_tree_from_string(src, parse_flag_none, &tree, NULL, symbol_argument_list)) { @@ -5083,6 +5097,7 @@ int main(int argc, char **argv) { if (should_test_function("convert_nulls")) test_convert_nulls(); if (should_test_function("tok")) test_tokenizer(); if (should_test_function("iothread")) test_iothread(); + if (should_test_function("pthread")) test_pthread(); if (should_test_function("parser")) test_parser(); if (should_test_function("cancellation")) test_cancellation(); if (should_test_function("indents")) test_indents(); diff --git a/src/iothread.cpp b/src/iothread.cpp index d834da8c1..c19f69339 100644 --- a/src/iothread.cpp +++ b/src/iothread.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -149,24 +150,14 @@ static void *iothread_worker(void *unused) { /// Spawn another thread. No lock is held when this is called. static void iothread_spawn() { - // The spawned thread inherits our signal mask. We don't want the thread to ever receive signals - // on the spawned thread, so temporarily block all signals, spawn the thread, and then restore - // it. - sigset_t new_set, saved_set; - sigfillset(&new_set); - DIE_ON_FAILURE(pthread_sigmask(SIG_BLOCK, &new_set, &saved_set)); - // Spawn a thread. If this fails, it means there's already a bunch of threads; it is very // unlikely that they are all on the verge of exiting, so one is likely to be ready to handle // extant requests. So we can ignore failure with some confidence. pthread_t thread = 0; - pthread_create(&thread, NULL, iothread_worker, NULL); - - // We will never join this thread. - DIE_ON_FAILURE(pthread_detach(thread)); - debug(5, "pthread %p spawned", (void *)(intptr_t)thread); - // Restore our sigmask. - DIE_ON_FAILURE(pthread_sigmask(SIG_SETMASK, &saved_set, NULL)); + if (make_pthread(&thread, iothread_worker, nullptr)) { + // We will never join this thread. + DIE_ON_FAILURE(pthread_detach(thread)); + } } int iothread_perform_impl(void_function_t &&func, void_function_t &&completion) { @@ -342,3 +333,48 @@ void iothread_perform_on_main(void_function_t &&func) { // Ok, the request must now be done. assert(req.done); } + +bool make_pthread(pthread_t *result, void *(*func)(void *), void *param) { + // The spawned thread inherits our signal mask. We don't want the thread to ever receive signals + // on the spawned thread, so temporarily block all signals, spawn the thread, and then restore + // it. + sigset_t new_set, saved_set; + sigfillset(&new_set); + DIE_ON_FAILURE(pthread_sigmask(SIG_BLOCK, &new_set, &saved_set)); + + // Spawn a thread. If this fails, it means there's already a bunch of threads; it is very + // unlikely that they are all on the verge of exiting, so one is likely to be ready to handle + // extant requests. So we can ignore failure with some confidence. + pthread_t thread = 0; + int err = pthread_create(&thread, NULL, func, param); + if (err == 0) { + // Success, return the thread. + debug(5, "pthread %p spawned", (void *)(intptr_t)thread); + *result = thread; + } else { + perror("pthread_create"); + } + // Restore our sigmask. + DIE_ON_FAILURE(pthread_sigmask(SIG_SETMASK, &saved_set, NULL)); + return err == 0; +} + +using void_func_t = std::function; + +static void *func_invoker(void *param) { + void_func_t *vf = static_cast(param); + (*vf)(); + delete vf; + return nullptr; +} + +bool make_pthread(pthread_t *result, void_func_t &&func) { + // Copy the function into a heap allocation. + void_func_t *vf = new void_func_t(std::move(func)); + if (make_pthread(result, func_invoker, vf)) { + return true; + } + // Thread spawning failed, clean up our heap allocation. + delete vf; + return false; +} diff --git a/src/iothread.h b/src/iothread.h index 8b29370c8..5e677b639 100644 --- a/src/iothread.h +++ b/src/iothread.h @@ -69,10 +69,16 @@ int iothread_perform(const HANDLER &handler, const COMPLETION &completion) { // variant of iothread_perform without a completion handler inline int iothread_perform(std::function &&func) { - return iothread_perform_impl(std::move(func), std::function()); + return iothread_perform_impl(std::move(func), {}); } /// Performs a function on the main thread, blocking until it completes. void iothread_perform_on_main(std::function &&func); +/// Creates a pthread, manipulating the signal mask so that the thread receives no signals. +/// The pthread runs \p func. +/// \returns true on success, false on failure. +bool make_pthread(pthread_t *result, void *(*func)(void *), void *param); +bool make_pthread(pthread_t *result, std::function &&func); + #endif