Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

misc: introduce uv_set_threadpool_size #4415

Open
wants to merge 1 commit into
base: v1.x
Choose a base branch
from

Conversation

juanarbol
Copy link
Contributor

This call will set the threadpool size from libuv and set UV_THREADPOOL_SIZE environment variable.

Fixes: #4401


This is my approach for #4401; and of course I'm open to changes and suggestions.
I decided to mutate UV_THREADPOOL_SIZE considering that maybe some Node.js users or anyone relies on that variable for certain situations (like monitoring or something). And this is not a thread safe call as uv_os_setenv is not. Even tho, I don't is possible make this thread safe on any way.

@juanarbol juanarbol force-pushed the juan/threadpool branch 2 times, most recently from af05da2 to e13cf0c Compare May 21, 2024 22:51
@cjihrig
Copy link
Contributor

cjihrig commented May 21, 2024

uv_set_threadpool_size() seems like a more intuitive name IMO.

@juanarbol
Copy link
Contributor Author

uv_set_threadpool_size() seems like a more intuitive name IMO.

I think you are right. Will rename and fix the windows under ASAN. It seems to be related with #4338

@juanarbol juanarbol force-pushed the juan/threadpool branch 2 times, most recently from 0217b12 to 2b0aa45 Compare May 21, 2024 23:14
@juanarbol juanarbol changed the title misc: introduce uv_threadpool_set_number misc: introduce uv_set_threadpool_size May 21, 2024
This function must be called before any other libuv function is called.

On success, it returns 0, if the threadpool is already spining it returns
`UV_EINVAL`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe UV_EALREADY or UV_EBUSY are better error codes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True that

src/threadpool.c Outdated
char num_str[12]; /* enough room for unsigned int and null terminator */

/* check if the threadpool is already initialized */
if (nthreads != 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder: would it be possible to enlarge the amount of threads while the pool is running?

Reducing it is probably hard / impossible, but adding new threads might be within the realm of possibility.

Copy link
Contributor Author

@juanarbol juanarbol May 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that is out of the scope of the issue itself. I could work on that as well. It could be a mix between @santigimeno suggestion of a getter for this and this PR rn.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a branch from 2015 that does just that: https://github.com/bnoordhuis/libuv/commits/auto-threadpool

Shrinking is done lazily. Threads terminate when they're idle and the actual thread count > desired thread count.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bnoordhuis Do you think I should pivot this approach into something like yours instead?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say that's a good idea. #382 is the PR from back then, you will want to read through the discussion.

cc @saghul as a fyi. You had some comments back then; maybe you still do?

@@ -886,3 +886,15 @@ is not complete.
for the NUL terminator.

.. versionadded:: 1.47.0

.. c:function:: int uv_set_threadpool_size(unsingned int number)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use size_t ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevermind, the code seems to like unsigned int instead.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your spin_threads function takes size_t

include/uv.h Outdated
Comment on lines 1316 to 1333
UV_EXTERN int uv_set_threadpool_size(unsigned int number);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding a uv_get_threadpool_size() would be a good idea, I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That could be useful as well

src/threadpool.c Outdated Show resolved Hide resolved
@juanarbol
Copy link
Contributor Author

Thanks for the review, everyone, after reading all the comments and suggestions.

  1. I will extend the functionalities over the setter, if UV_THREADPOOL_SIZE is not provided, will spin as many threads as requested in this call. If env is provided; that will be rule and there is nothing users can do to spin more threads or a different number of threads.
  2. If called again with more threads, spins k more threads
  3. Introduce the getter.

@juanarbol
Copy link
Contributor Author

juanarbol commented May 23, 2024

The new revision includes the getter and the behaviour is a bit different now as this patch surpasses the functionalities of the original issue. And most importantly, it respects UV_THREADPOOL_SIZE as the first source of truth and won't mutate it.

What a painful and joyful adventure 🫠

@juanarbol
Copy link
Contributor Author

I'll see what is wrong with Windows later, maybe tomorrow, but feel free to review anyway. </3

@juanarbol
Copy link
Contributor Author

r = uv_os_setenv("UV_THREADPOOL_SIZE", "1");
ASSERT_OK(r);
#ifdef _WIN32
  /* juanarbol: why? */
  ASSERT_EQ(nthreads, 4);
#else
  /* The default thread pool size is UV_THREADPOOL_SIZE */
  ASSERT_EQ(nthreads, 1);
#endif

This is odd. If anyone can confirm if windows simply does not work or have any idea about this, would be really appreciated.

src/threadpool.c Outdated Show resolved Hide resolved
src/threadpool.c Outdated
@@ -206,7 +225,7 @@ static void init_threads(void) {
nthreads = MAX_THREADPOOL_SIZE;

threads = default_threads;
if (nthreads > ARRAY_SIZE(default_threads)) {
if (nthreads != ARRAY_SIZE(default_threads)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why allocate memory if we need fewer threads than the default?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. Right, that was from a different experiment

src/threadpool.c Outdated Show resolved Hide resolved
src/threadpool.c Outdated Show resolved Hide resolved
src/threadpool.c Show resolved Hide resolved
@@ -886,3 +886,15 @@ is not complete.
for the NUL terminator.

.. versionadded:: 1.47.0

.. c:function:: int uv_set_threadpool_size(unsingned int number)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your spin_threads function takes size_t

include/uv.h Outdated Show resolved Hide resolved
@joy4eg
Copy link

joy4eg commented May 23, 2024

r = uv_os_setenv("UV_THREADPOOL_SIZE", "1");
ASSERT_OK(r);
#ifdef _WIN32
  /* juanarbol: why? */
  ASSERT_EQ(nthreads, 4);
#else
  /* The default thread pool size is UV_THREADPOOL_SIZE */
  ASSERT_EQ(nthreads, 1);
#endif

This is odd. If anyone can confirm if windows simply does not work or have any idea about this, would be really appreciated.

setenv() just sets the env, nothing more. 4 is the default thread pool size, and that's why you got it.

Copy link
Contributor Author

@juanarbol juanarbol left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I will address all the comments and wait for your opinions if we need all those locks

@juanarbol
Copy link
Contributor Author

```c
r = uv_os_setenv("UV_THREADPOOL_SIZE", "1");
ASSERT_OK(r);
#ifdef _WIN32
  /* juanarbol: why? */
  ASSERT_EQ(nthreads, 4);
#else
  /* The default thread pool size is UV_THREADPOOL_SIZE */
  ASSERT_EQ(nthreads, 1);
#endif

This is odd. If anyone can confirm if windows simply does not work or have any idea about this, would be really appreciated.

setenv() just sets the env, nothing more. 4 is the default thread pool size, and that's why you got it.

I was expecting that env var to affect the threadpool size, like it does in XNIX


uv_sem_destroy(&sem);
/* Spin up the default number of threads */
spin_threads(0 /* from */, nthreads /* to */);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This call is fine because it's inside init_threads, which is guarateed to only run once. Any other place where this is called needs to hold onto the mutex.

src/threadpool.c Outdated Show resolved Hide resolved
src/threadpool.c Outdated Show resolved Hide resolved
src/threadpool.c Show resolved Hide resolved
src/threadpool.c Outdated Show resolved Hide resolved
RETURN_SKIP("Test does not currently work on Windows under ASAN");
#endif

r = uv_os_setenv("UV_THREADPOOL_SIZE", "1");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how reliable this is, since any chance in the test runner what uses any fs API will trigger a threadpool initialization.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is not each test -at least in this case- like a "isolated" event loop?

@juanarbol
Copy link
Contributor Author

Ok, after addressing all the comments here, I think this is thread safe now :)

Thanks for reviewing, explanations and your time. Of course I'm open for more changes if needed.

@juanarbol
Copy link
Contributor Author

Humble ping @saghul, the CI failure is unrelated to this. And about the environment test case, I think it is worth having it, that describes the whole feature behaviour. But I can remove it if needed.

This patch will allow to grow and set the thread pool size on
demand via libuv calls instead of just `UV_THREADPOOL_SIZE` but
`UV_THREADPOOL_SIZE` will remain as the initial value.

Fixes: libuv#4401
Signed-off-by: Juan José Arboleda <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Provide an OS independent way to set/get UV_THREADPOOL_SIZE
6 participants