aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--Makefile1
-rw-r--r--libar2simplified.71
-rw-r--r--libar2simplified.h16
-rw-r--r--libar2simplified_hash.c361
-rw-r--r--libar2simplified_init_context.354
-rw-r--r--libar2simplified_init_context.c368
6 files changed, 441 insertions, 360 deletions
diff --git a/Makefile b/Makefile
index ab4c9ad..6827d97 100644
--- a/Makefile
+++ b/Makefile
@@ -22,6 +22,7 @@ OBJ =\
libar2simplified_encode.o\
libar2simplified_encode_hash.o\
libar2simplified_hash.o\
+ libar2simplified_init_context.o\
libar2simplified_recommendation.o
HDR =\
diff --git a/libar2simplified.7 b/libar2simplified.7
index ef7584c..fa9219d 100644
--- a/libar2simplified.7
+++ b/libar2simplified.7
@@ -46,4 +46,5 @@ the parameters.
.BR libar2simplified_encode (3),
.BR libar2simplified_encode_hash (3),
.BR libar2simplified_hash (3),
+.BR libar2simplified_init_context (3),
.BR libar2simplified_recommendation (3)
diff --git a/libar2simplified.h b/libar2simplified.h
index e1233e2..0d005fb 100644
--- a/libar2simplified.h
+++ b/libar2simplified.h
@@ -162,4 +162,20 @@ int libar2simplified_hash(void *hash, void *msg, size_t msglen, struct libar2_ar
LIBAR2_PUBLIC__ LIBAR2_NONNULL__(1, 2)
char *libar2simplified_crypt(char *msg, const char *params, char *rv);
+/* Lower-level functions: */
+
+/**
+ * Initialises the context argument for `libar2_hash`,
+ * with all auto-erase options turned off
+ *
+ * This function provides a dynamic memory management
+ * functions that erase memory before it is deallocated.
+ * It also also provides a multi-threading support using
+ * a thread pool.
+ *
+ * @param ctxp Output parameter
+ */
+LIBAR2_PUBLIC__
+void libar2simplified_init_context(struct libar2_context *ctxp);
+
#endif
diff --git a/libar2simplified_hash.c b/libar2simplified_hash.c
index e5765f5..20fb2c0 100644
--- a/libar2simplified_hash.c
+++ b/libar2simplified_hash.c
@@ -1,357 +1,5 @@
/* See LICENSE file for copyright and license details. */
#include "common.h"
-#include <pthread.h>
-#include <semaphore.h>
-
-
-struct user_data;
-
-struct thread_data {
- size_t index;
- struct user_data *master;
- pthread_t thread;
- sem_t semaphore;
- int error;
- void (*function)(void *data);
- void *function_input;
-};
-
-struct user_data {
- struct thread_data *threads;
- size_t nthreads;
- pthread_mutex_t mutex;
- sem_t semaphore;
- uint_least64_t *joined;
- uint_least64_t resting[];
-};
-
-
-static void *
-alignedalloc(size_t num, size_t size, size_t extra, size_t alignment)
-{
- void *ptr;
- int err;
- if (num > (SIZE_MAX - extra) / size) {
- errno = ENOMEM;
- return NULL;
- }
- if (alignment < sizeof(void *))
- alignment = sizeof(void *);
- err = posix_memalign(&ptr, alignment, num * size + extra);
- if (err) {
- errno = err;
- return NULL;
- } else {
- return ptr;
- }
-}
-
-
-static void *
-allocate(size_t num, size_t size, size_t alignment, struct libar2_context *ctx)
-{
- size_t pad = (alignment - ((2 * sizeof(size_t)) & (alignment - 1))) & (alignment - 1);
- char *ptr = alignedalloc(num, size, pad + 2 * sizeof(size_t), alignment);
- if (ptr) {
- ptr = &ptr[pad];
- *(size_t *)ptr = pad;
- ptr = &ptr[sizeof(size_t)];
- *(size_t *)ptr = num * size;
- ptr = &ptr[sizeof(size_t)];
- }
- (void) ctx;
- return ptr;
-}
-
-
-static void
-deallocate(void *ptr, struct libar2_context *ctx)
-{
- char *p = ptr;
- p -= sizeof(size_t);
- libar2_erase(ptr, *(size_t *)p);
- p -= sizeof(size_t);
- p -= *(size_t *)p;
- free(p);
- (void) ctx;
-}
-
-
-static void *
-thread_loop(void *data_)
-{
- struct thread_data *data = data_;
- int err;
-
- for (;;) {
- if (sem_wait(&data->semaphore)) {
- if (errno == EINTR)
- continue;
- data->error = errno;
- return NULL;
- }
-
- if (!data->function) {
- data->error = ENOTRECOVERABLE;
- return NULL;
- }
- data->function(data->function_input);
-
- err = pthread_mutex_lock(&data->master->mutex);
- if (err) {
- data->error = err;
- return NULL;
- }
- data->master->resting[data->index / 64] |= (uint_least64_t)1 << (data->index % 64);
- pthread_mutex_unlock(&data->master->mutex);
- if (sem_post(&data->master->semaphore)) {
- data->error = errno;
- return NULL;
- }
- }
-}
-
-
-static int
-run_thread(size_t index, void (*function)(void *arg), void *arg, struct libar2_context *ctx)
-{
- struct user_data *data = ctx->user_data;
- int err;
-
- err = pthread_mutex_lock(&data->mutex);
- if (err) {
- errno = err;
- return -1;
- }
- data->resting[index / 64] ^= (uint_least64_t)1 << (index % 64);
- pthread_mutex_unlock(&data->mutex);
-
- if (data->threads[index].error) {
- errno = data->threads[index].error;
- return -1;
- }
-
- data->threads[index].function = function;
- data->threads[index].function_input = arg;
- if (sem_post(&data->threads[index].semaphore))
- return -1;
-
- return 0;
-}
-
-
-static int
-destroy_thread_pool(struct libar2_context *ctx)
-{
- struct user_data *data = ctx->user_data;
- size_t i;
- int ret = 0;
- for (i = data->nthreads; i--;)
- if (run_thread(i, pthread_exit, NULL, ctx))
- return -1;
- for (i = data->nthreads; i--;) {
- pthread_join(data->threads[i].thread, NULL);
- sem_destroy(&data->threads[i].semaphore);
- if (data->threads[i].error)
- ret = data->threads[i].error;
- }
- free(data->threads);
- sem_destroy(&data->semaphore);
- pthread_mutex_destroy(&data->mutex);
- free(data);
- return ret;
-}
-
-
-static int
-init_thread_pool(size_t desired, size_t *createdp, struct libar2_context *ctx)
-{
- struct user_data *data;
- int err;
- size_t i, size;
- long int nproc, nproc_limit;
-#ifdef __linux__
- char path[sizeof("/sys/devices/system/cpu/cpu") + 3 * sizeof(nproc)];
-#endif
-#ifdef _SC_SEM_VALUE_MAX
- long int semlimit;
-#endif
-
- if (desired < 2) {
- *createdp = 0;
- return 0;
- }
-
- nproc = sysconf(_SC_NPROCESSORS_ONLN);
-#ifdef __linux__
- if (nproc < 1) {
- nproc_limit = desired > LONG_MAX ? LONG_MAX : (long int)desired;
- for (nproc = 0; nproc < nproc_limit; nproc++) {
- sprintf(path, "%s%li", "/sys/devices/system/cpu/cpu", nproc);
- if (access(path, F_OK))
- break;
- }
- }
-#endif
- if (nproc < 1)
- nproc = FALLBACK_NPROC;
-
-#ifdef _SC_SEM_VALUE_MAX
- semlimit = sysconf(_SC_SEM_VALUE_MAX);
- if (semlimit >= 1 && semlimit < nproc)
- nproc = semlimit;
-#endif
-
- if (nproc == 1) {
- *createdp = 0;
- return 0;
- }
-
- desired = (size_t)nproc < desired ? (size_t)nproc : desired;
-
- if (desired > SIZE_MAX - 63 || (desired + 63) / 64 > SIZE_MAX / sizeof(uint_least64_t) / 2) {
- errno = ENOMEM;
- return -1;
- }
- size = (desired + 63) / 64;
- size *= sizeof(uint_least64_t) * 2;
- data = alignedalloc(1, offsetof(struct user_data, resting), size, ALIGNOF(struct user_data));
- memset(data, 0, offsetof(struct user_data, resting) + size);
- data->joined = &data->resting[(desired + 63) / 64];
- ctx->user_data = data;
-
- *createdp = data->nthreads = desired;
-
- data->threads = alignedalloc(data->nthreads, sizeof(*data->threads), 0, ALIGNOF(struct thread_data));
- if (!data->threads)
- return -1;
-
- err = pthread_mutex_init(&data->mutex, NULL);
- if (err) {
- free(data->threads);
- return -1;
- }
- err = sem_init(&data->semaphore, 0, 0);
- if (err) {
- pthread_mutex_destroy(&data->mutex);
- free(data->threads);
- return -1;
- }
-
- for (i = 0; i < data->nthreads; i++) {
- memset(&data->threads[i], 0, sizeof(data->threads[i]));
- data->threads[i].master = data;
- data->threads[i].index = i;
- data->resting[i / 64] |= (uint_least64_t)1 << (i % 64);
- if (sem_init(&data->threads[i].semaphore, 0, 0)) {
- err = errno;
- goto fail_post_sem;
- }
- err = pthread_create(&data->threads[i].thread, NULL, thread_loop, &data->threads[i]);
- if (err) {
- sem_destroy(&data->threads[i].semaphore);
- fail_post_sem:
- data->nthreads = i;
- destroy_thread_pool(ctx);
- errno = err;
- return -1;
- }
- }
-
- return 0;
-}
-
-
-#if defined(__GNUC__)
-__attribute__((__const__))
-#endif
-static size_t
-lb(uint_least64_t x)
-{
- size_t r = 0;
- while (x > 1) {
- x >>= 1;
- r += 1;
- }
- return r;
-}
-
-static size_t
-await_threads(size_t *indices, size_t n, size_t require, struct libar2_context *ctx)
-{
- struct user_data *data = ctx->user_data;
- size_t ret = 0, i;
- uint_least64_t one;
- int err;
-
- memset(data->joined, 0, (data->nthreads + 63) / 64 * sizeof(*data->joined));
-
- for (i = 0; i < data->nthreads; i += 64) {
- for (;;) {
- one = data->resting[i / 64];
- one ^= data->joined[i / 64];
- if (!one)
- break;
- one &= ~(one - 1);
- data->joined[i / 64] |= one;
- if (ret++ < n)
- indices[ret - 1] = i + lb(one);
- }
- }
-
- for (;;) {
- if (ret < require) {
- if (sem_wait(&data->semaphore)) {
- if (errno == EINTR)
- continue;
- return 0;
- }
- } else if (sem_trywait(&data->semaphore)) {
- if (errno == EAGAIN)
- break;
- else
- return 0;
- }
-
- err = pthread_mutex_lock(&data->mutex);
- if (err) {
- errno = err;
- return 0;
- }
- for (i = 0; i < data->nthreads; i += 64) {
- one = data->resting[i / 64];
- one ^= data->joined[i / 64];
- if (!one)
- continue;
- one &= ~(one - 1);
- data->joined[i / 64] |= one;
- if (ret++ < n)
- indices[ret - 1] = i + lb(one);
- break;
- }
- pthread_mutex_unlock(&data->mutex);
- }
-
- return ret;
-}
-
-
-static size_t
-get_ready_threads(size_t *indices, size_t n, struct libar2_context *ctx)
-{
- return await_threads(indices, n, 1, ctx);
-}
-
-
-static int
-join_thread_pool(struct libar2_context *ctx)
-{
- struct user_data *data = ctx->user_data;
- if (await_threads(NULL, 0, data->nthreads, ctx))
- return 0;
- destroy_thread_pool(ctx);
- return -1;
-}
int
@@ -360,15 +8,8 @@ libar2simplified_hash(void *hash, void *msg, size_t msglen, struct libar2_argon2
struct libar2_context ctx;
int ret;
- memset(&ctx, 0, sizeof(ctx));
+ libar2simplified_init_context(&ctx);
ctx.autoerase_message = 1;
- ctx.allocate = allocate;
- ctx.deallocate = deallocate;
- ctx.init_thread_pool = init_thread_pool;
- ctx.get_ready_threads = get_ready_threads;
- ctx.run_thread = run_thread;
- ctx.join_thread_pool = join_thread_pool;
- ctx.destroy_thread_pool = destroy_thread_pool;
ret = libar2_hash(hash, msg, msglen, params, &ctx);
if (ret)
diff --git a/libar2simplified_init_context.3 b/libar2simplified_init_context.3
new file mode 100644
index 0000000..c6abae7
--- /dev/null
+++ b/libar2simplified_init_context.3
@@ -0,0 +1,54 @@
+.TH LIBAR2SIMPLIFIED_INIT_CONTEXT 3 LIBAR2SIMPLIFIED
+.SH NAME
+libar2simplified_init_context - Create context for libar2_hash
+
+.SH SYNOPSIS
+.nf
+#include <libar2simplified.h>
+
+void libar2simplified_init_context(struct libar2_context *ctxp);
+.fi
+.PP
+Link with
+.IR "-lar2simplified -lrt -pthread" .
+
+.SH DESCRIPTION
+The
+.BR libar2simplified_init_context ()
+function initialises the context argument
+for the
+.BR libar2_hash (3)
+function, provided via the
+.I ctxp
+parameter, with all auto-erase options
+turned off.
+.PP
+This function provides a dynamic memory
+management functions that erase memory
+before it is deallocated. It also also
+provides a multi-threading support using
+a thread pool.
+.PP
+This function is used internally by the
+.BR libar2simplified (7)
+library, but cannot be used with any
+function provided by the library, it can
+however be used together with the
+.BR libar2 (7)
+library, specifically it is designed for the
+.BR libar2_hash (3)
+function, but can also be used with the
+.BR libar2_decode_params (3)
+function.
+
+.SH RETURN VALUES
+None.
+
+.SH ERRORS
+The
+.BR libar2simplified_recommendation ()
+function cannot fail.
+
+.SH SEE ALSO
+.BR libar2simplified (7),
+.BR libar2_hash (3)
diff --git a/libar2simplified_init_context.c b/libar2simplified_init_context.c
new file mode 100644
index 0000000..c7f8b69
--- /dev/null
+++ b/libar2simplified_init_context.c
@@ -0,0 +1,368 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#include <pthread.h>
+#include <semaphore.h>
+
+
+struct user_data;
+
+struct thread_data {
+ size_t index;
+ struct user_data *master;
+ pthread_t thread;
+ sem_t semaphore;
+ int error;
+ void (*function)(void *data);
+ void *function_input;
+};
+
+struct user_data {
+ struct thread_data *threads;
+ size_t nthreads;
+ pthread_mutex_t mutex;
+ sem_t semaphore;
+ uint_least64_t *joined;
+ uint_least64_t resting[];
+};
+
+
+static void *
+alignedalloc(size_t num, size_t size, size_t extra, size_t alignment)
+{
+ void *ptr;
+ int err;
+ if (num > (SIZE_MAX - extra) / size) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ if (alignment < sizeof(void *))
+ alignment = sizeof(void *);
+ err = posix_memalign(&ptr, alignment, num * size + extra);
+ if (err) {
+ errno = err;
+ return NULL;
+ } else {
+ return ptr;
+ }
+}
+
+
+static void *
+allocate(size_t num, size_t size, size_t alignment, struct libar2_context *ctx)
+{
+ size_t pad = (alignment - ((2 * sizeof(size_t)) & (alignment - 1))) & (alignment - 1);
+ char *ptr = alignedalloc(num, size, pad + 2 * sizeof(size_t), alignment);
+ if (ptr) {
+ ptr = &ptr[pad];
+ *(size_t *)ptr = pad;
+ ptr = &ptr[sizeof(size_t)];
+ *(size_t *)ptr = num * size;
+ ptr = &ptr[sizeof(size_t)];
+ }
+ (void) ctx;
+ return ptr;
+}
+
+
+static void
+deallocate(void *ptr, struct libar2_context *ctx)
+{
+ char *p = ptr;
+ p -= sizeof(size_t);
+ libar2_erase(ptr, *(size_t *)p);
+ p -= sizeof(size_t);
+ p -= *(size_t *)p;
+ free(p);
+ (void) ctx;
+}
+
+
+static void *
+thread_loop(void *data_)
+{
+ struct thread_data *data = data_;
+ int err;
+
+ for (;;) {
+ if (sem_wait(&data->semaphore)) {
+ if (errno == EINTR)
+ continue;
+ data->error = errno;
+ return NULL;
+ }
+
+ if (!data->function) {
+ data->error = ENOTRECOVERABLE;
+ return NULL;
+ }
+ data->function(data->function_input);
+
+ err = pthread_mutex_lock(&data->master->mutex);
+ if (err) {
+ data->error = err;
+ return NULL;
+ }
+ data->master->resting[data->index / 64] |= (uint_least64_t)1 << (data->index % 64);
+ pthread_mutex_unlock(&data->master->mutex);
+ if (sem_post(&data->master->semaphore)) {
+ data->error = errno;
+ return NULL;
+ }
+ }
+}
+
+
+static int
+run_thread(size_t index, void (*function)(void *arg), void *arg, struct libar2_context *ctx)
+{
+ struct user_data *data = ctx->user_data;
+ int err;
+
+ err = pthread_mutex_lock(&data->mutex);
+ if (err) {
+ errno = err;
+ return -1;
+ }
+ data->resting[index / 64] ^= (uint_least64_t)1 << (index % 64);
+ pthread_mutex_unlock(&data->mutex);
+
+ if (data->threads[index].error) {
+ errno = data->threads[index].error;
+ return -1;
+ }
+
+ data->threads[index].function = function;
+ data->threads[index].function_input = arg;
+ if (sem_post(&data->threads[index].semaphore))
+ return -1;
+
+ return 0;
+}
+
+
+static int
+destroy_thread_pool(struct libar2_context *ctx)
+{
+ struct user_data *data = ctx->user_data;
+ size_t i;
+ int ret = 0;
+ for (i = data->nthreads; i--;)
+ if (run_thread(i, pthread_exit, NULL, ctx))
+ return -1;
+ for (i = data->nthreads; i--;) {
+ pthread_join(data->threads[i].thread, NULL);
+ sem_destroy(&data->threads[i].semaphore);
+ if (data->threads[i].error)
+ ret = data->threads[i].error;
+ }
+ free(data->threads);
+ sem_destroy(&data->semaphore);
+ pthread_mutex_destroy(&data->mutex);
+ free(data);
+ return ret;
+}
+
+
+static int
+init_thread_pool(size_t desired, size_t *createdp, struct libar2_context *ctx)
+{
+ struct user_data *data;
+ int err;
+ size_t i, size;
+ long int nproc, nproc_limit;
+#ifdef __linux__
+ char path[sizeof("/sys/devices/system/cpu/cpu") + 3 * sizeof(nproc)];
+#endif
+#ifdef _SC_SEM_VALUE_MAX
+ long int semlimit;
+#endif
+
+ if (desired < 2) {
+ *createdp = 0;
+ return 0;
+ }
+
+ nproc = sysconf(_SC_NPROCESSORS_ONLN);
+#ifdef __linux__
+ if (nproc < 1) {
+ nproc_limit = desired > LONG_MAX ? LONG_MAX : (long int)desired;
+ for (nproc = 0; nproc < nproc_limit; nproc++) {
+ sprintf(path, "%s%li", "/sys/devices/system/cpu/cpu", nproc);
+ if (access(path, F_OK))
+ break;
+ }
+ }
+#endif
+ if (nproc < 1)
+ nproc = FALLBACK_NPROC;
+
+#ifdef _SC_SEM_VALUE_MAX
+ semlimit = sysconf(_SC_SEM_VALUE_MAX);
+ if (semlimit >= 1 && semlimit < nproc)
+ nproc = semlimit;
+#endif
+
+ if (nproc == 1) {
+ *createdp = 0;
+ return 0;
+ }
+
+ desired = (size_t)nproc < desired ? (size_t)nproc : desired;
+
+ if (desired > SIZE_MAX - 63 || (desired + 63) / 64 > SIZE_MAX / sizeof(uint_least64_t) / 2) {
+ errno = ENOMEM;
+ return -1;
+ }
+ size = (desired + 63) / 64;
+ size *= sizeof(uint_least64_t) * 2;
+ data = alignedalloc(1, offsetof(struct user_data, resting), size, ALIGNOF(struct user_data));
+ memset(data, 0, offsetof(struct user_data, resting) + size);
+ data->joined = &data->resting[(desired + 63) / 64];
+ ctx->user_data = data;
+
+ *createdp = data->nthreads = desired;
+
+ data->threads = alignedalloc(data->nthreads, sizeof(*data->threads), 0, ALIGNOF(struct thread_data));
+ if (!data->threads)
+ return -1;
+
+ err = pthread_mutex_init(&data->mutex, NULL);
+ if (err) {
+ free(data->threads);
+ return -1;
+ }
+ err = sem_init(&data->semaphore, 0, 0);
+ if (err) {
+ pthread_mutex_destroy(&data->mutex);
+ free(data->threads);
+ return -1;
+ }
+
+ for (i = 0; i < data->nthreads; i++) {
+ memset(&data->threads[i], 0, sizeof(data->threads[i]));
+ data->threads[i].master = data;
+ data->threads[i].index = i;
+ data->resting[i / 64] |= (uint_least64_t)1 << (i % 64);
+ if (sem_init(&data->threads[i].semaphore, 0, 0)) {
+ err = errno;
+ goto fail_post_sem;
+ }
+ err = pthread_create(&data->threads[i].thread, NULL, thread_loop, &data->threads[i]);
+ if (err) {
+ sem_destroy(&data->threads[i].semaphore);
+ fail_post_sem:
+ data->nthreads = i;
+ destroy_thread_pool(ctx);
+ errno = err;
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+
+#if defined(__GNUC__)
+__attribute__((__const__))
+#endif
+static size_t
+lb(uint_least64_t x)
+{
+ size_t r = 0;
+ while (x > 1) {
+ x >>= 1;
+ r += 1;
+ }
+ return r;
+}
+
+static size_t
+await_threads(size_t *indices, size_t n, size_t require, struct libar2_context *ctx)
+{
+ struct user_data *data = ctx->user_data;
+ size_t ret = 0, i;
+ uint_least64_t one;
+ int err;
+
+ memset(data->joined, 0, (data->nthreads + 63) / 64 * sizeof(*data->joined));
+
+ for (i = 0; i < data->nthreads; i += 64) {
+ for (;;) {
+ one = data->resting[i / 64];
+ one ^= data->joined[i / 64];
+ if (!one)
+ break;
+ one &= ~(one - 1);
+ data->joined[i / 64] |= one;
+ if (ret++ < n)
+ indices[ret - 1] = i + lb(one);
+ }
+ }
+
+ for (;;) {
+ if (ret < require) {
+ if (sem_wait(&data->semaphore)) {
+ if (errno == EINTR)
+ continue;
+ return 0;
+ }
+ } else if (sem_trywait(&data->semaphore)) {
+ if (errno == EAGAIN)
+ break;
+ else
+ return 0;
+ }
+
+ err = pthread_mutex_lock(&data->mutex);
+ if (err) {
+ errno = err;
+ return 0;
+ }
+ for (i = 0; i < data->nthreads; i += 64) {
+ one = data->resting[i / 64];
+ one ^= data->joined[i / 64];
+ if (!one)
+ continue;
+ one &= ~(one - 1);
+ data->joined[i / 64] |= one;
+ if (ret++ < n)
+ indices[ret - 1] = i + lb(one);
+ break;
+ }
+ pthread_mutex_unlock(&data->mutex);
+ }
+
+ return ret;
+}
+
+
+static size_t
+get_ready_threads(size_t *indices, size_t n, struct libar2_context *ctx)
+{
+ return await_threads(indices, n, 1, ctx);
+}
+
+
+static int
+join_thread_pool(struct libar2_context *ctx)
+{
+ struct user_data *data = ctx->user_data;
+ if (await_threads(NULL, 0, data->nthreads, ctx))
+ return 0;
+ destroy_thread_pool(ctx);
+ return -1;
+}
+
+
+void
+libar2simplified_init_context(struct libar2_context *ctxp)
+{
+ memset(ctxp, 0, sizeof(*ctxp));
+ ctxp->allocate = allocate;
+ ctxp->deallocate = deallocate;
+ ctxp->init_thread_pool = init_thread_pool;
+ ctxp->get_ready_threads = get_ready_threads;
+ ctxp->run_thread = run_thread;
+ ctxp->join_thread_pool = join_thread_pool;
+ ctxp->destroy_thread_pool = destroy_thread_pool;
+}