1- use std:: sync:: atomic:: { AtomicU64 , Ordering } ;
1+ use std:: sync:: atomic:: { AtomicUsize , Ordering } ;
22use std:: thread;
33use std:: time:: Duration ;
44
@@ -8,8 +8,6 @@ use once_cell::sync::Lazy;
88use crate :: task:: { JoinHandle , Task } ;
99use crate :: utils:: { abort_on_panic, random} ;
1010
11- type Runnable = async_task:: Task < Task > ;
12-
1311/// Spawns a blocking task.
1412///
1513/// The task will be spawned onto a thread pool specifically dedicated to blocking tasks. This
@@ -44,93 +42,69 @@ where
4442 F : FnOnce ( ) -> T + Send + ' static ,
4543 T : Send + ' static ,
4644{
45+ let schedule = |task| POOL . sender . send ( task) . unwrap ( ) ;
4746 let ( task, handle) = async_task:: spawn ( async { f ( ) } , schedule, Task :: new ( None ) ) ;
4847 task. schedule ( ) ;
4948 JoinHandle :: new ( handle)
5049}
5150
52- const MAX_THREADS : u64 = 10_000 ;
51+ type Runnable = async_task :: Task < Task > ;
5352
54- static DYNAMIC_THREAD_COUNT : AtomicU64 = AtomicU64 :: new ( 0 ) ;
53+ /// The number of sleeping worker threads.
54+ static SLEEPING : AtomicUsize = AtomicUsize :: new ( 0 ) ;
5555
5656struct Pool {
5757 sender : Sender < Runnable > ,
5858 receiver : Receiver < Runnable > ,
5959}
6060
6161static POOL : Lazy < Pool > = Lazy :: new ( || {
62- for _ in 0 ..2 {
63- thread:: Builder :: new ( )
64- . name ( "async-std/blocking" . to_string ( ) )
65- . spawn ( || {
66- abort_on_panic ( || {
67- for task in & POOL . receiver {
68- task. run ( ) ;
69- }
70- } )
71- } )
72- . expect ( "cannot start a thread driving blocking tasks" ) ;
73- }
62+ // Start a single worker thread waiting for the first task.
63+ start_thread ( ) ;
7464
75- // We want to use an unbuffered channel here to help
76- // us drive our dynamic control. In effect, the
77- // kernel's scheduler becomes the queue, reducing
78- // the number of buffers that work must flow through
79- // before being acted on by a core. This helps keep
80- // latency snappy in the overall async system by
81- // reducing bufferbloat.
8265 let ( sender, receiver) = unbounded ( ) ;
8366 Pool { sender, receiver }
8467} ) ;
8568
86- // Create up to MAX_THREADS dynamic blocking task worker threads.
87- // Dynamic threads will terminate themselves if they don't
88- // receive any work after between one and ten seconds.
89- fn maybe_create_another_blocking_thread ( ) {
90- // We use a `Relaxed` atomic operation because
91- // it's just a heuristic, and would not lose correctness
92- // even if it's random.
93- let workers = DYNAMIC_THREAD_COUNT . load ( Ordering :: Relaxed ) ;
94- if workers >= MAX_THREADS {
95- return ;
96- }
69+ fn start_thread ( ) {
70+ SLEEPING . fetch_add ( 1 , Ordering :: SeqCst ) ;
9771
98- let n_to_spawn = std:: cmp:: min ( 2 + ( workers / 10 ) , 10 ) ;
72+ // Generate a random duration of time between 1 second and 10 seconds. If the thread doesn't
73+ // receive the next task in this duration of time, it will stop running.
74+ let timeout = Duration :: from_millis ( 1000 + u64:: from ( random ( 9_000 ) ) ) ;
9975
100- for _ in 0 ..n_to_spawn {
101- // We want to avoid having all threads terminate at
102- // exactly the same time, causing thundering herd
103- // effects. We want to stagger their destruction over
104- // 10 seconds or so to make the costs fade into
105- // background noise.
106- //
107- // Generate a simple random number of milliseconds
108- let rand_sleep_ms = u64:: from ( random ( 10_000 ) ) ;
76+ thread:: Builder :: new ( )
77+ . name ( "async-std/blocking" . to_string ( ) )
78+ . spawn ( move || {
79+ loop {
80+ let task = match POOL . receiver . recv_timeout ( timeout) {
81+ Ok ( task) => task,
82+ Err ( _) => {
83+ // Check whether this is the last sleeping thread.
84+ if SLEEPING . fetch_sub ( 1 , Ordering :: SeqCst ) == 1 {
85+ // If so, then restart the thread to make sure there is always at least
86+ // one sleeping thread.
87+ if SLEEPING . compare_and_swap ( 0 , 1 , Ordering :: SeqCst ) == 0 {
88+ continue ;
89+ }
90+ }
10991
110- thread:: Builder :: new ( )
111- . name ( "async-std/blocking" . to_string ( ) )
112- . spawn ( move || {
113- let wait_limit = Duration :: from_millis ( 1000 + rand_sleep_ms ) ;
92+ // Stop the thread.
93+ return ;
94+ }
95+ } ;
11496
115- DYNAMIC_THREAD_COUNT . fetch_add ( 1 , Ordering :: Relaxed ) ;
116- while let Ok ( task) = POOL . receiver . recv_timeout ( wait_limit) {
117- abort_on_panic ( || task. run ( ) ) ;
97+ // If there are no sleeping threads, then start one to make sure there is always at
98+ // least one sleeping thread.
99+ if SLEEPING . fetch_sub ( 1 , Ordering :: SeqCst ) == 1 {
100+ start_thread ( ) ;
118101 }
119- DYNAMIC_THREAD_COUNT . fetch_sub ( 1 , Ordering :: Relaxed ) ;
120- } )
121- . expect ( "cannot start a dynamic thread driving blocking tasks" ) ;
122- }
123- }
124102
125- // Enqueues work, attempting to send to the threadpool in a
126- // nonblocking way and spinning up another worker thread if
127- // there is not a thread ready to accept the work.
128- pub ( crate ) fn schedule ( task : Runnable ) {
129- if let Err ( err) = POOL . sender . try_send ( task) {
130- // We were not able to send to the channel without
131- // blocking. Try to spin up another thread and then
132- // retry sending while blocking.
133- maybe_create_another_blocking_thread ( ) ;
134- POOL . sender . send ( err. into_inner ( ) ) . unwrap ( ) ;
135- }
103+ // Run the task.
104+ abort_on_panic ( || task. run ( ) ) ;
105+
106+ SLEEPING . fetch_add ( 1 , Ordering :: SeqCst ) ;
107+ }
108+ } )
109+ . expect ( "cannot start a blocking thread" ) ;
136110}
0 commit comments