Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion doc/modules/ROOT/pages/execution/thread-pool.adoc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
// Copyright (c) 2026 Michael Vandeberg
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
Expand Down Expand Up @@ -42,9 +43,12 @@ thread_pool pool2(4);

// Single thread (useful for testing)
thread_pool pool3(1);

// Custom thread name prefix
thread_pool pool4(4, "myapp-io-"); // Threads named myapp-io-0, myapp-io-1, etc.
----

The thread count cannot be changed after construction.
The thread count and thread name prefix cannot be changed after construction.

== Getting an Executor

Expand Down Expand Up @@ -246,6 +250,38 @@ thread_pool compute_pool;
thread_pool io_pool(16);
----

== Thread Naming

Worker threads are automatically named for debugging purposes:

* Threads are named `capy-pool-0`, `capy-pool-1`, etc. by default
* The prefix is configurable via the constructor's second parameter
* Names appear in debuggers (GDB, LLDB, Visual Studio)
* Names appear in system tools (`htop`, Process Explorer, `ps -L`)

[source,cpp]
----
// Default naming: capy-pool-0, capy-pool-1, ...
thread_pool pool1(4);

// Custom naming: worker-0, worker-1, ...
thread_pool pool2(4, "worker-");
----

This aids in identifying thread pool workers when debugging multi-threaded
applications with multiple pools.

=== Name Length Limits

The thread name prefix is truncated to 12 characters, leaving room for up
to 3-digit thread indices (0-999).

For example, with prefix `"my-worker-"` and 100 threads, names would be
`my-worker-0` through `my-worker-99`.

NOTE: Thread naming is a best-effort feature. On unsupported platforms,
names may not appear.

== Common Patterns

=== Single-Threaded Testing
Expand Down
49 changes: 49 additions & 0 deletions include/boost/capy/detail/thread_name.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// Copyright (c) 2026 Michael Vandeberg
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/cppalliance/capy
//

#ifndef BOOST_CAPY_DETAIL_THREAD_NAME_HPP
#define BOOST_CAPY_DETAIL_THREAD_NAME_HPP

#include <boost/capy/detail/config.hpp>

/*
Thread naming abstraction for debugging purposes.

Sets the current thread's name which appears in debuggers
(GDB, LLDB, Visual Studio) and system tools (htop, Process Explorer).

Platform support:
- Windows: SetThreadDescription (Windows 10 1607+)
- macOS: pthread_setname_np (truncated to 63 chars)
- Linux/FreeBSD/NetBSD: pthread_setname_np (truncated to 15 chars)
- Other platforms: no-op
*/

namespace boost {
namespace capy {
namespace detail {

/** Set the name of the current thread for debugging purposes.

The name may be truncated to platform limits:
- Linux/FreeBSD/NetBSD: 15 characters
- macOS: 63 characters
- Windows: no practical limit

@param name The thread name to set (UTF-8 encoded on Windows).
*/
BOOST_CAPY_DECL
void
set_current_thread_name(char const* name) noexcept;

} // detail
} // capy
} // boost

#endif
11 changes: 10 additions & 1 deletion include/boost/capy/ex/thread_pool.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
// Copyright (c) 2026 Michael Vandeberg
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
Expand All @@ -14,6 +15,7 @@
#include <boost/capy/coro.hpp>
#include <boost/capy/ex/execution_context.hpp>
#include <cstddef>
#include <string_view>

namespace boost {
namespace capy {
Expand Down Expand Up @@ -62,9 +64,16 @@ class BOOST_CAPY_DECL

@param num_threads The number of worker threads, or zero
for automatic selection.

@param thread_name_prefix The prefix for worker thread names.
Thread names appear as "{prefix}0", "{prefix}1", etc.
The prefix is truncated to 12 characters. Defaults to
"capy-pool-".
*/
explicit
thread_pool(std::size_t num_threads = 0);
thread_pool(
std::size_t num_threads = 0,
std::string_view thread_name_prefix = "capy-pool-");

thread_pool(thread_pool const&) = delete;
thread_pool& operator=(thread_pool const&) = delete;
Expand Down
102 changes: 102 additions & 0 deletions src/detail/thread_name.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
//
// Copyright (c) 2026 Michael Vandeberg
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/cppalliance/capy
//

#include <boost/capy/detail/thread_name.hpp>

#if defined(_WIN32)

#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#include <string>

#elif defined(__APPLE__)

#include <pthread.h>
#include <cstring>

#elif defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__)

#include <pthread.h>
#include <cstring>

#endif

/*
Platform-specific thread naming implementation.

Each platform has a different API and name length limit:
- Windows: SetThreadDescription with UTF-8 to UTF-16 conversion (no limit)
- macOS: pthread_setname_np(name) with 63-char limit
- Linux/BSD: pthread_setname_np(thread, name) with 15-char limit

All operations are best-effort and silently fail on error, since thread
naming is purely for debugging visibility and should never affect program
correctness. The noexcept guarantee is maintained by catching exceptions
from std::wstring allocation on Windows.
*/

namespace boost {
namespace capy {
namespace detail {

void
set_current_thread_name(char const* name) noexcept
{
#if defined(_WIN32)
// SetThreadDescription requires Windows 10 1607+. Older Windows versions
// are unsupported; the program may fail to link on those systems.

// Query required buffer size for UTF-8 to wide conversion.
int required = MultiByteToWideChar(CP_UTF8, 0, name, -1, nullptr, 0);
if(required <= 0)
return;

// Allocate and convert; catch exceptions to maintain noexcept.
std::wstring wname;
try
{
wname.resize(static_cast<std::size_t>(required));
}
catch(...)
{
return;
}

if(MultiByteToWideChar(CP_UTF8, 0, name, -1, wname.data(), required) <= 0)
return;

// Ignore return value: thread naming is best-effort for debugging.
(void)SetThreadDescription(GetCurrentThread(), wname.c_str());
#elif defined(__APPLE__)
// macOS pthread_setname_np takes only the name (no thread handle)
// and has a 64 char limit (63 + null terminator)
char truncated[64];
std::strncpy(truncated, name, 63);
truncated[63] = '\0';

// Ignore return value: thread naming is best-effort for debugging.
(void)pthread_setname_np(truncated);
#elif defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__)
// pthread_setname_np has 16 char limit (15 + null terminator)
char truncated[16];
std::strncpy(truncated, name, 15);
truncated[15] = '\0';

// Ignore return value: thread naming is best-effort for debugging.
(void)pthread_setname_np(pthread_self(), truncated);
#else
(void)name;
#endif
}

} // detail
} // capy
} // boost
43 changes: 37 additions & 6 deletions src/ex/thread_pool.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
// Copyright (c) 2026 Michael Vandeberg
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
Expand All @@ -9,12 +10,32 @@

#include <boost/capy/ex/thread_pool.hpp>
#include <boost/capy/detail/intrusive.hpp>
#include <boost/capy/detail/thread_name.hpp>
#include <condition_variable>
#include <cstdio>
#include <mutex>
#include <stop_token>
#include <thread>
#include <vector>

/*
Thread pool implementation using a shared work queue.

Work items are coroutine handles wrapped in intrusive list nodes, stored
in a single queue protected by a mutex. Worker threads wait on a
condition_variable_any that integrates with std::stop_token for clean
shutdown.

Threads are started lazily on first post() via std::call_once to avoid
spawning threads for pools that are constructed but never used. Each
thread is named with a configurable prefix plus index for debugger
visibility.

Shutdown sequence: stop() requests all threads to stop via their stop
tokens, then the destructor joins threads and destroys any remaining
queued work without executing it.
*/

namespace boost {
namespace capy {

Expand Down Expand Up @@ -49,6 +70,7 @@ class thread_pool::impl
detail::intrusive_queue<work> q_;
std::vector<std::jthread> threads_;
std::size_t num_threads_;
char thread_name_prefix_[13]{}; // 12 chars max + null terminator
std::once_flag start_flag_;

public:
Expand All @@ -61,14 +83,17 @@ class thread_pool::impl
w->destroy();
}

explicit
impl(std::size_t num_threads)
impl(std::size_t num_threads, std::string_view thread_name_prefix)
: num_threads_(num_threads)
{
if(num_threads_ == 0)
num_threads_ = std::thread::hardware_concurrency();
if(num_threads_ == 0)
num_threads_ = 1;

// Truncate prefix to 12 chars, leaving room for up to 3-digit index.
auto n = thread_name_prefix.copy(thread_name_prefix_, 12);
thread_name_prefix_[n] = '\0';
}

void
Expand Down Expand Up @@ -98,13 +123,19 @@ class thread_pool::impl
std::call_once(start_flag_, [this]{
threads_.reserve(num_threads_);
for(std::size_t i = 0; i < num_threads_; ++i)
threads_.emplace_back([this](std::stop_token st){ run(st); });
threads_.emplace_back(
[this, i](std::stop_token st){ run(st, i); });
});
}

void
run(std::stop_token st)
run(std::stop_token st, std::size_t index)
{
// Build name; set_current_thread_name truncates to platform limits.
char name[16];
std::snprintf(name, sizeof(name), "%s%zu", thread_name_prefix_, index);
detail::set_current_thread_name(name);

for(;;)
{
work* w = nullptr;
Expand All @@ -130,8 +161,8 @@ thread_pool::
}

thread_pool::
thread_pool(std::size_t num_threads)
: impl_(new impl(num_threads))
thread_pool(std::size_t num_threads, std::string_view thread_name_prefix)
: impl_(new impl(num_threads, thread_name_prefix))
{
}

Expand Down
Loading
Loading