11//! Thread pool for blocking operations
22
3- use crate :: loom:: sync:: { Arc , Condvar , Mutex } ;
3+ use crate :: loom:: sync:: { Arc , Mutex } ;
44use crate :: loom:: thread;
55use crate :: runtime:: blocking:: schedule:: BlockingSchedule ;
6+ use crate :: runtime:: blocking:: sharded_queue:: { ShardedQueue , WaitResult } ;
67use crate :: runtime:: blocking:: { shutdown, BlockingTask } ;
78use crate :: runtime:: builder:: ThreadNameFn ;
89use crate :: runtime:: task:: { self , JoinHandle } ;
910use crate :: runtime:: { Builder , Callback , Handle , BOX_FUTURE_THRESHOLD } ;
1011use crate :: util:: metric_atomics:: MetricAtomicUsize ;
1112use crate :: util:: trace:: { blocking_task, SpawnMeta } ;
1213
13- use std:: collections:: { HashMap , VecDeque } ;
14+ use std:: collections:: HashMap ;
1415use std:: fmt;
1516use std:: io;
1617use std:: sync:: atomic:: Ordering ;
@@ -74,11 +75,11 @@ impl SpawnerMetrics {
7475}
7576
7677struct Inner {
77- /// State shared between worker threads .
78- shared : Mutex < Shared > ,
78+ /// Sharded queue for task distribution .
79+ queue : ShardedQueue ,
7980
80- /// Pool threads wait on this .
81- condvar : Condvar ,
81+ /// State shared between worker threads (thread management only) .
82+ shared : Mutex < Shared > ,
8283
8384 /// Spawned threads use this name.
8485 thread_name : ThreadNameFn ,
@@ -103,8 +104,6 @@ struct Inner {
103104}
104105
105106struct Shared {
106- queue : VecDeque < Task > ,
107- num_notify : u32 ,
108107 shutdown : bool ,
109108 shutdown_tx : Option < shutdown:: Sender > ,
110109 /// Prior to shutdown, we clean up `JoinHandles` by having each timed-out
@@ -214,16 +213,14 @@ impl BlockingPool {
214213 BlockingPool {
215214 spawner : Spawner {
216215 inner : Arc :: new ( Inner {
216+ queue : ShardedQueue :: new ( ) ,
217217 shared : Mutex :: new ( Shared {
218- queue : VecDeque :: new ( ) ,
219- num_notify : 0 ,
220218 shutdown : false ,
221219 shutdown_tx : Some ( shutdown_tx) ,
222220 last_exiting_thread : None ,
223221 worker_threads : HashMap :: new ( ) ,
224222 worker_thread_index : 0 ,
225223 } ) ,
226- condvar : Condvar :: new ( ) ,
227224 thread_name : builder. thread_name . clone ( ) ,
228225 stack_size : builder. thread_stack_size ,
229226 after_start : builder. after_start . clone ( ) ,
@@ -253,7 +250,7 @@ impl BlockingPool {
253250
254251 shared. shutdown = true ;
255252 shared. shutdown_tx = None ;
256- self . spawner . inner . condvar . notify_all ( ) ;
253+ self . spawner . inner . queue . shutdown ( ) ;
257254
258255 let last_exited_thread = std:: mem:: take ( & mut shared. last_exiting_thread ) ;
259256 let workers = std:: mem:: take ( & mut shared. worker_threads ) ;
@@ -391,9 +388,8 @@ impl Spawner {
391388 }
392389
393390 fn spawn_task ( & self , task : Task , rt : & Handle ) -> Result < ( ) , SpawnError > {
394- let mut shared = self . inner . shared . lock ( ) ;
395-
396- if shared. shutdown {
391+ // Check shutdown without holding the lock
392+ if self . inner . queue . is_shutdown ( ) {
397393 // Shutdown the task: it's fine to shutdown this task (even if
398394 // mandatory) because it was scheduled after the shutdown of the
399395 // runtime began.
@@ -403,52 +399,64 @@ impl Spawner {
403399 return Err ( SpawnError :: ShuttingDown ) ;
404400 }
405401
406- shared. queue . push_back ( task) ;
402+ // Get thread count for adaptive sharding
403+ let num_threads = self . inner . metrics . num_threads ( ) ;
404+
405+ // Push to the sharded queue - uses adaptive shard count based on thread count
406+ self . inner . queue . push ( task, num_threads) ;
407407 self . inner . metrics . inc_queue_depth ( ) ;
408408
409- if self . inner . metrics . num_idle_threads ( ) == 0 {
410- // No threads are able to process the task.
409+ // Check if we need to spawn a new thread or notify an idle one
410+ let num_idle = self . inner . metrics . num_idle_threads ( ) ;
411411
412- if self . inner . metrics . num_threads ( ) == self . inner . thread_cap {
413- // At max number of threads
414- } else {
415- assert ! ( shared . shutdown_tx . is_some ( ) ) ;
416- let shutdown_tx = shared . shutdown_tx . clone ( ) ;
412+ if num_idle == 0 {
413+ // No idle threads - might need to spawn one
414+ if num_threads < self . inner . thread_cap {
415+ // Try to spawn a new thread
416+ let mut shared = self . inner . shared . lock ( ) ;
417417
418- if let Some ( shutdown_tx) = shutdown_tx {
419- let id = shared. worker_thread_index ;
418+ // Double-check conditions after acquiring the lock
419+ if shared. shutdown {
420+ // Queue was shutdown while we were acquiring the lock
421+ // The task is already in the queue, so it will be drained during shutdown
422+ return Ok ( ( ) ) ;
423+ }
420424
421- match self . spawn_thread ( shutdown_tx, rt, id) {
422- Ok ( handle) => {
423- self . inner . metrics . inc_num_threads ( ) ;
424- shared. worker_thread_index += 1 ;
425- shared. worker_threads . insert ( id, handle) ;
426- }
427- Err ( ref e)
428- if is_temporary_os_thread_error ( e)
429- && self . inner . metrics . num_threads ( ) > 0 =>
430- {
431- // OS temporarily failed to spawn a new thread.
432- // The task will be picked up eventually by a currently
433- // busy thread.
434- }
435- Err ( e) => {
436- // The OS refused to spawn the thread and there is no thread
437- // to pick up the task that has just been pushed to the queue.
438- return Err ( SpawnError :: NoThreads ( e) ) ;
425+ // Re-check thread count (another thread might have spawned one)
426+ if self . inner . metrics . num_threads ( ) < self . inner . thread_cap {
427+ if let Some ( shutdown_tx) = shared. shutdown_tx . clone ( ) {
428+ let id = shared. worker_thread_index ;
429+
430+ match self . spawn_thread ( shutdown_tx, rt, id) {
431+ Ok ( handle) => {
432+ self . inner . metrics . inc_num_threads ( ) ;
433+ shared. worker_thread_index += 1 ;
434+ shared. worker_threads . insert ( id, handle) ;
435+ }
436+ Err ( ref e)
437+ if is_temporary_os_thread_error ( e)
438+ && self . inner . metrics . num_threads ( ) > 0 =>
439+ {
440+ // OS temporarily failed to spawn a new thread.
441+ // The task will be picked up eventually by a currently
442+ // busy thread.
443+ }
444+ Err ( e) => {
445+ // The OS refused to spawn the thread and there is no thread
446+ // to pick up the task that has just been pushed to the queue.
447+ return Err ( SpawnError :: NoThreads ( e) ) ;
448+ }
439449 }
440450 }
441451 }
452+ } else {
453+ // At max threads, notify anyway in case threads are waiting
454+ self . inner . queue . notify_one ( ) ;
442455 }
443456 } else {
444- // Notify an idle worker thread. The notification counter
445- // is used to count the needed amount of notifications
446- // exactly. Thread libraries may generate spurious
447- // wakeups, this counter is used to keep us in a
448- // consistent state.
457+ // There are idle threads waiting, notify one
449458 self . inner . metrics . dec_num_idle_threads ( ) ;
450- shared. num_notify += 1 ;
451- self . inner . condvar . notify_one ( ) ;
459+ self . inner . queue . notify_one ( ) ;
452460 }
453461
454462 Ok ( ( ) )
@@ -505,90 +513,81 @@ impl Inner {
505513 f ( ) ;
506514 }
507515
508- let mut shared = self . shared . lock ( ) ;
516+ // Use worker_thread_id as the preferred shard
517+ let preferred_shard = worker_thread_id;
509518 let mut join_on_thread = None ;
510519
511520 ' main: loop {
512- // BUSY
513- while let Some ( task) = shared . queue . pop_front ( ) {
521+ // BUSY: Process tasks from the queue
522+ while let Some ( task) = self . queue . pop ( preferred_shard ) {
514523 self . metrics . dec_queue_depth ( ) ;
515- drop ( shared) ;
516524 task. run ( ) ;
525+ }
517526
518- shared = self . shared . lock ( ) ;
527+ // Check for shutdown before going idle
528+ if self . queue . is_shutdown ( ) {
529+ break ;
519530 }
520531
521- // IDLE
532+ // IDLE: Wait for new tasks
522533 self . metrics . inc_num_idle_threads ( ) ;
523534
524- while !shared. shutdown {
525- let lock_result = self . condvar . wait_timeout ( shared, self . keep_alive ) . unwrap ( ) ;
526-
527- shared = lock_result. 0 ;
528- let timeout_result = lock_result. 1 ;
529-
530- if shared. num_notify != 0 {
531- // We have received a legitimate wakeup,
532- // acknowledge it by decrementing the counter
533- // and transition to the BUSY state.
534- shared. num_notify -= 1 ;
535- break ;
536- }
537-
538- // Even if the condvar "timed out", if the pool is entering the
539- // shutdown phase, we want to perform the cleanup logic.
540- if !shared. shutdown && timeout_result. timed_out ( ) {
541- // We'll join the prior timed-out thread's JoinHandle after dropping the lock.
542- // This isn't done when shutting down, because the thread calling shutdown will
543- // handle joining everything.
544- let my_handle = shared. worker_threads . remove ( & worker_thread_id) ;
545- join_on_thread = std:: mem:: replace ( & mut shared. last_exiting_thread , my_handle) ;
535+ loop {
536+ match self . queue . wait_for_task ( preferred_shard, self . keep_alive ) {
537+ WaitResult :: Task ( task) => {
538+ // Got a task, process it
539+ self . metrics . dec_num_idle_threads ( ) ;
540+ self . metrics . dec_queue_depth ( ) ;
541+ task. run ( ) ;
542+ // Go back to busy loop
543+ break ;
544+ }
545+ WaitResult :: Shutdown => {
546+ // Shutdown initiated
547+ self . metrics . dec_num_idle_threads ( ) ;
548+ break ' main;
549+ }
550+ WaitResult :: Timeout => {
551+ // Timed out, exit this thread
552+ self . metrics . dec_num_idle_threads ( ) ;
553+
554+ // Clean up thread handle
555+ let mut shared = self . shared . lock ( ) ;
556+ if !shared. shutdown {
557+ let my_handle = shared. worker_threads . remove ( & worker_thread_id) ;
558+ join_on_thread =
559+ std:: mem:: replace ( & mut shared. last_exiting_thread , my_handle) ;
560+ }
561+ drop ( shared) ;
546562
547- break ' main;
563+ // Exit the main loop and terminate the thread
564+ break ' main;
565+ }
566+ WaitResult :: Spurious => {
567+ // Spurious wakeup, check for tasks and continue waiting
568+ if let Some ( task) = self . queue . pop ( preferred_shard) {
569+ self . metrics . dec_num_idle_threads ( ) ;
570+ self . metrics . dec_queue_depth ( ) ;
571+ task. run ( ) ;
572+ break ;
573+ }
574+ // Continue waiting
575+ }
548576 }
549-
550- // Spurious wakeup detected, go back to sleep.
551577 }
578+ }
552579
553- if shared. shutdown {
554- // Drain the queue
555- while let Some ( task) = shared. queue . pop_front ( ) {
556- self . metrics . dec_queue_depth ( ) ;
557- drop ( shared) ;
558-
559- task. shutdown_or_run_if_mandatory ( ) ;
560-
561- shared = self . shared . lock ( ) ;
562- }
563-
564- // Work was produced, and we "took" it (by decrementing num_notify).
565- // This means that num_idle was decremented once for our wakeup.
566- // But, since we are exiting, we need to "undo" that, as we'll stay idle.
567- self . metrics . inc_num_idle_threads ( ) ;
568- // NOTE: Technically we should also do num_notify++ and notify again,
569- // but since we're shutting down anyway, that won't be necessary.
570- break ;
571- }
580+ // Drain remaining tasks if shutting down
581+ if self . queue . is_shutdown ( ) {
582+ self . queue . drain ( |task| {
583+ self . metrics . dec_queue_depth ( ) ;
584+ task. shutdown_or_run_if_mandatory ( ) ;
585+ } ) ;
572586 }
573587
574588 // Thread exit
575589 self . metrics . dec_num_threads ( ) ;
576590
577- // num_idle should now be tracked exactly, panic
578- // with a descriptive message if it is not the
579- // case.
580- let prev_idle = self . metrics . dec_num_idle_threads ( ) ;
581- assert ! (
582- prev_idle >= self . metrics. num_idle_threads( ) ,
583- "num_idle_threads underflowed on thread exit"
584- ) ;
585-
586- if shared. shutdown && self . metrics . num_threads ( ) == 0 {
587- self . condvar . notify_one ( ) ;
588- }
589-
590- drop ( shared) ;
591-
592591 if let Some ( f) = & self . before_stop {
593592 f ( ) ;
594593 }
0 commit comments