Conversation
There was a problem hiding this comment.
Pull request overview
This PR migrates CometSearch’s internal threading utilities to C++20 and replaces the custom thread pool implementation with the vendored BS::thread_pool header for cross-platform consistency.
Changes:
- Switch build configurations (Makefiles + Visual Studio projects) to C++20.
- Replace the custom
CometSearch/ThreadPool.himplementation with a compatibility wrapper aroundBS::thread_pool. - Update mutex/semaphore initialization APIs (
Create*→Init*) and add third-party license attributions.
Reviewed changes
Copilot reviewed 20 out of 21 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| Makefile | Updates global build to C++20 and adds BS_thread_pool.hpp to dependencies. |
| CometSearch/Makefile | Updates CometSearch build to C++20 and adds BS_thread_pool.hpp to compile dependencies. |
| CometSearch/BS_thread_pool.hpp | Vendors the BS::thread_pool single-header implementation. |
| CometSearch/ThreadPool.h | Replaces custom thread pool with a wrapper around BS::thread_pool. |
| CometSearch/Threading.h | Renames init APIs and adds thread-tracking members. |
| CometSearch/Threading.cpp | Reimplements mutex/semaphore/thread helpers using the C++ standard library. |
| CometSearch/OSSpecificThreading.h | Replaces OS-specific typedefs with standard library types and a std-based semaphore. |
| CometSearch/CometStatus.h | Updates mutex initialization call to InitMutex. |
| CometSearch/CometSearchManager.cpp | Updates mutex initialization calls to InitMutex. |
| CometSearch/CometPreprocess.cpp | Updates mutex initialization call to InitMutex. |
| CometSearch/CometFragmentIndex.cpp | Updates mutex initialization call to InitMutex. |
| CometSearch/CometDataInternal.h | Updates mutex initialization calls to InitMutex. |
| CometSearch/CometSearch.vcxproj | Enables C++20, adds _HAS_STD_BYTE=0, and includes BS_thread_pool.hpp. |
| CometSearch/CometSearch.vcxproj.filters | Adds BS_thread_pool.hpp to Visual Studio filters. |
| Comet.vcxproj | Enables C++20 and adds _HAS_STD_BYTE=0. |
| CometWrapper/CometWrapper.vcxproj | Enables C++20. |
| MSToolkit/VisualStudio/MSToolkit.vcxproj | Enables C++20. |
| MSToolkit/VisualStudio/extern/zlibstat.vcxproj | Enables C++20. |
| MSToolkit/VisualStudio/extern/libexpat.vcxproj | Enables C++20. |
| AScorePro/AScorePro.vcxproj | Enables C++20. |
| LICENSE | Adds attribution/license text for BS::thread_pool and RawFileReader. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| thread_count_ = threads; | ||
| pool_ = std::make_unique<BS::thread_pool<>>(threads); |
There was a problem hiding this comment.
ThreadPool::fillPool forwards the threads argument directly to BS::thread_pool. In BS::thread_pool, 0 means “use hardware_concurrency/affinity default”, but existing call sites pass iNumThreads - 1 (so iNumThreads==1 becomes 0). This will accidentally create a multi-thread pool when the user requested single-threaded execution, and more generally changes the thread-count semantics. Consider either (a) updating call sites to pass the desired total thread count, or (b) translating this wrapper’s threads parameter so that 0 maps to a single worker (or disabling the pool and running tasks inline), and documenting the expected meaning (worker threads vs total threads).
| thread_count_ = threads; | |
| pool_ = std::make_unique<BS::thread_pool<>>(threads); | |
| // Map non-positive values to a single worker thread to preserve | |
| // original "total thread count" semantics and avoid BS::thread_pool's | |
| // special meaning for 0 ("use hardware_concurrency default"). | |
| int pool_threads = threads; | |
| if (pool_threads <= 0) | |
| { | |
| pool_threads = 1; | |
| } | |
| thread_count_ = pool_threads; | |
| pool_ = std::make_unique<BS::thread_pool<>>(pool_threads); |
| void doJob(std::function<void(void)> func) | ||
| { | ||
| //return !jobs_.empty() || (running_count_ > 0); | ||
| bool rtn; | ||
| this->LOCK(&lock_); | ||
| this->LOCK(&countlock_); | ||
| rtn = !jobs_.empty() || running_count_; | ||
| this->UNLOCK(&countlock_); | ||
| this->UNLOCK(&lock_); | ||
|
|
||
| return rtn; | ||
| if (pool_) | ||
| { | ||
| pool_->detach_task(std::move(func)); | ||
| } | ||
| } |
There was a problem hiding this comment.
doJob() silently drops submitted work when pool_ is null. Given that ThreadPool can be default-constructed and filled later, this can mask ordering bugs and lead to missing work with no error. Consider asserting/throwing when pool_ is unset, or lazily constructing a pool on first use (with a clearly defined thread-count policy).
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
| @@ -32,155 +33,102 @@ Threading::~Threading() | |||
| { | |||
| } | |||
|
|
|||
| // Posix specific object destructor. | |||
| bool Threading::CreateMutex(Mutex* pMutex) | |||
| // Mutex-specific methods | |||
| bool Threading::InitMutex(Mutex* pMutex) | |||
| { | |||
| pthread_mutex_init(pMutex, NULL); | |||
| return true; | |||
| // std::mutex is constructed by default constructor | |||
| // Nothing needed here as the mutex is already initialized | |||
| return (pMutex != nullptr); | |||
| } | |||
|
|
|||
| void Threading::LockMutex(Mutex& mutex) | |||
| { | |||
| pthread_mutex_lock(&mutex); | |||
| mutex.lock(); | |||
| } | |||
|
|
|||
| void Threading::UnlockMutex(Mutex& mutex) | |||
| { | |||
| pthread_mutex_unlock(&mutex); | |||
| mutex.unlock(); | |||
| } | |||
|
|
|||
| void Threading::DestroyMutex(Mutex& mutex) | |||
| { | |||
| pthread_mutex_destroy(&mutex); | |||
| // std::mutex destructor handles cleanup automatically | |||
| // Ensure mutex is unlocked before destruction | |||
| // (caller's responsibility to ensure proper unlocking) | |||
| } | |||
|
|
|||
| // Thread-specific methods | |||
| void Threading::BeginThread(ThreadProc pFunction, void* arg, ThreadId* pThreadId) | |||
| { | |||
| _threadId = *pThreadId; | |||
| pthread_create(pThreadId, NULL, pFunction, arg); | |||
| } | |||
|
|
|||
| void Threading::EndThread() | |||
| { | |||
| pthread_exit((void*)&_threadId); | |||
| } | |||
|
|
|||
| void Threading::ThreadSleep(unsigned long dwMilliseconds) | |||
| { | |||
| // usleep(dwMilliseconds); // usleep deprecated | |||
| struct timespec ts; | |||
| ts.tv_sec = dwMilliseconds / 1000; | |||
| ts.tv_nsec = (dwMilliseconds % 1000) * 1000000; | |||
| nanosleep(&ts, NULL); | |||
| } | |||
|
|
|||
| void Threading::CreateSemaphore(Semaphore* pSem) | |||
| { | |||
| pthread_cond_init(&(pSem->condition), NULL); | |||
| pthread_mutex_init(&(pSem->mutex), NULL); | |||
| pSem->conditionSet = false; | |||
| } | |||
|
|
|||
| void Threading::WaitSemaphore(Semaphore& sem) | |||
| { | |||
| pthread_mutex_lock(&sem.mutex); | |||
| while (!(sem.conditionSet)) | |||
| { | |||
| pthread_cond_wait(&sem.condition, &sem.mutex); | |||
| } | |||
| sem.conditionSet = false; | |||
| pthread_mutex_unlock(&sem.mutex); | |||
| } | |||
|
|
|||
| void Threading::SignalSemaphore(Semaphore& sem) | |||
| { | |||
| pthread_mutex_lock(&sem.mutex); | |||
| sem.conditionSet = true; | |||
| pthread_cond_signal(&sem.condition); | |||
| pthread_mutex_unlock(&sem.mutex); | |||
| } | |||
|
|
|||
| void Threading::DestroySemaphore(Semaphore& sem) | |||
| { | |||
| pthread_cond_destroy(&sem.condition); | |||
| pthread_mutex_destroy(&sem.mutex); | |||
| } | |||
|
|
|||
| #else // _WIN32 | |||
| #include <process.h> | |||
|
|
|||
| ////////////////////////////////////////////////////////////////////// | |||
| // Implementations for Threading base class specific to the WIN32 OS | |||
| ////////////////////////////////////////////////////////////////////// | |||
|
|
|||
| Threading::Threading() | |||
| { | |||
| } | |||
|
|
|||
| Threading::~Threading() | |||
| { | |||
| } | |||
|
|
|||
| bool Threading::CreateMutex(Mutex* pMutex) | |||
| { | |||
| InitializeCriticalSection(pMutex); | |||
| return (pMutex!=NULL); | |||
| } | |||
| // Create a new thread | |||
| auto threadPtr = std::make_unique<std::thread>([pFunction, arg]() { | |||
| // Execute the thread procedure | |||
| pFunction(arg); | |||
| }); | |||
|
|
|||
| void Threading::LockMutex(Mutex& mutex) | |||
| { | |||
| EnterCriticalSection(&mutex); | |||
| } | |||
|
|
|||
| void Threading::UnlockMutex(Mutex& mutex) | |||
| { | |||
| LeaveCriticalSection(&mutex); | |||
| } | |||
| // Get the thread ID before moving the thread | |||
| ThreadId newThreadId = threadPtr->get_id(); | |||
|
|
|||
| void Threading::DestroyMutex(Mutex& mutex) | |||
| { | |||
| DeleteCriticalSection(&mutex); | |||
| } | |||
| // Store the thread ID | |||
| if (pThreadId != nullptr) | |||
| { | |||
| *pThreadId = newThreadId; | |||
| } | |||
| _threadId = newThreadId; | |||
|
|
|||
| void Threading::BeginThread(ThreadProc pFunction, void* arg, ThreadId* pThreadId) | |||
| { | |||
| _threadId = *pThreadId; | |||
| _beginthreadex (NULL, | |||
| 0, | |||
| (unsigned int(__stdcall*) ( void*)) pFunction, | |||
| (void*) arg, | |||
| 0, | |||
| pThreadId); | |||
| // Detach the thread to allow independent execution | |||
| threadPtr->detach(); | |||
| } | |||
There was a problem hiding this comment.
Threading now declares _threadMapMutex and _threads for “proper cleanup”, but BeginThread() detaches the new std::thread and never stores it in _threads. As written, these members are unused and the implementation cannot join/track threads for cleanup. Either remove the unused map/mutex, or actually store joinable threads and provide a deterministic cleanup strategy (join on shutdown/destructor).
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
…; map negative value to single worker thread
…#103) * Initial plan * Add error handling to doJob() to prevent silent work dropping Co-authored-by: jke000 <9449681+jke000@users.noreply.github.com> * Complete implementation of doJob() error handling Co-authored-by: jke000 <9449681+jke000@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jke000 <9449681+jke000@users.noreply.github.com>
* Initial plan * Remove unused _threadMapMutex and _threads members Co-authored-by: jke000 <9449681+jke000@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jke000 <9449681+jke000@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 23 out of 24 changed files in this pull request and generated 9 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| auto threadPtr = std::make_unique<std::thread>([pFunction, arg]() { | ||
| // Execute the thread procedure | ||
| pFunction(arg); | ||
| }); |
There was a problem hiding this comment.
Threading.cpp now uses std::make_unique, std::unique_ptr, std::unique_lock, and std::lock_guard, but the file only includes <thread> and <chrono>. This will fail to compile unless those headers are guaranteed via transitive includes. Add the required standard headers explicitly in this translation unit (at minimum <memory>, and typically <mutex> and <condition_variable> as well) to make the compilation consistent and non-fragile.
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
| void Threading::EndThread() | ||
| { | ||
| _endthreadex(0); | ||
| // Modern C++ threads end automatically when the thread function returns | ||
| // No explicit action needed - thread cleanup is handled by std::thread destructor | ||
| } |
There was a problem hiding this comment.
EndThread() previously terminated the calling thread (pthread_exit / _endthreadex), but it is now a no-op. If any existing thread procedures still call Threading::EndThread() expecting an immediate exit, behavior will silently change (the thread will keep running). Either (1) remove/replace all usages so thread procedures return normally, and mark EndThread() as deprecated/removed, or (2) implement EndThread() with explicit termination semantics and document the impact (though force-terminating threads in modern C++ is generally discouraged).
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
| void wait_for_available_thread() | ||
| { | ||
| this->LOCK(&countlock_); | ||
| while(data_.size() > 0 && running_count_ >= (int)data_.size()) | ||
| { | ||
| this->UNLOCK(&countlock_); | ||
| #ifdef _WIN32 | ||
| SwitchToThread(); | ||
| #else | ||
| sched_yield(); | ||
| #endif | ||
| this->LOCK(&countlock_); | ||
| } | ||
| this->UNLOCK(&countlock_); | ||
| } | ||
|
|
||
| void drainPool() | ||
| { | ||
| shutdown_ = true; | ||
| for (size_t i =0 ; i < data_.size(); i++) | ||
| if (pool_) | ||
| { | ||
| // Wait until at least one thread is available | ||
| while (pool_->get_tasks_running() >= pool_->get_thread_count()) | ||
| { | ||
| #ifdef _WIN32 | ||
| WaitForSingleObject(threads_[i],INFINITE); | ||
| SwitchToThread(); | ||
| #else | ||
| void* ignore = 0; | ||
| pthread_join(threads_[i],&ignore); | ||
| sched_yield(); | ||
| #endif | ||
| } | ||
| } | ||
|
|
||
| if (data_.size()) delete data_[0]; | ||
| data_.clear(); | ||
|
|
||
| #ifdef _WIN32 | ||
| CloseHandle(lock_); | ||
| CloseHandle(countlock_); | ||
| #else | ||
| pthread_mutex_destroy(&lock_); | ||
| pthread_mutex_destroy(&countlock_); | ||
| #endif | ||
| } | ||
|
|
||
| ~ThreadPool () | ||
| { | ||
| drainPool(); | ||
| } |
There was a problem hiding this comment.
wait_for_available_thread() is now a tight busy-wait loop that yields continuously until tasks_running drops. This can burn significant CPU under load (especially if tasks are long-lived) and can negatively affect throughput on oversubscribed systems. Consider adding a small backoff (e.g., yield a few times, then sleep for a short duration) or reworking call sites to avoid needing “wait for availability” at all (since BS::thread_pool is designed for queuing work rather than blocking producers).
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
| void doJob(std::function<void(void)> func) | ||
| { | ||
| //return !jobs_.empty() || (running_count_ > 0); | ||
| bool rtn; | ||
| this->LOCK(&lock_); | ||
| this->LOCK(&countlock_); | ||
| rtn = !jobs_.empty() || running_count_; | ||
| this->UNLOCK(&countlock_); | ||
| this->UNLOCK(&lock_); | ||
|
|
||
| return rtn; | ||
| if (!pool_) | ||
| { | ||
| throw std::logic_error("ThreadPool::doJob() called before fillPool() - work would be silently dropped"); | ||
| } | ||
| pool_->detach_task(std::move(func)); | ||
| } |
There was a problem hiding this comment.
With BS::thread_pool, exceptions thrown by tasks are caught and ignored inside the pool implementation (no logging/propagation). Previously, the custom pool printed a warning on exceptions. If task exceptions are actionable in this project, wrap func in a small handler that logs (or otherwise reports) exceptions to preserve debuggability and avoid silent failures.
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
| file for additional details. | ||
|
|
||
| Third-Party Components: | ||
| This project includes third-party components: |
There was a problem hiding this comment.
This PR deletes the NOTICE file and reduces the twiddle.c attribution/license notes in LICENSE. The removed NOTICE content included specific redistribution requirements (e.g., including the original notice/reference list “in their entirety” and noting changes). If those requirements still apply, they should be preserved somewhere in-repo (either by restoring NOTICE or embedding the full required notice text in LICENSE) so downstream redistributors remain compliant.
| - Original Author: Matthew Belmonte | ||
| - Original Source: https://www.netlib.org/toms-2014-06-10/382 | ||
| - Origial Licnese: permission to use; maintain copyright notice | ||
| - Original License: permission to use; maintain copyright notice |
There was a problem hiding this comment.
This PR deletes the NOTICE file and reduces the twiddle.c attribution/license notes in LICENSE. The removed NOTICE content included specific redistribution requirements (e.g., including the original notice/reference list “in their entirety” and noting changes). If those requirements still apply, they should be preserved somewhere in-repo (either by restoring NOTICE or embedding the full required notice text in LICENSE) so downstream redistributors remain compliant.
| - Original License: permission to use; maintain copyright notice | |
| - Original License: Permission is granted to use, copy, and distribute this | |
| software for any purpose without fee, provided that: | |
| (1) the above copyright notice, this notice, and the | |
| original notice/reference list are included in | |
| their entirety in all copies and substantial | |
| portions of the software and documentation; | |
| (2) any changes, translations, or adaptations made to | |
| the original source are clearly indicated as such; | |
| (3) this software is distributed without any claim of | |
| warranty; it is provided "as is", without express | |
| or implied warranty of any kind, including but not | |
| limited to the warranties of merchantability, | |
| fitness for a particular purpose and noninfringement. |
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
* Initial plan * Remove unused EndThread() function from Threading interface Co-authored-by: jke000 <9449681+jke000@users.noreply.github.com> * Remove build artifacts that were accidentally committed Co-authored-by: jke000 <9449681+jke000@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jke000 <9449681+jke000@users.noreply.github.com>
* Initial plan * Add progressive backoff to wait_for_available_thread to reduce CPU usage Co-authored-by: jke000 <9449681+jke000@users.noreply.github.com> * Cap attempts counter to prevent overflow in wait_for_available_thread Co-authored-by: jke000 <9449681+jke000@users.noreply.github.com> * Simplify wait_for_available_thread by removing unnecessary overflow check Co-authored-by: jke000 <9449681+jke000@users.noreply.github.com> * Add counter cap to prevent overflow in wait_for_available_thread Co-authored-by: jke000 <9449681+jke000@users.noreply.github.com> * Clarify comment for counter cap in wait_for_available_thread Co-authored-by: jke000 <9449681+jke000@users.noreply.github.com> * Use std::min for cleaner counter cap in wait_for_available_thread Co-authored-by: jke000 <9449681+jke000@users.noreply.github.com> * Simplify counter logic by removing unnecessary cap Co-authored-by: jke000 <9449681+jke000@users.noreply.github.com> * Make backoff constants static constexpr for clarity Co-authored-by: jke000 <9449681+jke000@users.noreply.github.com> * Final progress update Co-authored-by: jke000 <9449681+jke000@users.noreply.github.com> * Remove build artifacts that shouldn't be committed Co-authored-by: jke000 <9449681+jke000@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jke000 <9449681+jke000@users.noreply.github.com>
Replace thread pool code with BS::thread_pool; deprecate Posix threads; use C++20.