diff --git a/docs/src/threading.rst b/docs/src/threading.rst index 89bb4a6f3ae..a5759a38a67 100644 --- a/docs/src/threading.rst +++ b/docs/src/threading.rst @@ -55,10 +55,39 @@ API Threads ^^^^^^^ +.. c:type:: uv_thread_options_t + + Options for spawning a new thread (passed to :c:func:`uv_thread_create_ex`). + + :: + + typedef struct uv_process_options_s { + enum { + UV_THREAD_NO_FLAGS = 0x00, + UV_THREAD_HAS_STACK_SIZE = 0x01 + } flags; + size_t stack_size; + } uv_process_options_t; + + More fields may be added to this struct at any time, so its exact + layout and size should not be relied upon. + + .. versionadded:: 1.26.0 + .. c:function:: int uv_thread_create(uv_thread_t* tid, uv_thread_cb entry, void* arg) .. versionchanged:: 1.4.1 returns a UV_E* error code on failure +.. c:function:: int uv_thread_create_ex(uv_thread_t* tid, const uv_thread_options_t* params, uv_thread_cb entry, void* arg) + + Like :c:func:`uv_thread_create`, but additionally specifies options for creating a new thread. + + If `UV_THREAD_HAS_STACK_SIZE` is set, `stack_size` specifies a stack size for the new thread. + `0` indicates that the default value should be used, i.e. behaves as if the flag was not set. + Other values will be rounded up to the nearest page boundary. + + .. versionadded:: 1.26.0 + .. c:function:: uv_thread_t uv_thread_self(void) .. c:function:: int uv_thread_join(uv_thread_t *tid) .. c:function:: int uv_thread_equal(const uv_thread_t* t1, const uv_thread_t* t2) diff --git a/include/uv.h b/include/uv.h index d8f0b7b6bd0..1578bbd5682 100644 --- a/include/uv.h +++ b/include/uv.h @@ -1585,6 +1585,24 @@ UV_EXTERN void uv_key_set(uv_key_t* key, void* value); typedef void (*uv_thread_cb)(void* arg); UV_EXTERN int uv_thread_create(uv_thread_t* tid, uv_thread_cb entry, void* arg); + +typedef enum { + UV_THREAD_NO_FLAGS = 0x00, + UV_THREAD_HAS_STACK_SIZE = 0x01 +} uv_thread_create_flags; + +struct uv_thread_options_s { + unsigned int flags; + size_t stack_size; + /* More fields may be added at any time. */ +}; + +typedef struct uv_thread_options_s uv_thread_options_t; + +UV_EXTERN int uv_thread_create_ex(uv_thread_t* tid, + const uv_thread_options_t* params, + uv_thread_cb entry, + void* arg); UV_EXTERN uv_thread_t uv_thread_self(void); UV_EXTERN int uv_thread_join(uv_thread_t *tid); UV_EXTERN int uv_thread_equal(const uv_thread_t* t1, const uv_thread_t* t2); diff --git a/src/unix/thread.c b/src/unix/thread.c index 8bcb857610f..c17a51fed23 100644 --- a/src/unix/thread.c +++ b/src/unix/thread.c @@ -194,13 +194,34 @@ static size_t thread_stack_size(void) { int uv_thread_create(uv_thread_t *tid, void (*entry)(void *arg), void *arg) { + uv_thread_options_t params; + params.flags = UV_THREAD_NO_FLAGS; + return uv_thread_create_ex(tid, ¶ms, entry, arg); +} + +int uv_thread_create_ex(uv_thread_t* tid, + const uv_thread_options_t* params, + void (*entry)(void *arg), + void *arg) { int err; - size_t stack_size; pthread_attr_t* attr; pthread_attr_t attr_storage; + size_t pagesize; + size_t stack_size; + + stack_size = + params->flags & UV_THREAD_HAS_STACK_SIZE ? params->stack_size : 0; attr = NULL; - stack_size = thread_stack_size(); + if (stack_size == 0) { + stack_size = thread_stack_size(); + } else { + pagesize = (size_t)getpagesize(); + /* Round up to the nearest page boundary. */ + stack_size = (stack_size + pagesize - 1) &~ (pagesize - 1); + if (stack_size < PTHREAD_STACK_MIN) + stack_size = PTHREAD_STACK_MIN; + } if (stack_size > 0) { attr = &attr_storage; diff --git a/src/win/thread.c b/src/win/thread.c index fd4b7c98688..89c53ada755 100644 --- a/src/win/thread.c +++ b/src/win/thread.c @@ -112,9 +112,34 @@ static UINT __stdcall uv__thread_start(void* arg) { int uv_thread_create(uv_thread_t *tid, void (*entry)(void *arg), void *arg) { + uv_thread_options_t params; + params.flags = UV_THREAD_NO_FLAGS; + return uv_thread_create_ex(tid, ¶ms, entry, arg); +} + +int uv_thread_create_ex(uv_thread_t* tid, + const uv_thread_options_t* params, + void (*entry)(void *arg), + void *arg) { struct thread_ctx* ctx; int err; HANDLE thread; + SYSTEM_INFO sysinfo; + size_t stack_size; + size_t pagesize; + + stack_size = + params->flags & UV_THREAD_HAS_STACK_SIZE ? params->stack_size : 0; + + if (stack_size != 0) { + GetNativeSystemInfo(&sysinfo); + pagesize = (size_t)sysinfo.dwPageSize; + /* Round up to the nearest page boundary. */ + stack_size = (stack_size + pagesize - 1) &~ (pagesize - 1); + + if ((unsigned)stack_size != stack_size) + return UV_EINVAL; + } ctx = uv__malloc(sizeof(*ctx)); if (ctx == NULL) @@ -126,7 +151,7 @@ int uv_thread_create(uv_thread_t *tid, void (*entry)(void *arg), void *arg) { /* Create the thread in suspended state so we have a chance to pass * its own creation handle to it */ thread = (HANDLE) _beginthreadex(NULL, - 0, + (unsigned)stack_size, uv__thread_start, ctx, CREATE_SUSPENDED, diff --git a/test/test-list.h b/test/test-list.h index 53372c90f76..fe6b45ce50f 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -365,6 +365,7 @@ TEST_DECLARE (threadpool_cancel_fs) TEST_DECLARE (threadpool_cancel_single) TEST_DECLARE (thread_local_storage) TEST_DECLARE (thread_stack_size) +TEST_DECLARE (thread_stack_size_explicit) TEST_DECLARE (thread_mutex) TEST_DECLARE (thread_mutex_recursive) TEST_DECLARE (thread_rwlock) @@ -930,6 +931,7 @@ TASK_LIST_START TEST_ENTRY (threadpool_cancel_single) TEST_ENTRY (thread_local_storage) TEST_ENTRY (thread_stack_size) + TEST_ENTRY (thread_stack_size_explicit) TEST_ENTRY (thread_mutex) TEST_ENTRY (thread_mutex_recursive) TEST_ENTRY (thread_rwlock) diff --git a/test/test-thread.c b/test/test-thread.c index 955c9f2f1be..be72d5e8b39 100644 --- a/test/test-thread.c +++ b/test/test-thread.c @@ -26,6 +26,10 @@ #include #include /* memset */ +#ifdef __POSIX__ +#include +#endif + struct getaddrinfo_req { uv_thread_t thread_id; unsigned int counter; @@ -207,10 +211,15 @@ TEST_IMPL(thread_local_storage) { static void thread_check_stack(void* arg) { #if defined(__APPLE__) + size_t expected; + expected = arg == NULL ? 0 : ((uv_thread_options_t*)arg)->stack_size; /* 512 kB is the default stack size of threads other than the main thread * on MacOS. */ - ASSERT(pthread_get_stacksize_np(pthread_self()) > 512*1024); + if (expected == 0) + expected = 512 * 1024; + ASSERT(pthread_get_stacksize_np(pthread_self()) >= expected); #elif defined(__linux__) && defined(__GLIBC__) + size_t expected; struct rlimit lim; size_t stack_size; pthread_attr_t attr; @@ -219,7 +228,10 @@ static void thread_check_stack(void* arg) { lim.rlim_cur = 2 << 20; /* glibc default. */ ASSERT(0 == pthread_getattr_np(pthread_self(), &attr)); ASSERT(0 == pthread_attr_getstacksize(&attr, &stack_size)); - ASSERT(stack_size >= lim.rlim_cur); + expected = arg == NULL ? 0 : ((uv_thread_options_t*)arg)->stack_size; + if (expected == 0) + expected = (size_t)lim.rlim_cur; + ASSERT(stack_size >= expected); #endif } @@ -230,3 +242,44 @@ TEST_IMPL(thread_stack_size) { ASSERT(0 == uv_thread_join(&thread)); return 0; } + +TEST_IMPL(thread_stack_size_explicit) { + uv_thread_t thread; + uv_thread_options_t options; + + options.flags = UV_THREAD_HAS_STACK_SIZE; + options.stack_size = 1024 * 1024; + ASSERT(0 == uv_thread_create_ex(&thread, &options, + thread_check_stack, &options)); + ASSERT(0 == uv_thread_join(&thread)); + + options.stack_size = 8 * 1024 * 1024; /* larger than most default os sizes */ + ASSERT(0 == uv_thread_create_ex(&thread, &options, + thread_check_stack, &options)); + ASSERT(0 == uv_thread_join(&thread)); + + options.stack_size = 0; + ASSERT(0 == uv_thread_create_ex(&thread, &options, + thread_check_stack, &options)); + ASSERT(0 == uv_thread_join(&thread)); + +#ifdef PTHREAD_STACK_MIN + options.stack_size = PTHREAD_STACK_MIN - 42; /* unaligned size */ + ASSERT(0 == uv_thread_create_ex(&thread, &options, + thread_check_stack, &options)); + ASSERT(0 == uv_thread_join(&thread)); + + options.stack_size = PTHREAD_STACK_MIN / 2 - 42; /* unaligned size */ + ASSERT(0 == uv_thread_create_ex(&thread, &options, + thread_check_stack, &options)); + ASSERT(0 == uv_thread_join(&thread)); +#endif + + /* unaligned size, should be larger than PTHREAD_STACK_MIN */ + options.stack_size = 1234567; + ASSERT(0 == uv_thread_create_ex(&thread, &options, + thread_check_stack, &options)); + ASSERT(0 == uv_thread_join(&thread)); + + return 0; +}