diff --git a/Makefile.am b/Makefile.am index b104edec..128ac566 100644 --- a/Makefile.am +++ b/Makefile.am @@ -222,6 +222,7 @@ test_run_tests_SOURCES = test/blackhole-server.c \ test/test-poll-closesocket.c \ test/test-poll-oob.c \ test/test-process-title.c \ + test/test-process-title-threadsafe.c \ test/test-queue-foreach-delete.c \ test/test-ref.c \ test/test-run-nowait.c \ diff --git a/checksparse.sh b/checksparse.sh index 02a5d7f2..27eb529b 100755 --- a/checksparse.sh +++ b/checksparse.sh @@ -128,6 +128,7 @@ test/test-platform-output.c test/test-poll-close.c test/test-poll.c test/test-process-title.c +test/test-process-title-threadsafe.c test/test-ref.c test/test-run-nowait.c test/test-run-once.c diff --git a/docs/src/misc.rst b/docs/src/misc.rst index a653413e..07908c98 100644 --- a/docs/src/misc.rst +++ b/docs/src/misc.rst @@ -197,8 +197,7 @@ API `UV_EINVAL` is returned. If `size` cannot accommodate the process title and terminating `NULL` character, the function returns `UV_ENOBUFS`. - .. warning:: - `uv_get_process_title` is not thread safe on any platform except Windows. + .. versionchanged:: 1.18.1 now thread-safe on all supported platforms. .. c:function:: int uv_set_process_title(const char* title) @@ -208,8 +207,7 @@ API larger than the available space. Other platforms will return `UV_ENOMEM` if they cannot allocate enough space to duplicate the contents of `title`. - .. warning:: - `uv_set_process_title` is not thread safe on any platform except Windows. + .. versionchanged:: 1.18.1 now thread-safe on all supported platforms. .. c:function:: int uv_resident_set_memory(size_t* rss) diff --git a/src/unix/aix.c b/src/unix/aix.c index 06f19a4f..fd413090 100644 --- a/src/unix/aix.c +++ b/src/unix/aix.c @@ -65,11 +65,18 @@ #define RDWR_BUF_SIZE 4096 #define EQ(a,b) (strcmp(a,b) == 0) +static uv_mutex_t process_title_mutex; +static uv_once_t process_title_mutex_once = UV_ONCE_INIT; static void* args_mem = NULL; static char** process_argv = NULL; static int process_argc = 0; static char* process_title_ptr = NULL; +static void init_process_title_mutex_once(void) { + uv_mutex_init(&process_title_mutex); +} + + int uv__platform_loop_init(uv_loop_t* loop) { loop->fs_fd = -1; @@ -856,6 +863,9 @@ int uv_set_process_title(const char* title) { if (new_title == NULL) return -ENOMEM; + uv_once(&process_title_mutex_once, init_process_title_mutex_once); + uv_mutex_lock(&process_title_mutex); + /* If this is the first time this is set, * don't free and set argv[1] to NULL. */ @@ -868,6 +878,8 @@ int uv_set_process_title(const char* title) { if (process_argc > 1) process_argv[1] = NULL; + uv_mutex_unlock(&process_title_mutex); + return 0; } @@ -880,8 +892,13 @@ int uv_get_process_title(char* buffer, size_t size) { else if (size <= len) return -ENOBUFS; + uv_once(&process_title_mutex_once, init_process_title_mutex_once); + uv_mutex_lock(&process_title_mutex); + memcpy(buffer, process_argv[0], len + 1); + uv_mutex_unlock(&process_title_mutex); + return 0; } diff --git a/src/unix/freebsd.c b/src/unix/freebsd.c index dba94298..39db148a 100644 --- a/src/unix/freebsd.c +++ b/src/unix/freebsd.c @@ -47,9 +47,16 @@ # define CP_INTR 4 #endif +static uv_mutex_t process_title_mutex; +static uv_once_t process_title_mutex_once = UV_ONCE_INIT; static char *process_title; +static void init_process_title_mutex_once(void) { + uv_mutex_init(&process_title_mutex); +} + + int uv__platform_loop_init(uv_loop_t* loop) { return uv__kqueue_init(loop); } @@ -163,8 +170,15 @@ int uv_set_process_title(const char* title) { char* new_title; new_title = uv__strdup(title); - if (process_title == NULL) + + uv_once(&process_title_mutex_once, init_process_title_mutex_once); + uv_mutex_lock(&process_title_mutex); + + if (process_title == NULL) { + uv_mutex_unlock(&process_title_mutex); return -ENOMEM; + } + uv__free(process_title); process_title = new_title; @@ -180,6 +194,8 @@ int uv_set_process_title(const char* title) { process_title, strlen(process_title) + 1); + uv_mutex_unlock(&process_title_mutex); + return 0; } @@ -190,17 +206,24 @@ int uv_get_process_title(char* buffer, size_t size) { if (buffer == NULL || size == 0) return -EINVAL; + uv_once(&process_title_mutex_once, init_process_title_mutex_once); + uv_mutex_lock(&process_title_mutex); + if (process_title) { len = strlen(process_title) + 1; - if (size < len) + if (size < len) { + uv_mutex_unlock(&process_title_mutex); return -ENOBUFS; + } memcpy(buffer, process_title, len); } else { len = 0; } + uv_mutex_unlock(&process_title_mutex); + buffer[len] = '\0'; return 0; diff --git a/src/unix/netbsd.c b/src/unix/netbsd.c index d9066349..74250723 100644 --- a/src/unix/netbsd.c +++ b/src/unix/netbsd.c @@ -40,9 +40,16 @@ #include #include +static uv_mutex_t process_title_mutex; +static uv_once_t process_title_mutex_once = UV_ONCE_INIT; static char *process_title; +static void init_process_title_mutex_once(void) { + uv_mutex_init(&process_title_mutex); +} + + int uv__platform_loop_init(uv_loop_t* loop) { return uv__kqueue_init(loop); } @@ -137,12 +144,21 @@ int uv_set_process_title(const char* title) { char* new_title; new_title = uv__strdup(title); - if (process_title == NULL) + + uv_once(&process_title_mutex_once, init_process_title_mutex_once); + uv_mutex_lock(&process_title_mutex); + + if (process_title == NULL) { + uv_mutex_unlock(&process_title_mutex); return -ENOMEM; + } + uv__free(process_title); process_title = new_title; setproctitle("%s", title); + uv_mutex_unlock(&process_title_mutex); + return 0; } @@ -153,17 +169,24 @@ int uv_get_process_title(char* buffer, size_t size) { if (buffer == NULL || size == 0) return -EINVAL; + uv_once(&process_title_mutex_once, init_process_title_mutex_once); + uv_mutex_lock(&process_title_mutex); + if (process_title) { len = strlen(process_title) + 1; - if (size < len) + if (size < len) { + uv_mutex_unlock(&process_title_mutex); return -ENOBUFS; + } memcpy(buffer, process_title, len); } else { len = 0; } + uv_mutex_unlock(&process_title_mutex); + buffer[len] = '\0'; return 0; diff --git a/src/unix/openbsd.c b/src/unix/openbsd.c index d1c90289..c0ffa564 100644 --- a/src/unix/openbsd.c +++ b/src/unix/openbsd.c @@ -36,9 +36,16 @@ #include +static uv_mutex_t process_title_mutex; +static uv_once_t process_title_mutex_once = UV_ONCE_INIT; static char *process_title; +static void init_process_title_mutex_once(void) { + uv_mutex_init(&process_title_mutex); +} + + int uv__platform_loop_init(uv_loop_t* loop) { return uv__kqueue_init(loop); } @@ -149,11 +156,21 @@ int uv_set_process_title(const char* title) { char* new_title; new_title = uv__strdup(title); - if (process_title == NULL) + + uv_once(&process_title_mutex_once, init_process_title_mutex_once); + uv_mutex_lock(&process_title_mutex); + + if (process_title == NULL) { + uv_mutex_unlock(&process_title_mutex); return -ENOMEM; + } + uv__free(process_title); process_title = new_title; setproctitle("%s", title); + + uv_mutex_unlock(&process_title_mutex); + return 0; } @@ -164,17 +181,24 @@ int uv_get_process_title(char* buffer, size_t size) { if (buffer == NULL || size == 0) return -EINVAL; + uv_once(&process_title_mutex_once, init_process_title_mutex_once); + uv_mutex_lock(&process_title_mutex); + if (process_title) { len = strlen(process_title) + 1; - if (size < len) + if (size < len) { + uv_mutex_unlock(&process_title_mutex); return -ENOBUFS; + } memcpy(buffer, process_title, len); } else { len = 0; } + uv_mutex_unlock(&process_title_mutex); + buffer[len] = '\0'; return 0; diff --git a/src/unix/proctitle.c b/src/unix/proctitle.c index 2ed0b21c..1b3a7988 100644 --- a/src/unix/proctitle.c +++ b/src/unix/proctitle.c @@ -26,6 +26,8 @@ extern void uv__set_process_title(const char* title); +static uv_mutex_t process_title_mutex; +static uv_once_t process_title_mutex_once = UV_ONCE_INIT; static void* args_mem; static struct { @@ -34,6 +36,11 @@ static struct { } process_title; +static void init_process_title_mutex_once(void) { + uv_mutex_init(&process_title_mutex); +} + + char** uv_setup_args(int argc, char** argv) { char** new_argv; size_t size; @@ -81,12 +88,16 @@ char** uv_setup_args(int argc, char** argv) { int uv_set_process_title(const char* title) { - if (process_title.len == 0) - return 0; + uv_once(&process_title_mutex_once, init_process_title_mutex_once); + uv_mutex_lock(&process_title_mutex); - /* No need to terminate, byte after is always '\0'. */ - strncpy(process_title.str, title, process_title.len); - uv__set_process_title(title); + if (process_title.len != 0) { + /* No need to terminate, byte after is always '\0'. */ + strncpy(process_title.str, title, process_title.len); + uv__set_process_title(title); + } + + uv_mutex_unlock(&process_title_mutex); return 0; } @@ -95,14 +106,22 @@ int uv_set_process_title(const char* title) { int uv_get_process_title(char* buffer, size_t size) { if (buffer == NULL || size == 0) return -EINVAL; - else if (size <= process_title.len) + + uv_once(&process_title_mutex_once, init_process_title_mutex_once); + uv_mutex_lock(&process_title_mutex); + + if (size <= process_title.len) { + uv_mutex_unlock(&process_title_mutex); return -ENOBUFS; + } if (process_title.len != 0) memcpy(buffer, process_title.str, process_title.len + 1); buffer[process_title.len] = '\0'; + uv_mutex_unlock(&process_title_mutex); + return 0; } diff --git a/test/test-list.h b/test/test-list.h index 662fe78b..08a59991 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -214,6 +214,7 @@ TEST_DECLARE (async_null_cb) TEST_DECLARE (eintr_handling) TEST_DECLARE (get_currentexe) TEST_DECLARE (process_title) +TEST_DECLARE (process_title_threadsafe) TEST_DECLARE (cwd_and_chdir) TEST_DECLARE (get_memory) TEST_DECLARE (get_passwd) @@ -672,6 +673,7 @@ TASK_LIST_START TEST_ENTRY (get_currentexe) TEST_ENTRY (process_title) + TEST_ENTRY (process_title_threadsafe) TEST_ENTRY (cwd_and_chdir) diff --git a/test/test-process-title-threadsafe.c b/test/test-process-title-threadsafe.c new file mode 100644 index 00000000..d986576e --- /dev/null +++ b/test/test-process-title-threadsafe.c @@ -0,0 +1,90 @@ +/* Copyright libuv project contributors. All rights reserved. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to +* deal in the Software without restriction, including without limitation the +* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +* sell copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +* IN THE SOFTWARE. +*/ + + +#include "uv.h" +#include "task.h" + +#include + +#ifdef __APPLE__ +# define NUM_ITERATIONS 20 +#else +# define NUM_ITERATIONS 50 +#endif + +static const char* titles[] = { + "8L2NY0Kdj0XyNFZnmUZigIOfcWjyNr0SkMmUhKw99VLUsZFrvCQQC3XIRfNR8pjyMjXObllled", + "jUAcscJN49oLSN8GdmXj2Wo34XX2T2vp2j5khfajNQarlOulp57cE130yiY53ipJFnPyTn5i82", + "9niCI5icXGFS72XudhXqo5alftmZ1tpE7B3cwUmrq0CCDjC84FzBNB8XAHqvpNQfI2QAQG6ztT", + "n8qXVXuG6IEHDpabJgTEiwtpY6LHMZ8MgznnMpdHARu5EywufA6hcBaQfetb0YhEsK0ykDd7JU" +}; + +static void getter_thread_body(void* arg) { + char buffer[512]; + + for (;;) { + ASSERT(0 == uv_get_process_title(buffer, sizeof(buffer))); + ASSERT( + 0 == strcmp(buffer, titles[0]) || + 0 == strcmp(buffer, titles[1]) || + 0 == strcmp(buffer, titles[2]) || + 0 == strcmp(buffer, titles[3])); + + uv_sleep(0); + } +} + + +static void setter_thread_body(void* arg) { + int i; + + for (i = 0; i < NUM_ITERATIONS; i++) { + ASSERT(0 == uv_set_process_title(titles[0])); + ASSERT(0 == uv_set_process_title(titles[1])); + ASSERT(0 == uv_set_process_title(titles[2])); + ASSERT(0 == uv_set_process_title(titles[3])); + } +} + + +TEST_IMPL(process_title_threadsafe) { + uv_thread_t setter_threads[4]; + uv_thread_t getter_thread; + int i; + +#if defined(__sun) || defined(__CYGWIN__) || defined(__MSYS__) || \ + defined(__MVS__) + RETURN_SKIP("uv_(get|set)_process_title is not implemented."); +#else + + ASSERT(0 == uv_set_process_title(titles[0])); + ASSERT(0 == uv_thread_create(&getter_thread, getter_thread_body, NULL)); + + for (i = 0; i < (int) ARRAY_SIZE(setter_threads); i++) + ASSERT(0 == uv_thread_create(&setter_threads[i], setter_thread_body, NULL)); + + for (i = 0; i < (int) ARRAY_SIZE(setter_threads); i++) + ASSERT(0 == uv_thread_join(&setter_threads[i])); + + return 0; +#endif +} diff --git a/uv.gyp b/uv.gyp index 10f32279..dade2125 100644 --- a/uv.gyp +++ b/uv.gyp @@ -427,6 +427,7 @@ 'test/test-poll-closesocket.c', 'test/test-poll-oob.c', 'test/test-process-title.c', + 'test/test-process-title-threadsafe.c', 'test/test-queue-foreach-delete.c', 'test/test-ref.c', 'test/test-run-nowait.c',