1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
//! A throttle pool library designed for thread-based concurrency.
//!
//! # Concepts
//!
//! This crate contain two primary types: [`ThrottlePool`] and [`Throttle`].
//!
//! Each [`Throttle`] has it own concurrent and interval state for delaying.
//! On the other hand, [`ThrottlePool`] can automatic create [`Throttle`] when
//! corresponding `id` first time be used. User can treat `id` as some kind of
//! resource identity like hostname, IP address, etc.
//!
//! Here is a running chart of a [`ThrottlePool`] with `concurrent` == `2`.
//!
//! ```text
//! ThrottlePool
//!  |
//!  +-- Throttle (resource-1)
//!  |      |
//!  |      +-- Thread quota 1     ... run ...
//!  |      +-- Thread quota 2     ... run ...
//!  |
//!  +-- Throttle (resource-2)
//!  |      |
//!  |      +-- Thread quota 3     ... run ...
//!  |      +-- Thread quota 4     ... run ...
//!  ...
//!  +-- Throttle (resource-N)
//!         |
//!         +-- Thread quota 2N-1  ... run ...
//!         +-- Thread quota 2N    ... run ...
//! ```
//!
//!
//!
//! If `concurrent == 1`, thread quota usage may work like this:
//!
//! ```text
//! f: assigned jobs, s: sleep function
//!
//! thread 1:   |f()----|s()----|f()--|s()------|f()----------------|f()-----|..........|f()--
//!             |   interval    |   interval    |   interval    |...|   interval    |...|
//!                                             ^^^^^^^^^^^^^^^^^^^^^
//!             job run longer than interval --^                             ^^^^^^^^
//!             so skip sleep() step                                        /
//!                                                                        /
//!                 If new job not inject into the -----------------------
//!                 "should wait interval", sleep() will not be triggered
//!
//! time pass ----->
//! ```
//!
//!
//!
//! If `concurrent == 2`, threads will work like this:
//!
//! ```text
//! f: assigned jobs, s: sleep function
//!
//! thread 1:   |f()----|s()----|f()--|s()------|f()------------------------------|....|f()--
//!             |   interval    |   interval    |           2x interval         |......|
//!
//! thread 2:   ........|f()-|s()-------|f()-------|s()-|f()|s()|f()--|s|f()-|s-|f()---------
//!             ........|   interval    |   interval    |  1/2  |  1/2  |  1/2  |
//!                                                     ^^^^^^^^^^^^^^^^^^^^^^^^^
//!                 max concurrent forced to 2  -------^
//!                 but expected value of maximux access speed is "concurrent per interval".
//!
//! time pass ----->
//! ```
//!
//! [`Throttle`] would not create threads, but only block current thread.
//! User should create threads by themself and sync throttle to all those
//! threads, to control access speed entirely.
//!
//! User can just using [`Throttle`] directly if not need the pool-related facility.
//!
//!
//!
//! # Examples
//!
//! ```rust
//! use rayon::prelude::*;
//! use slottle::ThrottlePool;
//! use std::time::{Duration, Instant};
//!
//! // Make sure we have enough of threads can be blocked.
//! // Here we use rayon as example but you can choice any thread implementation.
//! rayon::ThreadPoolBuilder::new()
//!     .num_threads(8)
//!     .build_global()
//!     .unwrap();
//!
//! // Create ThrottlePool.
//! //
//! // In here `id` is `bool` type for demonstration.
//! // If you're writing a web spider, type of `id` might should be `url::Host`.
//! let throttles: ThrottlePool<bool> = ThrottlePool::builder()
//!     .interval(Duration::from_millis(20)) // set interval to 20ms
//!     .concurrent(2) // set concurrent to 2
//!     .build()
//!     .unwrap();
//!
//! // HINT: according previous config, expected access speed is
//! // 2 per 20ms = 1 per 10ms (in each throttle)
//!
//! let started_time = Instant::now();
//!
//! let mut all_added_one: Vec<i32> = vec![1, 2, 3, 4, 5, 6]
//!     .into_par_iter()
//!     .map(|x| {
//!         throttles
//!             .get(x >= 5)    // 5,6 in throttle `true` & 1,2,3,4 in throttle `false`
//!             .run(|| {       // here is the operation we want to throttling
//!                 let time_passed_ms = started_time.elapsed().as_secs_f64() * 1000.0;
//!                 println!(
//!                     "[throttle: {:>5}] allowed job {} to start at: {:.2}ms",
//!                     x >= 5, x, time_passed_ms,
//!                 );
//!
//!                 // // you can add some long-running task to see how throttle work
//!                 // std::thread::sleep(Duration::from_millis(40));
//!
//!                 x + 1
//!             })
//!     })
//!     .collect();
//!
//! assert_eq!(all_added_one, vec![2, 3, 4, 5, 6, 7]);
//! ```
//!
//! Output:
//!
//! ```text
//! [throttle: false] allowed job 1 to start at: 0.09ms
//! [throttle:  true] allowed job 6 to start at: 0.10ms
//! [throttle: false] allowed job 4 to start at: 10.40ms
//! [throttle:  true] allowed job 5 to start at: 10.42ms
//! [throttle: false] allowed job 3 to start at: 20.12ms
//! [throttle: false] allowed job 2 to start at: 30.12ms
//! ```
//!
//!
//!
//! # Crate Naming
//!
//! Crate name `slottle` is the abbr of "slotted throttle". Which is the original name of `ThrottlePool`.

mod throttle;
mod throttle_pool;

#[doc(inline)]
pub use throttle::{
    interval::fibonacci, Interval, RetryableResult, Throttle, ThrottleBuilder, ThrottleLog,
};

#[doc(inline)]
pub use throttle_pool::{ThrottlePool, ThrottlePoolBuilder};