fix(threadpool): batches run every task once, and Stop ends the whole pool
- Enqueue(vector) dropped tasks when the count didn't divide by the workers: the last one got only the remainder. Tasks are now split as evenly as the count allows, each worker's taskCount grows by what it was given (the single-task Enqueue picks by it), and Start(1), which has no workers, leaves the batch for the caller's JoinPool. - Stop wakes every slot, JoinPool's too, after joining the workers: a thread that had joined the pool slept on. It takes each slot's mutex before notifying, so a waiter between its check and its sleep can't miss the wake-up, and works on the pool it started with. Again, or before Start, it does nothing; joining a thread twice threw. Its documentation says what it doesn't do: stop from a pool task, or from two threads at once, run tasks enqueued once it has begun, or have JoinPool wait for it. - Tests: EnqueueBatch, StopJoinPool. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
0837bbab4c
commit
f71cb43e85
5 changed files with 308 additions and 149 deletions
43
tests/StopJoinPool/main.cpp
Normal file
43
tests/StopJoinPool/main.cpp
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
//SPDX-License-Identifier: LGPL-3.0-only
|
||||
//SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||||
|
||||
// ThreadPool::Stop ends the pool for everyone in it, the thread that joined
|
||||
// it with JoinPool too. Stop woke and joined the worker threads only, so a
|
||||
// JoinPool caller slept on, never told. And Stop called again joined threads
|
||||
// already joined, which throws.
|
||||
//
|
||||
// Here another thread stops the pool this one has joined, then stops it
|
||||
// again. A watchdog fails the test if JoinPool doesn't return.
|
||||
|
||||
#include <cstdlib>
|
||||
#include <unistd.h>
|
||||
|
||||
import Crafter.Thread;
|
||||
import std;
|
||||
|
||||
using namespace Crafter;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
int main() {
|
||||
std::thread watchdog([] {
|
||||
std::this_thread::sleep_for(10s);
|
||||
std::println("FAIL JoinPool didn't return within 10 s of Stop");
|
||||
_exit(EXIT_FAILURE);
|
||||
});
|
||||
watchdog.detach();
|
||||
|
||||
ThreadPool::Start(4);
|
||||
std::atomic<int> ran = 0;
|
||||
for (int i = 0; i < 100; ++i) ThreadPool::Enqueue([&ran] { ++ran; });
|
||||
std::thread stopper([] {
|
||||
std::this_thread::sleep_for(100ms);
|
||||
ThreadPool::Stop();
|
||||
});
|
||||
ThreadPool::JoinPool();
|
||||
stopper.join();
|
||||
std::println("{} Stop from another thread ends the JoinPool of the thread that joined, after the queued tasks ({} of 100)",
|
||||
ran == 100 ? "PASS" : "FAIL", ran.load());
|
||||
ThreadPool::Stop();
|
||||
std::println("PASS and stopping it again does nothing");
|
||||
return ran == 100 ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
Loading…
Reference in a new issue