diff --git a/Configure b/Configure index 01c2d0bafe..fbafe0e867 100755 --- a/Configure +++ b/Configure @@ -655,6 +655,9 @@ my @disable_cascades = ( "fips" => [ "fips-securitychecks", "acvp-tests" ], + "threads" => [ "thread-pool" ], + "thread-pool" => [ "default-thread-pool" ], + "deprecated-3.0" => [ "engine", "srp" ] ); @@ -812,8 +815,6 @@ while (@argvcopy) s /^-?-?shared$/enable-shared/; s /^sctp$/enable-sctp/; s /^threads$/enable-threads/; - s /^thread-pool$/enable-thread-pool/; - s /^default-thread-pool$/enable-default-thread-pool/; s /^zlib$/enable-zlib/; s /^zlib-dynamic$/enable-zlib-dynamic/; s /^fips$/enable-fips/; @@ -1400,14 +1401,6 @@ if (grep { $_ =~ /(?:^|\s)-static(?:\s|$)/ } @{$config{LDFLAGS}}) { disable('static', 'pic', 'threads'); } -if ($disabled{threads}) { - disable('unavailable', 'thread-pool'); -} - -if ($disabled{"thread-pool"}) { - disable('unavailable', 'default-thread-pool'); -} - # Allow overriding the build file name $config{build_file} = env('BUILDFILE') || $target{build_file} || "Makefile"; @@ -1506,12 +1499,6 @@ foreach (grep /^-fsanitize=/, @{$config{CFLAGS} || []}) { unless($disabled{threads}) { push @{$config{openssl_feature_defines}}, "OPENSSL_THREADS"; } -unless($disabled{"thread-pool"}) { - push @{$config{openssl_feature_defines}}, "OPENSSL_THREAD_POOL"; -} -unless($disabled{"default-thread-pool"}) { - push @{$config{openssl_feature_defines}}, "OPENSSL_DEFAULT_THREAD_POOL"; -} my $no_shared_warn=0; if (($target{shared_target} // '') eq "") diff --git a/INSTALL.md b/INSTALL.md index 3c995349bd..f16ecf9c89 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -899,6 +899,27 @@ will usually require additional system-dependent options! See [Notes on multi-threading](#notes-on-multi-threading) below. +### no-thread-pool + +Don't build with support for thread pool functionality. + +### thread-pool + +Build with thread pool functionality. If enabled, OpenSSL algorithms may +use the thread pool to perform parallel computation. This option in itself +does not enable OpenSSL to spawn new threads. Currently the only supported +thread pool mechanism is the default thread pool. + +### no-default-thread-pool + +Don't build with support for default thread pool functionality. + +### default-thread-pool + +Build with default thread pool functionality. If enabled, OpenSSL may create +and manage threads up to a maximum number of threads authorized by the +application. Supported on POSIX compliant platforms and Windows. + ### enable-trace Build with support for the integrated tracing api. diff --git a/crypto/build.info b/crypto/build.info index f5b29cca1c..c064351b5a 100644 --- a/crypto/build.info +++ b/crypto/build.info @@ -6,7 +6,7 @@ SUBDIRS=objects buffer bio stack lhash rand evp asn1 pem x509 conf \ siphash sm3 des aes rc2 rc4 rc5 idea aria bf cast camellia \ seed sm4 chacha modes bn ec rsa dsa dh sm2 dso engine \ err comp http ocsp cms ts srp cmac ct async ess crmf cmp encode_decode \ - ffc hpke + ffc hpke thread LIBS=../libcrypto diff --git a/crypto/context.c b/crypto/context.c index aec9ecd4ac..a7b1832cbc 100644 --- a/crypto/context.c +++ b/crypto/context.c @@ -36,6 +36,9 @@ struct ossl_lib_ctx_st { OSSL_METHOD_STORE *encoder_store; OSSL_METHOD_STORE *store_loader_store; void *self_test_cb; +#endif +#if defined(OPENSSL_THREADS) + void *threads; #endif void *rand_crngt; #ifdef FIPS_MODULE @@ -171,6 +174,12 @@ static int context_init(OSSL_LIB_CTX *ctx) goto err; #endif +#if defined(OPENSSL_THREADS) + ctx->threads = ossl_threads_ctx_new(ctx); + if (ctx->threads == NULL) + goto err; +#endif + /* Low priority. */ #ifndef FIPS_MODULE ctx->child_provider = ossl_child_prov_ctx_new(ctx); @@ -299,6 +308,13 @@ static void context_deinit_objs(OSSL_LIB_CTX *ctx) } #endif +#if defined(OPENSSL_THREADS) + if (ctx->threads != NULL) { + ossl_threads_ctx_free(ctx->threads); + ctx->threads = NULL; + } +#endif + /* Low priority. */ #ifndef FIPS_MODULE if (ctx->child_provider != NULL) { @@ -526,6 +542,10 @@ void *ossl_lib_ctx_get_data(OSSL_LIB_CTX *ctx, int index) case OSSL_LIB_CTX_SELF_TEST_CB_INDEX: return ctx->self_test_cb; #endif +#if defined(OPENSSL_THREADS) + case OSSL_LIB_CTX_THREAD_INDEX: + return ctx->threads; +#endif case OSSL_LIB_CTX_RAND_CRNGT_INDEX: { diff --git a/crypto/thread/api.c b/crypto/thread/api.c new file mode 100644 index 0000000000..e025d24cea --- /dev/null +++ b/crypto/thread/api.c @@ -0,0 +1,73 @@ +/* + * Copyright 2019-2021 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#include +#include +#include + +uint32_t OSSL_get_thread_support_flags(void) +{ + int support = 0; + +#if !defined(OPENSSL_NO_THREAD_POOL) + support |= OSSL_THREAD_SUPPORT_FLAG_THREAD_POOL; +#endif +#if !defined(OPENSSL_NO_DEFAULT_THREAD_POOL) + support |= OSSL_THREAD_SUPPORT_FLAG_DEFAULT_SPAWN; +#endif + + return support; +} + +#if defined(OPENSSL_NO_THREAD_POOL) || defined(OPENSSL_NO_DEFAULT_THREAD_POOL) + +int OSSL_set_max_threads(OSSL_LIB_CTX *ctx, uint64_t max_threads) +{ + return 0; +} + +uint64_t OSSL_get_max_threads(OSSL_LIB_CTX *ctx) +{ + return 0; +} + +#else + +uint64_t OSSL_get_max_threads(OSSL_LIB_CTX *ctx) +{ + uint64_t ret = 0; + OSSL_LIB_CTX_THREADS *tdata = OSSL_LIB_CTX_GET_THREADS(ctx); + + if (tdata == NULL) + goto fail; + + ossl_crypto_mutex_lock(tdata->lock); + ret = tdata->max_threads; + ossl_crypto_mutex_unlock(tdata->lock); + +fail: + return ret; +} + +int OSSL_set_max_threads(OSSL_LIB_CTX *ctx, uint64_t max_threads) +{ + OSSL_LIB_CTX_THREADS *tdata; + + tdata = OSSL_LIB_CTX_GET_THREADS(ctx); + if (tdata == NULL) + return 0; + + ossl_crypto_mutex_lock(tdata->lock); + tdata->max_threads = max_threads; + ossl_crypto_mutex_unlock(tdata->lock); + + return 1; +} + +#endif diff --git a/crypto/thread/arch.c b/crypto/thread/arch.c new file mode 100644 index 0000000000..565f87b93a --- /dev/null +++ b/crypto/thread/arch.c @@ -0,0 +1,91 @@ +/* + * Copyright 2019-2021 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#include +#include + +#if defined(OPENSSL_THREADS) + +CRYPTO_THREAD *ossl_crypto_thread_native_start(CRYPTO_THREAD_ROUTINE routine, + void *data, int joinable) +{ + CRYPTO_THREAD *handle; + + if (routine == NULL) + return NULL; + + handle = OPENSSL_zalloc(sizeof(*handle)); + if (handle == NULL) + return NULL; + + if ((handle->lock = ossl_crypto_mutex_new()) == NULL) + goto fail; + if ((handle->statelock = ossl_crypto_mutex_new()) == NULL) + goto fail; + if ((handle->condvar = ossl_crypto_condvar_new()) == NULL) + goto fail; + + handle->data = data; + handle->routine = routine; + handle->joinable = joinable; + + if (ossl_crypto_thread_native_spawn(handle) == 1) + return handle; + +fail: + ossl_crypto_condvar_free(&handle->condvar); + ossl_crypto_mutex_free(&handle->statelock); + ossl_crypto_mutex_free(&handle->lock); + OPENSSL_free(handle); + return NULL; +} + +int ossl_crypto_thread_native_clean(CRYPTO_THREAD *handle) +{ + uint64_t req_state_mask; + + if (handle == NULL) + return 0; + + req_state_mask = 0; + req_state_mask |= CRYPTO_THREAD_FINISHED; + req_state_mask |= CRYPTO_THREAD_TERMINATED; + req_state_mask |= CRYPTO_THREAD_JOINED; + + ossl_crypto_mutex_lock(handle->statelock); + if (CRYPTO_THREAD_GET_STATE(handle, req_state_mask) == 0) { + ossl_crypto_mutex_unlock(handle->statelock); + return 0; + } + ossl_crypto_mutex_unlock(handle->statelock); + + ossl_crypto_mutex_free(&handle->lock); + ossl_crypto_mutex_free(&handle->statelock); + ossl_crypto_condvar_free(&handle->condvar); + + OPENSSL_free(handle->handle); + OPENSSL_free(handle); + + return 1; +} + +#else + +CRYPTO_THREAD *ossl_crypto_thread_native_start(CRYPTO_THREAD_ROUTINE routine, + void *data, int joinable) +{ + return NULL; +} + +int ossl_crypto_thread_native_clean(CRYPTO_THREAD *handle) +{ + return 0; +} + +#endif diff --git a/crypto/thread/arch/thread_none.c b/crypto/thread/arch/thread_none.c new file mode 100644 index 0000000000..8a0389f5cb --- /dev/null +++ b/crypto/thread/arch/thread_none.c @@ -0,0 +1,82 @@ +/* + * Copyright 2019-2021 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#include + +#if defined(OPENSSL_THREADS_NONE) + +int ossl_crypto_thread_native_spawn(CRYPTO_THREAD *thread) +{ + return 0; +} + +int ossl_crypto_thread_native_join(CRYPTO_THREAD *thread, CRYPTO_THREAD_RETVAL *retval) +{ + return 0; +} + +int ossl_crypto_thread_native_terminate(CRYPTO_THREAD *thread) +{ + return 0; +} + +int ossl_crypto_thread_native_exit(void) +{ + return 0; +} + +int ossl_crypto_thread_native_is_self(CRYPTO_THREAD *thread) +{ + return 0; +} + +CRYPTO_MUTEX *ossl_crypto_mutex_new(void) +{ + return NULL; +} + +void ossl_crypto_mutex_lock(CRYPTO_MUTEX *mutex) +{ +} + +int ossl_crypto_mutex_try_lock(CRYPTO_MUTEX *mutex) +{ + return 0; +} + +void ossl_crypto_mutex_unlock(CRYPTO_MUTEX *mutex) +{ +} + +void ossl_crypto_mutex_free(CRYPTO_MUTEX **mutex) +{ +} + +CRYPTO_CONDVAR *ossl_crypto_condvar_new(void) +{ + return NULL; +} + +void ossl_crypto_condvar_wait(CRYPTO_CONDVAR *cv, CRYPTO_MUTEX *mutex) +{ +} + +void ossl_crypto_condvar_broadcast(CRYPTO_CONDVAR *cv) +{ +} + +void ossl_crypto_condvar_free(CRYPTO_CONDVAR **cv) +{ +} + +void ossl_crypto_mem_barrier(void) +{ +} + +#endif diff --git a/crypto/thread/arch/thread_posix.c b/crypto/thread/arch/thread_posix.c new file mode 100644 index 0000000000..d74cfddab3 --- /dev/null +++ b/crypto/thread/arch/thread_posix.c @@ -0,0 +1,305 @@ +/* + * Copyright 2019-2021 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#include + +#if defined(OPENSSL_THREADS_POSIX) +# define _GNU_SOURCE +# include +# include +# include + +static void *thread_start_thunk(void *vthread) +{ + CRYPTO_THREAD *thread; + CRYPTO_THREAD_RETVAL ret; + + thread = (CRYPTO_THREAD *)vthread; + + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); + pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); + + ret = thread->routine(thread->data); + ossl_crypto_mutex_lock(thread->statelock); + CRYPTO_THREAD_SET_STATE(thread, CRYPTO_THREAD_FINISHED); + thread->retval = ret; + ossl_crypto_condvar_broadcast(thread->condvar); + ossl_crypto_mutex_unlock(thread->statelock); + + return NULL; +} + +int ossl_crypto_thread_native_spawn(CRYPTO_THREAD *thread) +{ + int ret; + pthread_attr_t attr; + pthread_t *handle; + + handle = OPENSSL_zalloc(sizeof(*handle)); + if (handle == NULL) + goto fail; + + pthread_attr_init(&attr); + if (!thread->joinable) + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + ret = pthread_create(handle, &attr, thread_start_thunk, thread); + pthread_attr_destroy(&attr); + + if (ret != 0) + goto fail; + + thread->handle = handle; + return 1; + +fail: + thread->handle = NULL; + OPENSSL_free(handle); + return 0; +} + +int ossl_crypto_thread_native_join(CRYPTO_THREAD *thread, CRYPTO_THREAD_RETVAL *retval) +{ + void *thread_retval; + pthread_t *handle; + uint64_t req_state_mask; + + if (thread == NULL) + return 0; + + req_state_mask = CRYPTO_THREAD_TERMINATED | CRYPTO_THREAD_JOINED; + + ossl_crypto_mutex_lock(thread->statelock); + if (CRYPTO_THREAD_GET_STATE(thread, req_state_mask)) { + ossl_crypto_mutex_unlock(thread->statelock); + goto pass; + } + while (!CRYPTO_THREAD_GET_STATE(thread, CRYPTO_THREAD_FINISHED)) + ossl_crypto_condvar_wait(thread->condvar, thread->statelock); + ossl_crypto_mutex_unlock(thread->statelock); + + handle = (pthread_t *) thread->handle; + if (handle == NULL) + goto fail; + + if (pthread_join(*handle, &thread_retval) != 0) + goto fail; + + /* + * Join return value may be non-NULL when the thread has been cancelled, + * as indicated by thread_retval set to PTHREAD_CANCELLED. + */ + if (thread_retval != NULL) + goto fail; + +pass: + if (retval != NULL) + *retval = thread->retval; + + ossl_crypto_mutex_lock(thread->statelock); + CRYPTO_THREAD_UNSET_ERROR(thread, CRYPTO_THREAD_JOINED); + CRYPTO_THREAD_SET_STATE(thread, CRYPTO_THREAD_JOINED); + ossl_crypto_mutex_unlock(thread->statelock); + return 1; + +fail: + ossl_crypto_mutex_lock(thread->statelock); + CRYPTO_THREAD_SET_ERROR(thread, CRYPTO_THREAD_JOINED); + ossl_crypto_mutex_unlock(thread->statelock); + return 0; +} + +int ossl_crypto_thread_native_terminate(CRYPTO_THREAD *thread) +{ + void *res; + uint64_t mask; + pthread_t *handle; + + mask = CRYPTO_THREAD_FINISHED; + mask |= CRYPTO_THREAD_TERMINATED; + mask |= CRYPTO_THREAD_JOINED; + + if (thread == NULL) + return 0; + + ossl_crypto_mutex_lock(thread->statelock); + if (thread->handle == NULL || CRYPTO_THREAD_GET_STATE(thread, mask)) + goto terminated; + ossl_crypto_mutex_unlock(thread->statelock); + + handle = thread->handle; + if (pthread_cancel(*handle) != 0) { + ossl_crypto_mutex_lock(thread->statelock); + CRYPTO_THREAD_SET_ERROR(thread, CRYPTO_THREAD_TERMINATED); + ossl_crypto_mutex_unlock(thread->statelock); + return 0; + } + if (pthread_join(*handle, &res) != 0) + return 0; + if (res != PTHREAD_CANCELED) + return 0; + + thread->handle = NULL; + OPENSSL_free(handle); + + ossl_crypto_mutex_lock(thread->statelock); +terminated: + CRYPTO_THREAD_UNSET_ERROR(thread, CRYPTO_THREAD_TERMINATED); + CRYPTO_THREAD_SET_STATE(thread, CRYPTO_THREAD_TERMINATED); + ossl_crypto_mutex_unlock(thread->statelock); + return 1; +} + +int ossl_crypto_thread_native_exit(void) +{ + pthread_exit(NULL); + return 1; +} + +int ossl_crypto_thread_native_is_self(CRYPTO_THREAD *thread) +{ + return pthread_equal(*(pthread_t *)thread->handle, pthread_self()); +} + +CRYPTO_MUTEX *ossl_crypto_mutex_new(void) +{ + pthread_mutex_t *mutex; + + if ((mutex = OPENSSL_zalloc(sizeof(*mutex))) == NULL) + return NULL; + if (pthread_mutex_init(mutex, NULL) != 0) { + OPENSSL_free(mutex); + return NULL; + } + return (CRYPTO_MUTEX *)mutex; +} + +int ossl_crypto_mutex_try_lock(CRYPTO_MUTEX *mutex) +{ + pthread_mutex_t *mutex_p; + + mutex_p = (pthread_mutex_t *)mutex; + + if (pthread_mutex_trylock(mutex_p) == EBUSY) + return 0; + + return 1; +} + +void ossl_crypto_mutex_lock(CRYPTO_MUTEX *mutex) +{ + pthread_mutex_t *mutex_p; + + mutex_p = (pthread_mutex_t *)mutex; + pthread_mutex_lock(mutex_p); +} + +void ossl_crypto_mutex_unlock(CRYPTO_MUTEX *mutex) +{ + pthread_mutex_t *mutex_p; + + mutex_p = (pthread_mutex_t *)mutex; + pthread_mutex_unlock(mutex_p); +} + +void ossl_crypto_mutex_free(CRYPTO_MUTEX **mutex) +{ + pthread_mutex_t **mutex_p; + + if (mutex == NULL) + return; + + mutex_p = (pthread_mutex_t **)mutex; + if (*mutex_p != NULL) + pthread_mutex_destroy(*mutex_p); + OPENSSL_free(*mutex_p); + *mutex = NULL; +} + +CRYPTO_CONDVAR *ossl_crypto_condvar_new(void) +{ + pthread_cond_t *cv_p; + + if ((cv_p = OPENSSL_zalloc(sizeof(*cv_p))) == NULL) + return NULL; + if (pthread_cond_init(cv_p, NULL) != 0) { + OPENSSL_free(cv_p); + return NULL; + } + return (CRYPTO_CONDVAR *) cv_p; +} + +void ossl_crypto_condvar_wait(CRYPTO_CONDVAR *cv, CRYPTO_MUTEX *mutex) +{ + pthread_cond_t *cv_p; + pthread_mutex_t *mutex_p; + + cv_p = (pthread_cond_t *)cv; + mutex_p = (pthread_mutex_t *)mutex; + pthread_cond_wait(cv_p, mutex_p); +} + +void ossl_crypto_condvar_broadcast(CRYPTO_CONDVAR *cv) +{ + pthread_cond_t *cv_p; + + cv_p = (pthread_cond_t *)cv; + pthread_cond_broadcast(cv_p); +} + +void ossl_crypto_condvar_free(CRYPTO_CONDVAR **cv) +{ + pthread_cond_t **cv_p; + + if (cv == NULL) + return; + + cv_p = (pthread_cond_t **)cv; + if (*cv_p != NULL) + pthread_cond_destroy(*cv_p); + OPENSSL_free(*cv_p); + *cv_p = NULL; +} + +void ossl_crypto_mem_barrier(void) +{ +# if defined(__clang__) || defined(__GNUC__) + __sync_synchronize(); +# elif !defined(OPENSSL_NO_ASM) +# if defined(__alpha__) /* Alpha */ + __asm__ volatile("mb" : : : "memory"); +# elif defined(__amd64__) || defined(__i386__) || defined(__i486__) \ + || defined(__i586__) || defined(__i686__) || defined(__i386) /* x86 */ + __asm__ volatile("mfence" : : : "memory"); +# elif defined(__arm__) || defined(__aarch64__) /* ARMv7, ARMv8 */ + __asm__ volatile("dmb ish" : : : "memory"); +# elif defined(__hppa__) /* PARISC */ + __asm__ volatile("" : : : "memory"); +# elif defined(__mips__) /* MIPS */ + __asm__ volatile("sync" : : : "memory"); +# elif defined(__powerpc__) || defined(__powerpc64__) /* power, ppc64, ppc64le */ + __asm__ volatile("sync" : : : "memory"); +# elif defined(__sparc__) + __asm__ volatile("ba,pt %%xcc, 1f\n\t" \ + " membar #Sync\n" \ + "1:\n" \ + : : : "memory"); +# elif defined(__s390__) || defined(__s390x__) /* z */ + __asm__ volatile("bcr 15,0" : : : "memory"); +# elif defined(__riscv) || defined(__riscv__) /* riscv */ + __asm__ volatile("fence iorw,iorw" : : : "memory"); +# else /* others, compiler only */ + __asm__ volatile("" : : : "memory"); +# endif +# else + /* compiler only barrier */ + __asm__ volatile("" : : : "memory"); +# endif +} + +#endif diff --git a/crypto/thread/arch/thread_win.c b/crypto/thread/arch/thread_win.c new file mode 100644 index 0000000000..b71cda85ea --- /dev/null +++ b/crypto/thread/arch/thread_win.c @@ -0,0 +1,254 @@ +/* + * Copyright 2019-2021 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#include + +#if defined(OPENSSL_THREADS_WINNT) +# include +# include + +static DWORD __stdcall thread_start_thunk(LPVOID vthread) +{ + CRYPTO_THREAD *thread; + CRYPTO_THREAD_RETVAL ret; + + thread = (CRYPTO_THREAD *)vthread; + + thread->thread_id = GetCurrentThreadId(); + + ret = thread->routine(thread->data); + ossl_crypto_mutex_lock(thread->statelock); + CRYPTO_THREAD_SET_STATE(thread, CRYPTO_THREAD_FINISHED); + thread->retval = ret; + ossl_crypto_condvar_broadcast(thread->condvar); + ossl_crypto_mutex_unlock(thread->statelock); + + return 0; +} + +int ossl_crypto_thread_native_spawn(CRYPTO_THREAD *thread) +{ + HANDLE *handle; + + handle = OPENSSL_zalloc(sizeof(*handle)); + if (handle == NULL) + goto fail; + + *handle = (HANDLE)_beginthreadex(NULL, 0, &thread_start_thunk, thread, 0, NULL); + if (*handle == NULL) + goto fail; + + thread->handle = handle; + return 1; + +fail: + thread->handle = NULL; + OPENSSL_free(handle); + return 0; +} + +int ossl_crypto_thread_native_join(CRYPTO_THREAD *thread, CRYPTO_THREAD_RETVAL *retval) +{ + int req_state_mask; + DWORD thread_retval; + HANDLE *handle; + + if (thread == NULL) + return 0; + + req_state_mask = CRYPTO_THREAD_TERMINATED | CRYPTO_THREAD_JOINED; + + ossl_crypto_mutex_lock(thread->statelock); + if (CRYPTO_THREAD_GET_STATE(thread, req_state_mask)) + goto pass; + while (!CRYPTO_THREAD_GET_STATE(thread, CRYPTO_THREAD_FINISHED)) + ossl_crypto_condvar_wait(thread->condvar, thread->statelock); + + handle = (HANDLE *) thread->handle; + if (handle == NULL) + goto fail; + + if (WaitForSingleObject(*handle, INFINITE) != WAIT_OBJECT_0) + goto fail; + + if (GetExitCodeThread(*handle, &thread_retval) == 0) + goto fail; + + /* + * GetExitCodeThread call followed by this check is to make sure that + * the thread exitted properly. In particular, thread_retval may be + * non-zero when exitted via explicit ExitThread/TerminateThread or + * if the thread is still active (returns STILL_ACTIVE (259)). + */ + if (thread_retval != 0) + goto fail; + + if (CloseHandle(*handle) == 0) + goto fail; + +pass: + if (retval != NULL) + *retval = thread->retval; + + CRYPTO_THREAD_UNSET_ERROR(thread, CRYPTO_THREAD_JOINED); + CRYPTO_THREAD_SET_STATE(thread, CRYPTO_THREAD_JOINED); + ossl_crypto_mutex_unlock(thread->statelock); + return 1; + +fail: + CRYPTO_THREAD_SET_ERROR(thread, CRYPTO_THREAD_JOINED); + ossl_crypto_mutex_unlock(thread->statelock); + return 0; +} + +int ossl_crypto_thread_native_terminate(CRYPTO_THREAD *thread) +{ + uint64_t mask; + HANDLE *handle; + + mask = CRYPTO_THREAD_FINISHED; + mask |= CRYPTO_THREAD_TERMINATED; + mask |= CRYPTO_THREAD_JOINED; + + if (thread == NULL) + return 1; + + ossl_crypto_mutex_lock(thread->statelock); + if (thread->handle == NULL || CRYPTO_THREAD_GET_STATE(thread, mask)) + goto terminated; + ossl_crypto_mutex_unlock(thread->statelock); + + handle = thread->handle; + if (WaitForSingleObject(*handle, 0) != WAIT_OBJECT_0) { + if (TerminateThread(*handle, STILL_ACTIVE) == 0) { + ossl_crypto_mutex_lock(thread->statelock); + CRYPTO_THREAD_SET_ERROR(thread, CRYPTO_THREAD_TERMINATED); + ossl_crypto_mutex_unlock(thread->statelock); + return 0; + } + } + + if (CloseHandle(*handle) == 0) { + CRYPTO_THREAD_SET_ERROR(thread, CRYPTO_THREAD_TERMINATED); + return 0; + } + + thread->handle = NULL; + OPENSSL_free(handle); + + ossl_crypto_mutex_lock(thread->statelock); +terminated: + CRYPTO_THREAD_UNSET_ERROR(thread, CRYPTO_THREAD_TERMINATED); + CRYPTO_THREAD_SET_STATE(thread, CRYPTO_THREAD_TERMINATED); + ossl_crypto_mutex_unlock(thread->statelock); + return 1; +} + +int ossl_crypto_thread_native_exit(void) +{ + _endthreadex(0); + return 1; +} + +int ossl_crypto_thread_native_is_self(CRYPTO_THREAD *thread) +{ + return thread->thread_id == GetCurrentThreadId(); +} + +CRYPTO_MUTEX *ossl_crypto_mutex_new(void) +{ + CRITICAL_SECTION *mutex; + + if ((mutex = OPENSSL_zalloc(sizeof(*mutex))) == NULL) + return NULL; + InitializeCriticalSection(mutex); + return (CRYPTO_MUTEX *)mutex; +} + +void ossl_crypto_mutex_lock(CRYPTO_MUTEX *mutex) +{ + CRITICAL_SECTION *mutex_p; + + mutex_p = (CRITICAL_SECTION *)mutex; + EnterCriticalSection(mutex_p); +} + +int ossl_crypto_mutex_try_lock(CRYPTO_MUTEX *mutex) +{ + CRITICAL_SECTION *mutex_p; + + mutex_p = (CRITICAL_SECTION *)mutex; + if (TryEnterCriticalSection(mutex_p)) + return 1; + + return 0; +} + +void ossl_crypto_mutex_unlock(CRYPTO_MUTEX *mutex) +{ + CRITICAL_SECTION *mutex_p; + + mutex_p = (CRITICAL_SECTION *)mutex; + LeaveCriticalSection(mutex_p); +} + +void ossl_crypto_mutex_free(CRYPTO_MUTEX **mutex) +{ + CRITICAL_SECTION **mutex_p; + + mutex_p = (CRITICAL_SECTION **)mutex; + if (*mutex_p != NULL) + DeleteCriticalSection(*mutex_p); + OPENSSL_free(*mutex_p); + *mutex = NULL; +} + +CRYPTO_CONDVAR *ossl_crypto_condvar_new(void) +{ + CONDITION_VARIABLE *cv_p; + + if ((cv_p = OPENSSL_zalloc(sizeof(*cv_p))) == NULL) + return NULL; + InitializeConditionVariable(cv_p); + return (CRYPTO_CONDVAR *)cv_p; +} + +void ossl_crypto_condvar_wait(CRYPTO_CONDVAR *cv, CRYPTO_MUTEX *mutex) +{ + CONDITION_VARIABLE *cv_p; + CRITICAL_SECTION *mutex_p; + + cv_p = (CONDITION_VARIABLE *)cv; + mutex_p = (CRITICAL_SECTION *)mutex; + SleepConditionVariableCS(cv_p, mutex_p, INFINITE); +} + +void ossl_crypto_condvar_broadcast(CRYPTO_CONDVAR *cv) +{ + CONDITION_VARIABLE *cv_p; + + cv_p = (CONDITION_VARIABLE *)cv; + WakeAllConditionVariable(cv_p); +} + +void ossl_crypto_condvar_free(CRYPTO_CONDVAR **cv) +{ + CONDITION_VARIABLE **cv_p; + + cv_p = (CONDITION_VARIABLE **)cv; + OPENSSL_free(*cv_p); + *cv_p = NULL; +} + +void ossl_crypto_mem_barrier(void) +{ + MemoryBarrier(); +} + +#endif diff --git a/crypto/thread/build.info b/crypto/thread/build.info new file mode 100644 index 0000000000..3ab689d4a4 --- /dev/null +++ b/crypto/thread/build.info @@ -0,0 +1,8 @@ +LIBS=../../libcrypto + +$THREADS=\ + api.c internal.c arch.c \ + arch/thread_win.c arch/thread_posix.c arch/thread_none.c + +SOURCE[../../libcrypto]=$THREADS +SOURCE[../../providers/libfips.a]=$THREADS diff --git a/crypto/thread/internal.c b/crypto/thread/internal.c new file mode 100644 index 0000000000..22af876cd7 --- /dev/null +++ b/crypto/thread/internal.c @@ -0,0 +1,161 @@ +/* + * Copyright 2019-2021 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#include +#include +#include +#include +#include +#include + +#if !defined(OPENSSL_NO_DEFAULT_THREAD_POOL) + +static ossl_inline uint64_t _ossl_get_avail_threads(OSSL_LIB_CTX_THREADS *tdata) +{ + /* assumes that tdata->lock is taken */ + return tdata->max_threads - tdata->active_threads; +} + +uint64_t ossl_get_avail_threads(OSSL_LIB_CTX *ctx) +{ + uint64_t retval = 0; + OSSL_LIB_CTX_THREADS *tdata = OSSL_LIB_CTX_GET_THREADS(ctx); + + if (tdata == NULL) + return retval; + + ossl_crypto_mutex_lock(tdata->lock); + retval = _ossl_get_avail_threads(tdata); + ossl_crypto_mutex_unlock(tdata->lock); + + return retval; +} + +void *ossl_crypto_thread_start(OSSL_LIB_CTX *ctx, CRYPTO_THREAD_ROUTINE start, + void *data) +{ + CRYPTO_THREAD *thread; + OSSL_LIB_CTX_THREADS *tdata = OSSL_LIB_CTX_GET_THREADS(ctx); + + if (tdata == NULL) + return NULL; + + ossl_crypto_mutex_lock(tdata->lock); + if (tdata == NULL || tdata->max_threads == 0) { + ossl_crypto_mutex_unlock(tdata->lock); + return NULL; + } + + while (_ossl_get_avail_threads(tdata) == 0) + ossl_crypto_condvar_wait(tdata->cond_finished, tdata->lock); + tdata->active_threads++; + ossl_crypto_mutex_unlock(tdata->lock); + + thread = ossl_crypto_thread_native_start(start, data, 1); + if (thread == NULL) { + ossl_crypto_mutex_lock(tdata->lock); + tdata->active_threads--; + ossl_crypto_mutex_unlock(tdata->lock); + goto fail; + } + thread->ctx = ctx; + +fail: + return (void *) thread; +} + +int ossl_crypto_thread_join(void *vhandle, CRYPTO_THREAD_RETVAL *retval) +{ + CRYPTO_THREAD *handle = vhandle; + OSSL_LIB_CTX_THREADS *tdata; + + if (vhandle == NULL) + return 0; + + tdata = OSSL_LIB_CTX_GET_THREADS(handle->ctx); + if (tdata == NULL) + return 0; + + if (ossl_crypto_thread_native_join(handle, retval) == 0) + return 0; + + ossl_crypto_mutex_lock(tdata->lock); + tdata->active_threads--; + ossl_crypto_condvar_broadcast(tdata->cond_finished); + ossl_crypto_mutex_unlock(tdata->lock); + return 1; +} + +int ossl_crypto_thread_clean(void *vhandle) +{ + CRYPTO_THREAD *handle = vhandle; + + return ossl_crypto_thread_native_clean(handle); +} + +#else + +ossl_inline uint64_t ossl_get_avail_threads(OSSL_LIB_CTX *ctx) +{ + return 0; +} + +void *ossl_crypto_thread_start(OSSL_LIB_CTX *ctx, CRYPTO_THREAD_ROUTINE start, + void *data) +{ + return NULL; +} + +int ossl_crypto_thread_join(void *vhandle, CRYPTO_THREAD_RETVAL *retval) +{ + return 0; +} + +int ossl_crypto_thread_clean(void *vhandle) +{ + return 0; +} + +#endif + +#if defined(OPENSSL_THREADS) + +void *ossl_threads_ctx_new(OSSL_LIB_CTX *ctx) +{ + struct openssl_threads_st *t = OPENSSL_zalloc(sizeof(*t)); + + if (t == NULL) + return NULL; + + t->lock = ossl_crypto_mutex_new(); + t->cond_finished = ossl_crypto_condvar_new(); + + if (t->lock == NULL || t->cond_finished == NULL) + goto fail; + + return t; + +fail: + ossl_threads_ctx_free((void *)t); + return NULL; +} + +void ossl_threads_ctx_free(void *vdata) +{ + OSSL_LIB_CTX_THREADS *t = (OSSL_LIB_CTX_THREADS *) vdata; + + if (t == NULL) + return; + + ossl_crypto_mutex_free(&t->lock); + ossl_crypto_condvar_free(&t->cond_finished); + OPENSSL_free(t); +} + +#endif diff --git a/doc/man3/CRYPTO_THREAD_run_once.pod b/doc/man3/CRYPTO_THREAD_run_once.pod index a51679b97e..fd2d6a207f 100644 --- a/doc/man3/CRYPTO_THREAD_run_once.pod +++ b/doc/man3/CRYPTO_THREAD_run_once.pod @@ -5,7 +5,9 @@ CRYPTO_THREAD_run_once, CRYPTO_THREAD_lock_new, CRYPTO_THREAD_read_lock, CRYPTO_THREAD_write_lock, CRYPTO_THREAD_unlock, CRYPTO_THREAD_lock_free, -CRYPTO_atomic_add, CRYPTO_atomic_or, CRYPTO_atomic_load - OpenSSL thread support +CRYPTO_atomic_add, CRYPTO_atomic_or, CRYPTO_atomic_load, +OSSL_set_max_threads, OSSL_get_max_threads, +OSSL_get_thread_support_flags - OpenSSL thread support =head1 SYNOPSIS @@ -25,6 +27,10 @@ CRYPTO_atomic_add, CRYPTO_atomic_or, CRYPTO_atomic_load - OpenSSL thread support CRYPTO_RWLOCK *lock); int CRYPTO_atomic_load(uint64_t *val, uint64_t *ret, CRYPTO_RWLOCK *lock); + int OSSL_set_max_threads(OSSL_LIB_CTX *ctx, uint64_t max_threads); + uint64_t OSSL_get_max_threads(OSSL_LIB_CTX *ctx); + uint32_t OSSL_get_thread_support_flags(void); + =head1 DESCRIPTION OpenSSL can be safely used in multi-threaded applications provided that @@ -98,6 +104,16 @@ read by CRYPTO_atomic_load() then CRYPTO_atomic_load() must be the only way that the variable is read. If atomic operations are not supported and I is NULL, then the function will fail. +=item * + +OSSL_set_max_threads() sets the maximum number of threads to be used by the +thread pool. If the argument is 0, thread pooling is disabled. OpenSSL will +not create any threads and existing threads in the thread pool will be torn +down. The maximum thread count is a limit, not a target. Threads will not be +spawned unless (and until) there is demand. Thread polling is disabled by +default. To enable threading you must call OSSL_set_max_threads() explicitly. +Under no circumstances is this done for you. + =back =head1 RETURN VALUES @@ -108,6 +124,15 @@ CRYPTO_THREAD_lock_new() returns the allocated lock, or NULL on error. CRYPTO_THREAD_lock_free() returns no value. +OSSL_set_max_threads() returns 1 on success and 0 on failure. Returns failure +if OpenSSL-managed thread pooling is not supported (for example, if it is not +supported on the current platform, or because OpenSSL is not built with the +necessary support). + +OSSL_get_max_threads() returns the maximum number of threads currently allowed +to be used by the thread pool. If thread pooling is disabled or not available, +returns 0. + The other functions return 1 on success, or 0 on error. =head1 NOTES diff --git a/include/crypto/context.h b/include/crypto/context.h index 143f6d6b6d..950d6f11e4 100644 --- a/include/crypto/context.h +++ b/include/crypto/context.h @@ -23,6 +23,9 @@ void *ossl_self_test_set_callback_new(OSSL_LIB_CTX *); void *ossl_rand_crng_ctx_new(OSSL_LIB_CTX *); void *ossl_thread_event_ctx_new(OSSL_LIB_CTX *); void *ossl_fips_prov_ossl_ctx_new(OSSL_LIB_CTX *); +#if defined(OPENSSL_THREADS) +void *ossl_threads_ctx_new(OSSL_LIB_CTX *); +#endif void ossl_provider_store_free(void *); void ossl_property_string_data_free(void *); @@ -38,3 +41,6 @@ void ossl_self_test_set_callback_free(void *); void ossl_rand_crng_ctx_free(void *); void ossl_thread_event_ctx_free(void *); void ossl_fips_prov_ossl_ctx_free(void *); +#if defined(OPENSSL_THREADS) +void ossl_threads_ctx_free(void *); +#endif diff --git a/include/internal/cryptlib.h b/include/internal/cryptlib.h index 71b6b125f3..700f387531 100644 --- a/include/internal/cryptlib.h +++ b/include/internal/cryptlib.h @@ -116,7 +116,8 @@ typedef struct ossl_ex_data_global_st { # define OSSL_LIB_CTX_PROVIDER_CONF_INDEX 16 # define OSSL_LIB_CTX_BIO_CORE_INDEX 17 # define OSSL_LIB_CTX_CHILD_PROVIDER_INDEX 18 -# define OSSL_LIB_CTX_MAX_INDEXES 19 +# define OSSL_LIB_CTX_THREAD_INDEX 19 +# define OSSL_LIB_CTX_MAX_INDEXES 20 OSSL_LIB_CTX *ossl_lib_ctx_get_concrete(OSSL_LIB_CTX *ctx); int ossl_lib_ctx_is_default(OSSL_LIB_CTX *ctx); diff --git a/include/internal/thread.h b/include/internal/thread.h new file mode 100644 index 0000000000..8c5bad7763 --- /dev/null +++ b/include/internal/thread.h @@ -0,0 +1,39 @@ +/* + * Copyright 2019-2021 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef OPENSSL_INTERNAL_THREAD_H +# define OPENSSL_INTERNAL_THREAD_H +# include +# include +# include +# include +# include +# include "crypto/context.h" + +void *ossl_crypto_thread_start(OSSL_LIB_CTX *ctx, CRYPTO_THREAD_ROUTINE start, + void *data); +int ossl_crypto_thread_join(void *task, CRYPTO_THREAD_RETVAL *retval); +int ossl_crypto_thread_clean(void *vhandle); +uint64_t ossl_get_avail_threads(OSSL_LIB_CTX *ctx); + +# if defined(OPENSSL_THREADS) + +# define OSSL_LIB_CTX_GET_THREADS(CTX) \ + ossl_lib_ctx_get_data(CTX, OSSL_LIB_CTX_THREAD_INDEX); + +typedef struct openssl_threads_st { + uint64_t max_threads; + uint64_t active_threads; + CRYPTO_MUTEX *lock; + CRYPTO_CONDVAR *cond_finished; +} OSSL_LIB_CTX_THREADS; + +# endif /* defined(OPENSSL_THREADS) */ + +#endif /* OPENSSL_INTERNAL_THREAD_H */ diff --git a/include/internal/thread_arch.h b/include/internal/thread_arch.h new file mode 100644 index 0000000000..fcf312ff14 --- /dev/null +++ b/include/internal/thread_arch.h @@ -0,0 +1,119 @@ +/* + * Copyright 2019-2021 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef OSSL_INTERNAL_THREAD_ARCH_H +# define OSSL_INTERNAL_THREAD_ARCH_H +# include +# include + +# if defined(_WIN32) +# include +# endif + +# if defined(OPENSSL_THREADS) && defined(OPENSSL_SYS_UNIX) +# define OPENSSL_THREADS_POSIX +# elif defined(OPENSSL_THREADS) && defined(OPENSSL_SYS_WINDOWS) && \ + defined(_WIN32_WINNT) +# if _WIN32_WINNT >= 0x0600 +# define OPENSSL_THREADS_WINNT +# else +# define OPENSSL_THREADS_NONE +# endif +# else +# define OPENSSL_THREADS_NONE +# endif + +# include + +typedef void CRYPTO_MUTEX; +typedef void CRYPTO_CONDVAR; + +CRYPTO_MUTEX *ossl_crypto_mutex_new(void); +void ossl_crypto_mutex_lock(CRYPTO_MUTEX *mutex); +int ossl_crypto_mutex_try_lock(CRYPTO_MUTEX *mutex); +void ossl_crypto_mutex_unlock(CRYPTO_MUTEX *mutex); +void ossl_crypto_mutex_free(CRYPTO_MUTEX **mutex); + +CRYPTO_CONDVAR *ossl_crypto_condvar_new(void); +void ossl_crypto_condvar_wait(CRYPTO_CONDVAR *cv, CRYPTO_MUTEX *mutex); +void ossl_crypto_condvar_broadcast(CRYPTO_CONDVAR *cv); +void ossl_crypto_condvar_free(CRYPTO_CONDVAR **cv); + +typedef uint32_t CRYPTO_THREAD_RETVAL; +typedef CRYPTO_THREAD_RETVAL (*CRYPTO_THREAD_ROUTINE)(void *); +typedef CRYPTO_THREAD_RETVAL (*CRYPTO_THREAD_ROUTINE_CB)(void *, + void (**)(void *), + void **); + +# define CRYPTO_THREAD_NO_STATE 0UL +# define CRYPTO_THREAD_FINISHED (1UL << 1) +# define CRYPTO_THREAD_JOINED (1UL << 2) +# define CRYPTO_THREAD_TERMINATED (1UL << 3) + +# define CRYPTO_THREAD_GET_STATE(THREAD, FLAG) ((THREAD)->state & (FLAG)) +# define CRYPTO_THREAD_GET_ERROR(THREAD, FLAG) (((THREAD)->state >> 16) & (FLAG)) + +typedef struct crypto_thread_st { + uint32_t state; + void *data; + CRYPTO_THREAD_ROUTINE routine; + CRYPTO_THREAD_RETVAL retval; + void *handle; + CRYPTO_MUTEX *lock; + CRYPTO_MUTEX *statelock; + CRYPTO_CONDVAR *condvar; + unsigned long thread_id; + int joinable; + OSSL_LIB_CTX *ctx; +} CRYPTO_THREAD; + +# if defined(OPENSSL_THREADS) + +# define CRYPTO_THREAD_UNSET_STATE(THREAD, FLAG) \ + do { \ + (THREAD)->state &= ~(FLAG); \ + } while ((void)0, 0) + +# define CRYPTO_THREAD_SET_STATE(THREAD, FLAG) \ + do { \ + (THREAD)->state |= (FLAG); \ + } while ((void)0, 0) + +# define CRYPTO_THREAD_SET_ERROR(THREAD, FLAG) \ + do { \ + (THREAD)->state |= ((FLAG) << 16); \ + } while ((void)0, 0) + +# define CRYPTO_THREAD_UNSET_ERROR(THREAD, FLAG) \ + do { \ + (THREAD)->state &= ~((FLAG) << 16); \ + } while ((void)0, 0) + +# else + +# define CRYPTO_THREAD_UNSET_STATE(THREAD, FLAG) +# define CRYPTO_THREAD_SET_STATE(THREAD, FLAG) +# define CRYPTO_THREAD_SET_ERROR(THREAD, FLAG) +# define CRYPTO_THREAD_UNSET_ERROR(THREAD, FLAG) + +# endif /* defined(OPENSSL_THREADS) */ + +CRYPTO_THREAD * ossl_crypto_thread_native_start(CRYPTO_THREAD_ROUTINE routine, + void *data, int joinable); +int ossl_crypto_thread_native_spawn(CRYPTO_THREAD *thread); +int ossl_crypto_thread_native_join(CRYPTO_THREAD *thread, + CRYPTO_THREAD_RETVAL *retval); +int ossl_crypto_thread_native_terminate(CRYPTO_THREAD *thread); +int ossl_crypto_thread_native_exit(void); +int ossl_crypto_thread_native_is_self(CRYPTO_THREAD *thread); +int ossl_crypto_thread_native_clean(CRYPTO_THREAD *thread); + +void ossl_crypto_mem_barrier(void); + +#endif /* OSSL_INTERNAL_THREAD_ARCH_H */ diff --git a/include/openssl/thread.h b/include/openssl/thread.h new file mode 100644 index 0000000000..68ecf9c4c4 --- /dev/null +++ b/include/openssl/thread.h @@ -0,0 +1,23 @@ +/* + * Copyright 1995-2022 The OpenSSL Project Authors. All Rights Reserved. + * Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef OPENSSL_THREAD_H +# define OPENSSL_THREAD_H + +# define OSSL_THREAD_SUPPORT_FLAG_THREAD_POOL (1U<<0) +# define OSSL_THREAD_SUPPORT_FLAG_DEFAULT_SPAWN (1U<<1) + +# include + +uint32_t OSSL_get_thread_support_flags(void); +int OSSL_set_max_threads(OSSL_LIB_CTX *ctx, uint64_t max_threads); +uint64_t OSSL_get_max_threads(OSSL_LIB_CTX *ctx); + +#endif /* OPENSSL_THREAD_H */ diff --git a/test/build.info b/test/build.info index 6caaa618cf..e2cfddb222 100644 --- a/test/build.info +++ b/test/build.info @@ -340,8 +340,8 @@ IF[{- !$disabled{tests} -}] DEPEND[ct_test]=../libcrypto libtestutil.a SOURCE[threadstest]=threadstest.c - INCLUDE[threadstest]=../include ../apps/include - DEPEND[threadstest]=../libcrypto libtestutil.a + INCLUDE[threadstest]=.. ../include ../apps/include + DEPEND[threadstest]=../libcrypto.a libtestutil.a SOURCE[threadstest_fips]=threadstest_fips.c INCLUDE[threadstest_fips]=../include ../apps/include diff --git a/test/threadstest.c b/test/threadstest.c index 505b2cf07e..af1d78a3b7 100644 --- a/test/threadstest.c +++ b/test/threadstest.c @@ -20,11 +20,15 @@ #endif #include +#include +#include +#include #include #include #include #include #include +#include #include "internal/tsan_assist.h" #include "internal/nelem.h" #include "testutil.h" @@ -741,6 +745,326 @@ err: } #endif +static int test_thread_reported_flags(void) +{ + uint32_t flags = OSSL_get_thread_support_flags(); + +#if !defined(OPENSSL_THREADS) + if (!TEST_int_eq(flags, 0)) + return 0; +#endif + +#if defined(OPENSSL_NO_THREAD_POOL) + if (!TEST_int_eq(flags & OSSL_THREAD_SUPPORT_FLAG_THREAD_POOL, 0)) + return 0; +#else + if (!TEST_int_eq(flags & OSSL_THREAD_SUPPORT_FLAG_THREAD_POOL, + OSSL_THREAD_SUPPORT_FLAG_THREAD_POOL)) + return 0; +#endif + +#if defined(OPENSSL_NO_DEFAULT_THREAD_POOL) + if (!TEST_int_eq(flags & OSSL_THREAD_SUPPORT_FLAG_DEFAULT_SPAWN, 0)) + return 0; +#else + if (!TEST_int_eq(flags & OSSL_THREAD_SUPPORT_FLAG_DEFAULT_SPAWN, + OSSL_THREAD_SUPPORT_FLAG_DEFAULT_SPAWN)) + return 0; +#endif + + return 1; +} + +#if defined(OPENSSL_THREADS) + +# define TEST_THREAD_NATIVE_FN_SET_VALUE 1 +static uint32_t test_thread_native_fn(void *data) +{ + uint32_t *ldata = (uint32_t*) data; + *ldata = *ldata + 1; + return *ldata - 1; +} + +static uint32_t test_thread_noreturn(void *data) +{ + CRYPTO_MUTEX *lock = (uint32_t*) data; + + /* lock is assumed to be locked */ + ossl_crypto_mutex_lock(lock); + + /* unreachable */ + OPENSSL_die("test_thread_noreturn", __FILE__, __LINE__); + return 0; +} + +/* Tests of native threads */ + +static int test_thread_native(void) +{ + int testval = 0; + uint32_t retval; + uint32_t local; + CRYPTO_THREAD *t; + CRYPTO_MUTEX *lock; + + /* thread spawn, join and termination */ + + local = 1; + t = ossl_crypto_thread_native_start(test_thread_native_fn, &local, 1); + if (!TEST_ptr(t)) + return 0; + + /* + * pthread_join results in undefined behaviour if called on a joined + * thread. We do not impose such restrictions, so it's up to us to + * ensure that this does not happen (thread sanitizer will warn us + * if we do). + */ + if (!TEST_int_eq(ossl_crypto_thread_native_join(t, &retval), 1)) + return 0; + if (!TEST_int_eq(ossl_crypto_thread_native_join(t, &retval), 1)) + return 0; + + if (!TEST_int_eq(retval, 1) || !TEST_int_eq(local, 2)) + return 0; + + if (!TEST_int_eq(ossl_crypto_thread_native_terminate(t), 1)) + return 0; + if (!TEST_int_eq(ossl_crypto_thread_native_terminate(t), 1)) + return 0; + + if (!TEST_int_eq(ossl_crypto_thread_native_join(t, &retval), 1)) + return 0; + + if (!TEST_int_eq(ossl_crypto_thread_native_clean(t), 1)) + return 0; + t = NULL; + + if (!TEST_int_eq(ossl_crypto_thread_native_clean(t), 0)) + return 0; + + /* termination of a long running thread */ + + lock = ossl_crypto_mutex_new(); + if (!TEST_ptr(lock)) + return 0; + ossl_crypto_mutex_lock(lock); + + t = ossl_crypto_thread_native_start(test_thread_noreturn, lock, 1); + if (!TEST_ptr(t)) + goto fail; + if (!TEST_int_eq(ossl_crypto_thread_native_terminate(t), 1)) + goto fail; + if (!TEST_int_eq(ossl_crypto_thread_native_clean(t), 1)) + goto fail; + + testval = 1; + +fail: + ossl_crypto_mutex_unlock(lock); + ossl_crypto_mutex_free(&lock); + if (!TEST_ptr_null(lock)) + return 0; + + return testval; +} + +#if !defined(OPENSSL_NO_DEFAULT_THREAD_POOL) +static int test_thread_internal(void) +{ + uint32_t retval[3]; + uint32_t local[3] = { 0 }; + uint32_t threads_supported; + size_t i; + void *t[3]; + OSSL_LIB_CTX *cust_ctx = OSSL_LIB_CTX_new(); + + threads_supported = OSSL_get_thread_support_flags(); + threads_supported &= OSSL_THREAD_SUPPORT_FLAG_DEFAULT_SPAWN; + + if (threads_supported == 0) { + if (!TEST_uint64_t_eq(OSSL_get_max_threads(NULL), 0)) + return 0; + if (!TEST_uint64_t_eq(OSSL_get_max_threads(cust_ctx), 0)) + return 0; + + if (!TEST_int_eq(OSSL_set_max_threads(NULL, 1), 0)) + return 0; + if (!TEST_int_eq(OSSL_set_max_threads(cust_ctx, 1), 0)) + return 0; + + if (!TEST_uint64_t_eq(OSSL_get_max_threads(NULL), 0)) + return 0; + if (!TEST_uint64_t_eq(OSSL_get_max_threads(cust_ctx), 0)) + return 0; + + t[0] = ossl_crypto_thread_start(NULL, test_thread_native_fn, &local[0]); + if (!TEST_ptr_null(t[0])) + return 0; + + return 1; + } + + /* fail when not allowed to use threads */ + + if (!TEST_uint64_t_eq(OSSL_get_max_threads(NULL), 0)) + return 0; + t[0] = ossl_crypto_thread_start(NULL, test_thread_native_fn, &local[0]); + if (!TEST_ptr_null(t[0])) + return 0; + + /* fail when enabled on a different context */ + if (!TEST_uint64_t_eq(OSSL_get_max_threads(cust_ctx), 0)) + return 0; + if (!TEST_int_eq(OSSL_set_max_threads(cust_ctx, 1), 1)) + return 0; + if (!TEST_uint64_t_eq(OSSL_get_max_threads(NULL), 0)) + return 0; + if (!TEST_uint64_t_eq(OSSL_get_max_threads(cust_ctx), 1)) + return 0; + t[0] = ossl_crypto_thread_start(NULL, test_thread_native_fn, &local[0]); + if (!TEST_ptr_null(t[0])) + return 0; + if (!TEST_int_eq(OSSL_set_max_threads(cust_ctx, 0), 1)) + return 0; + + /* sequential startup */ + + if (!TEST_int_eq(OSSL_set_max_threads(NULL, 1), 1)) + return 0; + if (!TEST_uint64_t_eq(OSSL_get_max_threads(NULL), 1)) + return 0; + if (!TEST_uint64_t_eq(OSSL_get_max_threads(cust_ctx), 0)) + return 0; + + for (i = 0; i < OSSL_NELEM(t); ++i) { + local[0] = i + 1; + + t[i] = ossl_crypto_thread_start(NULL, test_thread_native_fn, &local[0]); + if (!TEST_ptr(t[i])) + return 0; + + /* + * pthread_join results in undefined behaviour if called on a joined + * thread. We do not impose such restrictions, so it's up to us to + * ensure that this does not happen (thread sanitizer will warn us + * if we do). + */ + if (!TEST_int_eq(ossl_crypto_thread_join(t[i], &retval[0]), 1)) + return 0; + if (!TEST_int_eq(ossl_crypto_thread_join(t[i], &retval[0]), 1)) + return 0; + + if (!TEST_int_eq(retval[0], i + 1) || !TEST_int_eq(local[0], i + 2)) + return 0; + + if (!TEST_int_eq(ossl_crypto_thread_clean(t[i]), 1)) + return 0; + t[i] = NULL; + + if (!TEST_int_eq(ossl_crypto_thread_clean(t[i]), 0)) + return 0; + } + + /* parallel startup */ + + if (!TEST_int_eq(OSSL_set_max_threads(NULL, OSSL_NELEM(t)), 1)) + return 0; + + for (i = 0; i < OSSL_NELEM(t); ++i) { + local[i] = i + 1; + t[i] = ossl_crypto_thread_start(NULL, test_thread_native_fn, &local[i]); + if (!TEST_ptr(t[i])) + return 0; + } + for (i = 0; i < OSSL_NELEM(t); ++i) { + if (!TEST_int_eq(ossl_crypto_thread_join(t[i], &retval[i]), 1)) + return 0; + } + for (i = 0; i < OSSL_NELEM(t); ++i) { + if (!TEST_int_eq(retval[i], i + 1) || !TEST_int_eq(local[i], i + 2)) + return 0; + if (!TEST_int_eq(ossl_crypto_thread_clean(t[i]), 1)) + return 0; + } + + /* parallel startup, bottleneck */ + + if (!TEST_int_eq(OSSL_set_max_threads(NULL, OSSL_NELEM(t) - 1), 1)) + return 0; + + for (i = 0; i < OSSL_NELEM(t); ++i) { + local[i] = i + 1; + t[i] = ossl_crypto_thread_start(NULL, test_thread_native_fn, &local[i]); + if (!TEST_ptr(t[i])) + return 0; + } + for (i = 0; i < OSSL_NELEM(t); ++i) { + if (!TEST_int_eq(ossl_crypto_thread_join(t[i], &retval[i]), 1)) + return 0; + } + for (i = 0; i < OSSL_NELEM(t); ++i) { + if (!TEST_int_eq(retval[i], i + 1) || !TEST_int_eq(local[i], i + 2)) + return 0; + if (!TEST_int_eq(ossl_crypto_thread_clean(t[i]), 1)) + return 0; + } + + if (!TEST_int_eq(OSSL_set_max_threads(NULL, 0), 1)) + return 0; + + OSSL_LIB_CTX_free(cust_ctx); + return 1; +} +#endif + +static uint32_t test_thread_native_multiple_joins_fn1(void *data) +{ + return 0; +} + +static uint32_t test_thread_native_multiple_joins_fn2(void *data) +{ + ossl_crypto_thread_native_join((CRYPTO_THREAD *)data, NULL); + return 0; +} + +static uint32_t test_thread_native_multiple_joins_fn3(void *data) +{ + ossl_crypto_thread_native_join((CRYPTO_THREAD *)data, NULL); + return 0; +} + +static int test_thread_native_multiple_joins(void) +{ + CRYPTO_THREAD *t, *t1, *t2; + + t = ossl_crypto_thread_native_start(test_thread_native_multiple_joins_fn1, NULL, 1); + t1 = ossl_crypto_thread_native_start(test_thread_native_multiple_joins_fn2, t, 1); + t2 = ossl_crypto_thread_native_start(test_thread_native_multiple_joins_fn3, t, 1); + + if (!TEST_ptr(t) || !TEST_ptr(t1) || !TEST_ptr(t2)) + return 0; + + if (!TEST_int_eq(ossl_crypto_thread_native_join(t2, NULL), 1)) + return 0; + if (!TEST_int_eq(ossl_crypto_thread_native_join(t1, NULL), 1)) + return 0; + + if (!TEST_int_eq(ossl_crypto_thread_native_clean(t2), 1)) + return 0; + + if (!TEST_int_eq(ossl_crypto_thread_native_clean(t1), 1)) + return 0; + + if (!TEST_int_eq(ossl_crypto_thread_native_clean(t), 1)) + return 0; + + return 1; +} + +#endif + typedef enum OPTION_choice { OPT_ERR = -1, OPT_EOF = 0, @@ -816,6 +1140,15 @@ int setup_tests(void) #if !defined(OPENSSL_NO_DGRAM) && !defined(OPENSSL_NO_SOCK) ADD_TEST(test_bio_dgram_pair); #endif + ADD_TEST(test_thread_reported_flags); +#if defined(OPENSSL_THREADS) + ADD_TEST(test_thread_native); + ADD_TEST(test_thread_native_multiple_joins); +#if !defined(OPENSSL_NO_DEFAULT_THREAD_POOL) + ADD_TEST(test_thread_internal); +#endif +#endif + return 1; } diff --git a/util/libcrypto.num b/util/libcrypto.num index ba44622210..f5951d59e5 100644 --- a/util/libcrypto.num +++ b/util/libcrypto.num @@ -5466,3 +5466,6 @@ EVP_PKEY_auth_decapsulate_init ? 3_2_0 EXIST::FUNCTION: PKCS12_SAFEBAG_set0_attrs ? 3_2_0 EXIST::FUNCTION: PKCS12_create_ex2 ? 3_2_0 EXIST::FUNCTION: OSSL_sleep ? 3_2_0 EXIST::FUNCTION: +OSSL_get_thread_support_flags ? 3_2_0 EXIST::FUNCTION: +OSSL_set_max_threads ? 3_2_0 EXIST::FUNCTION: +OSSL_get_max_threads ? 3_2_0 EXIST::FUNCTION: