- improve joining
- use wait_while and wait_timeout_while to protect against spurious wakeups
- add join_generation field to WorkerData to make sure all threads
waiting for a join leave at once by having the first awakened thread
increment the counter
- otherwise the pool might become non-idle again before all notified threads were awakened, causing them to keep waiting
- implement ThreadPool::start_core_threads
- starts all core workers by creating core idle workers until the total worker count reaches the core count
- implement WorkerCountData::try_increment_worker_count as an alternative to try_increment_worker_total allowing to specify a custom increment
- replace INCREMENT_TOTAL + INCREMENT_IDLE with INCREMENT_TOTAL | INCREMENT_IDLE as a bitwise or is enough to combine the increments as they have no overlapping bit and addition is not required
- make field JoinHandle.receiver public
- replace Atomic64 with AtomicUsize for worker count bookkeeping
- refactor worker count bookkeeping arithmetics to work based on the size of usize on a given platform instead of requiring 64 bits
- enables using the crate on non 64 bit platforms
- Replace compare_and_swap with compare_exchange_weak.
- compare_and_swap has been deprecated as of rust 1.50.0
- Instead of incrementing the worker total and using the returned
previous value to check if the worker is still required and then
decrementing the counter again if that's not the case, use a compare
and swap mechanism implemented by WorkerCountData::try_increment_worker_total
to increment the total count if the current workerCountData matches
the expected value and retry until it succeeds or until the observed
value reaches the provided maximum value (either the core pool size
when attempting to create a core worker, else the max pool size),
at which point the pool proceeds to creating a non-core worker or
to sending the task to the channel.
- This only requires one atomic operation per retry instead of 2 (if contention is high it may lead to more retries as increments could not fail before but retries are much cheaper as it does not have to decrement the counter and recursively call the method) and guarantees that the total worker count is always accurate.
- Use Ordering::Relaxed instead of SeqCst as the relaxed ordering suffices when incrementing a counter.
- ThreadPool::default: Handle possible u32 overflow when calculating max_size.
- Update dependencies.
- Use twice the core count instead of 4 times the core count as default max_size.
- Worker::mark_idle_and_notify_joiners_if_no_work: Check whether old_total_count equals old_idle_count + 1 before checking whether the receiver is empty as it is the cheaper operation.
- _do_join(): recheck whether the pool has become idle after acquiring the lock
- Builder: make sure core size is lower or equal to max size if max size was specified explicitly but core size wasn't.
- build() panicked if max size was specified to be lower than default core size.
- Set the core size to the specified max size if lower than the number of CPUs.
- Handle possible u32 overflow when calculating default max size based on core size.
- Remove unneeded
Sync
trait bounds. - Remove unneeded boxing.
- Add additional tests.
- Add
spawn_await()
andtry_spawn_await()
as an extension ofspawn()
that creates a new top-level future that awaits the provided future and sends the result to aJoinHandle
. This enables awaiting the final result outside of an async context likecomplete()
while still polling the future lazily instead of eagerly blocking the worker until the future is done. - Ignore dropped oneshot receiver for
try_evaluate()
.- It is perfectly valid that the caller does not care about the result and drops the
JoinHandle
before the result has been completed. In that case attempting to send the result should not panic.
- It is perfectly valid that the caller does not care about the result and drops the
- Add async support
- Add "async" feature that enables using the rusty_pool as a fully featured futures executor that may poll features and handle awakened futures.
- Add "complete" function to simply block a worker until a future is polled to completion.
- Add
JoinHandle
to enable awaiting a task's completion.- The
JoinHandle
holds the receiving end of a oneshot channel that receives the result of the task or a cancellation error if the task panics. Theawait_complete()
andtry_await_complete()
functions can be used to block the calling thread until the task has completed or failed. TheJoinHandle
may also be sent to theThreadPool
to create a task that awaits the completion of the other task and then performs work using the result.
- The
- Add Builder struct to create a new
ThreadPool
more conveniently. - Name the
ThreadPool
and use the name as prefix for each worker thread's name. - Create
Task
trait representing everything that can be executed by theThreadPool
.- Add an implementation for any
FnOnce
closure that returns a thread-safe result - Add an implementation for
AsyncTask
representing a future that may be polled and awakened if the "async" feature is enabled.
- Add an implementation for any
- Implement
Default
forThreadPool
. - Implement
Clone
forThreadPool
.- All clones will use the same channel and the same worker pool, meaning shutting down and dropping one clone will
not cause the channel to break and other clones will still be intact. This also means that
shutdown_join()
will behave the same as callingjoin()
on a liveThreadPool
while only dropping said clone, meaning tasks submitted to other clones aftershutdown()
is called on this clone are joined as well. AsyncTask
instances hold a clone of theThreadPool
they were created for used to re-submit themselves when being awakened. This means the channel stays alive as long as there are pendingAsyncTask
s.
- All clones will use the same channel and the same worker pool, meaning shutting down and dropping one clone will
not cause the channel to break and other clones will still be intact. This also means that
- Update readme and documentation and add doctests.
- Cleanup worker and channel data by putting them into separate structs.
- Only 2 Arcs needed now by wrapping the entire
ChannelData
andWorkerData
structs into an Arc.
- Only 2 Arcs needed now by wrapping the entire
- Add proper handling for panicking threads using a
Sentinel
that implements Drop and manages handling post-execution steps for workers that panicked while executing a submitted task and clones and replaces the worker. - Improve joins by also checking whether the
Receiver
is empty before fast-returning to eliminate a race condition where if a thread calls join in a brief window between one worker finishing a task and the next worker beginning a task.
- No longer wrap crossbeam Receivers inside an Arc because they already can be cloned and sent to other threads.
- Removed
Result
fromThreadPool::execute()
and addedThreadPool::try_execute()
instead.- Most often the user does not want to handle the result and prefers a panic in the very rare case the function returns an error (should not be possible with safe code).
- Added
shutdown_join()
andshutdown_join_timeout(Duration)
to block the current thread until work in the pool is finished when shutting down the pool. - Improved bookkeeping of the total and idle worker counters by putting both values into one
AtomicU64
, which enables updating both counters in one single atomic operation.- this is part of a series of otherwise small changes to make joining the threadpool fully reliable and eliminating races where a thread could join the pool, see that the idle worker counter is lower than the total worker counter and assume that there is work to be joined when in fact the read was performed just before the idle worker counter was incremented, leading the thread to wait for work to be completed when all threads are idle. This should never happen now because an increment to both counter is one single atomic operation and the idle worker counter is incremented immediately after a worker finishes a task and before joining threads are notified. Because even if a thread would join exactly after the worker finished but before the idle worker count is incremented back to being equal to the total worker count it will get notified by the worker immediately after.
- Improved Worker creation.
- The workers now only spawn a thread once the re-check passes successfully, meaning the new worker is committed. The new worker is now started explicitly
when the re_check succeeds and receives it's first task via the
start()
function directly. Thecreate_worker()
function now returns a Result where the Err variant contains the submitted task in case the re-check fails and the new worker is abandoned in which case the task will be sent to the main multi consumer channel instead.
- The workers now only spawn a thread once the re-check passes successfully, meaning the new worker is committed. The new worker is now started explicitly
when the re_check succeeds and receives it's first task via the
- If the re-recheck fails when creating new core worker try creating a non-core thread instead.
- Fix workers notifying joining threads when the channel is empty but there's still threads executing work.