1use std::collections::HashMap;
9use std::collections::VecDeque;
10use std::marker::PhantomData;
11
12use crate::factory::worker::WorkerProperties;
13use crate::factory::Job;
14use crate::factory::JobKey;
15use crate::factory::WorkerId;
16use crate::ActorProcessingErr;
17use crate::Message;
18use crate::State;
19
20pub trait CustomHashFunction<TKey>: Send + Sync
22where
23 TKey: Send + Sync + 'static,
24{
25 fn hash(&self, key: &TKey, worker_count: usize) -> usize;
27}
28
29#[derive(Debug)]
31pub enum RouteResult<TKey, TMsg>
32where
33 TKey: JobKey,
34 TMsg: Message,
35{
36 Handled,
38 Backlog(Job<TKey, TMsg>),
41 RateLimited(Job<TKey, TMsg>),
48}
49
50pub trait Router<TKey, TMsg>: State
53where
54 TKey: JobKey,
55 TMsg: Message,
56{
57 fn route_message(
69 &mut self,
70 job: Job<TKey, TMsg>,
71 pool_size: usize,
72 worker_hint: Option<WorkerId>,
73 worker_pool: &mut HashMap<WorkerId, WorkerProperties<TKey, TMsg>>,
74 ) -> Result<RouteResult<TKey, TMsg>, ActorProcessingErr>;
75
76 fn choose_target_worker(
95 &mut self,
96 job: &Job<TKey, TMsg>,
97 pool_size: usize,
98 worker_hint: Option<WorkerId>,
99 worker_pool: &HashMap<WorkerId, WorkerProperties<TKey, TMsg>>,
100 ) -> Option<WorkerId>;
101
102 fn is_factory_queueing(&self) -> bool;
105
106 fn on_worker_availability_change(&mut self, _wid: WorkerId, _available: bool) {}
114}
115
116macro_rules! impl_routing_mode {
118 ($routing_mode: ident, $doc:expr) => {
119 #[doc = $doc]
120 #[derive(Debug)]
121 pub struct $routing_mode<TKey, TMsg>
122 where
123 TKey: JobKey,
124 TMsg: Message,
125 {
126 _key: PhantomData<fn() -> TKey>,
127 _msg: PhantomData<fn() -> TMsg>,
128 }
129
130 impl<TKey, TMsg> Default for $routing_mode<TKey, TMsg>
131 where
132 TKey: JobKey,
133 TMsg: Message,
134 {
135 fn default() -> Self {
136 Self {
137 _key: PhantomData,
138 _msg: PhantomData,
139 }
140 }
141 }
142 };
143}
144
145impl_routing_mode! {KeyPersistentRouting, "Factory will select worker by hashing the job's key.
147Workers will have jobs placed into their incoming message queue's"}
148
149impl<TKey, TMsg> Router<TKey, TMsg> for KeyPersistentRouting<TKey, TMsg>
150where
151 TKey: JobKey,
152 TMsg: Message,
153{
154 fn route_message(
155 &mut self,
156 job: Job<TKey, TMsg>,
157 pool_size: usize,
158 worker_hint: Option<WorkerId>,
159 worker_pool: &mut HashMap<WorkerId, WorkerProperties<TKey, TMsg>>,
160 ) -> Result<RouteResult<TKey, TMsg>, ActorProcessingErr> {
161 if let Some(worker) = self
162 .choose_target_worker(&job, pool_size, worker_hint, worker_pool)
163 .and_then(|wid| worker_pool.get_mut(&wid))
164 {
165 worker.enqueue_job(job)?;
166 }
167 Ok(RouteResult::Handled)
168 }
169
170 fn choose_target_worker(
171 &mut self,
172 job: &Job<TKey, TMsg>,
173 pool_size: usize,
174 worker_hint: Option<WorkerId>,
175 _worker_pool: &HashMap<WorkerId, WorkerProperties<TKey, TMsg>>,
176 ) -> Option<WorkerId> {
177 let key =
178 worker_hint.unwrap_or_else(|| crate::factory::hash::hash_with_max(&job.key, pool_size));
179 Some(key)
180 }
181
182 fn is_factory_queueing(&self) -> bool {
183 false
184 }
185}
186
187#[derive(Debug)]
191pub struct QueuerRouting<TKey, TMsg>
192where
193 TKey: JobKey,
194 TMsg: Message,
195{
196 _key: PhantomData<fn() -> TKey>,
197 _msg: PhantomData<fn() -> TMsg>,
198 available_workers: VecDeque<WorkerId>,
200 worker_in_queue: Vec<bool>,
202}
203
204impl<TKey, TMsg> Default for QueuerRouting<TKey, TMsg>
205where
206 TKey: JobKey,
207 TMsg: Message,
208{
209 fn default() -> Self {
210 Self {
211 _key: PhantomData,
212 _msg: PhantomData,
213 available_workers: VecDeque::new(),
214 worker_in_queue: Vec::new(),
215 }
216 }
217}
218
219impl<TKey, TMsg> Router<TKey, TMsg> for QueuerRouting<TKey, TMsg>
220where
221 TKey: JobKey,
222 TMsg: Message,
223{
224 fn route_message(
225 &mut self,
226 job: Job<TKey, TMsg>,
227 pool_size: usize,
228 worker_hint: Option<WorkerId>,
229 worker_pool: &mut HashMap<WorkerId, WorkerProperties<TKey, TMsg>>,
230 ) -> Result<RouteResult<TKey, TMsg>, ActorProcessingErr> {
231 if let Some(worker) = self
232 .choose_target_worker(&job, pool_size, worker_hint, worker_pool)
233 .and_then(|wid| worker_pool.get_mut(&wid))
234 {
235 worker.enqueue_job(job)?;
236 Ok(RouteResult::Handled)
237 } else {
238 Ok(RouteResult::Backlog(job))
239 }
240 }
241
242 fn choose_target_worker(
243 &mut self,
244 _job: &Job<TKey, TMsg>,
245 _pool_size: usize,
246 worker_hint: Option<WorkerId>,
247 worker_pool: &HashMap<WorkerId, WorkerProperties<TKey, TMsg>>,
248 ) -> Option<WorkerId> {
249 if let Some(worker) = worker_hint.and_then(|worker| worker_pool.get(&worker)) {
250 if worker.is_available() {
251 return worker_hint;
252 }
253 }
254 while let Some(wid) = self.available_workers.pop_front() {
256 if wid < self.worker_in_queue.len() {
257 self.worker_in_queue[wid] = false;
258 }
259 if let Some(worker) = worker_pool.get(&wid) {
260 if worker.is_available() {
261 return Some(wid);
262 }
263 }
264 }
266 None
267 }
268
269 fn is_factory_queueing(&self) -> bool {
270 true
271 }
272
273 fn on_worker_availability_change(&mut self, wid: WorkerId, available: bool) {
274 if wid >= self.worker_in_queue.len() {
276 self.worker_in_queue.resize(wid + 1, false);
277 }
278 if available {
279 if !self.worker_in_queue[wid] {
280 self.worker_in_queue[wid] = true;
281 self.available_workers.push_back(wid);
282 }
283 } else {
284 self.worker_in_queue[wid] = false;
286 }
287 }
288}
289
290#[derive(Debug)]
298pub struct StickyQueuerRouting<TKey, TMsg>
299where
300 TKey: JobKey,
301 TMsg: Message,
302{
303 _key: PhantomData<fn() -> TKey>,
304 _msg: PhantomData<fn() -> TMsg>,
305 available_workers: VecDeque<WorkerId>,
307 worker_in_queue: Vec<bool>,
309}
310
311impl<TKey, TMsg> Default for StickyQueuerRouting<TKey, TMsg>
312where
313 TKey: JobKey,
314 TMsg: Message,
315{
316 fn default() -> Self {
317 Self {
318 _key: PhantomData,
319 _msg: PhantomData,
320 available_workers: VecDeque::new(),
321 worker_in_queue: Vec::new(),
322 }
323 }
324}
325
326impl<TKey, TMsg> Router<TKey, TMsg> for StickyQueuerRouting<TKey, TMsg>
327where
328 TKey: JobKey,
329 TMsg: Message,
330{
331 fn route_message(
332 &mut self,
333 job: Job<TKey, TMsg>,
334 pool_size: usize,
335 worker_hint: Option<WorkerId>,
336 worker_pool: &mut HashMap<WorkerId, WorkerProperties<TKey, TMsg>>,
337 ) -> Result<RouteResult<TKey, TMsg>, ActorProcessingErr> {
338 if let Some(worker) = self
339 .choose_target_worker(&job, pool_size, worker_hint, worker_pool)
340 .and_then(|wid| worker_pool.get_mut(&wid))
341 {
342 worker.enqueue_job(job)?;
343 Ok(RouteResult::Handled)
344 } else {
345 Ok(RouteResult::Backlog(job))
346 }
347 }
348
349 fn choose_target_worker(
350 &mut self,
351 job: &Job<TKey, TMsg>,
352 _pool_size: usize,
353 worker_hint: Option<WorkerId>,
354 worker_pool: &HashMap<WorkerId, WorkerProperties<TKey, TMsg>>,
355 ) -> Option<WorkerId> {
356 if let Some(worker) = worker_hint.and_then(|worker| worker_pool.get(&worker)) {
358 if worker.is_processing_key(&job.key) {
359 return worker_hint;
360 }
361 }
362
363 let maybe_worker = worker_pool
364 .iter()
365 .find(|(_, worker)| worker.is_processing_key(&job.key))
366 .map(|(a, _)| *a);
367 if maybe_worker.is_some() {
368 return maybe_worker;
369 }
370
371 if let Some(worker) = worker_hint.and_then(|worker| worker_pool.get(&worker)) {
373 if worker.is_available() {
374 return worker_hint;
375 }
376 }
377
378 while let Some(wid) = self.available_workers.pop_front() {
380 if wid < self.worker_in_queue.len() {
381 self.worker_in_queue[wid] = false;
382 }
383 if let Some(worker) = worker_pool.get(&wid) {
384 if worker.is_available() {
385 return Some(wid);
386 }
387 }
388 }
390 None
391 }
392
393 fn is_factory_queueing(&self) -> bool {
394 true
395 }
396
397 fn on_worker_availability_change(&mut self, wid: WorkerId, available: bool) {
398 if wid >= self.worker_in_queue.len() {
400 self.worker_in_queue.resize(wid + 1, false);
401 }
402 if available {
403 if !self.worker_in_queue[wid] {
404 self.worker_in_queue[wid] = true;
405 self.available_workers.push_back(wid);
406 }
407 } else {
408 self.worker_in_queue[wid] = false;
410 }
411 }
412}
413
414#[derive(Debug)]
419pub struct RoundRobinRouting<TKey, TMsg>
420where
421 TKey: JobKey,
422 TMsg: Message,
423{
424 _key: PhantomData<fn() -> TKey>,
425 _msg: PhantomData<fn() -> TMsg>,
426 last_worker: WorkerId,
427}
428
429impl<TKey, TMsg> Default for RoundRobinRouting<TKey, TMsg>
430where
431 TKey: JobKey,
432 TMsg: Message,
433{
434 fn default() -> Self {
435 Self {
436 _key: PhantomData,
437 _msg: PhantomData,
438 last_worker: 0,
439 }
440 }
441}
442
443impl<TKey, TMsg> Router<TKey, TMsg> for RoundRobinRouting<TKey, TMsg>
444where
445 TKey: JobKey,
446 TMsg: Message,
447{
448 fn route_message(
449 &mut self,
450 job: Job<TKey, TMsg>,
451 pool_size: usize,
452 worker_hint: Option<WorkerId>,
453 worker_pool: &mut HashMap<WorkerId, WorkerProperties<TKey, TMsg>>,
454 ) -> Result<RouteResult<TKey, TMsg>, ActorProcessingErr> {
455 if let Some(worker) = self
456 .choose_target_worker(&job, pool_size, worker_hint, worker_pool)
457 .and_then(|wid| worker_pool.get_mut(&wid))
458 {
459 worker.enqueue_job(job)?;
460 }
461 Ok(RouteResult::Handled)
462 }
463
464 fn choose_target_worker(
465 &mut self,
466 _job: &Job<TKey, TMsg>,
467 pool_size: usize,
468 worker_hint: Option<WorkerId>,
469 worker_pool: &HashMap<WorkerId, WorkerProperties<TKey, TMsg>>,
470 ) -> Option<WorkerId> {
471 if let Some(worker) = worker_hint.and_then(|worker| worker_pool.get(&worker)) {
472 if worker.is_available() {
473 return worker_hint;
474 }
475 }
476
477 let mut key = self.last_worker + 1;
478 if key >= pool_size {
479 key = 0;
480 }
481 self.last_worker = key;
482 Some(key)
483 }
484
485 fn is_factory_queueing(&self) -> bool {
486 false
487 }
488}
489
490#[derive(Debug)]
496pub struct CustomRouting<TKey, TMsg, THasher>
497where
498 TKey: JobKey,
499 TMsg: Message,
500 THasher: CustomHashFunction<TKey>,
501{
502 _key: PhantomData<fn() -> TKey>,
503 _msg: PhantomData<fn() -> TMsg>,
504 hasher: THasher,
505}
506
507impl<TKey, TMsg, THasher> CustomRouting<TKey, TMsg, THasher>
508where
509 TKey: JobKey,
510 TMsg: Message,
511 THasher: CustomHashFunction<TKey>,
512{
513 pub fn new(hasher: THasher) -> Self {
515 Self {
516 _key: PhantomData,
517 _msg: PhantomData,
518 hasher,
519 }
520 }
521}
522
523impl<TKey, TMsg, THasher> Router<TKey, TMsg> for CustomRouting<TKey, TMsg, THasher>
524where
525 TKey: JobKey,
526 TMsg: Message,
527 THasher: CustomHashFunction<TKey> + 'static,
528{
529 fn route_message(
530 &mut self,
531 job: Job<TKey, TMsg>,
532 pool_size: usize,
533 worker_hint: Option<WorkerId>,
534 worker_pool: &mut HashMap<WorkerId, WorkerProperties<TKey, TMsg>>,
535 ) -> Result<RouteResult<TKey, TMsg>, ActorProcessingErr> {
536 if let Some(worker) = self
537 .choose_target_worker(&job, pool_size, worker_hint, worker_pool)
538 .and_then(|wid| worker_pool.get_mut(&wid))
539 {
540 worker.enqueue_job(job)?;
541 }
542 Ok(RouteResult::Handled)
543 }
544
545 fn choose_target_worker(
546 &mut self,
547 job: &Job<TKey, TMsg>,
548 pool_size: usize,
549 _worker_hint: Option<WorkerId>,
550 _worker_pool: &HashMap<WorkerId, WorkerProperties<TKey, TMsg>>,
551 ) -> Option<WorkerId> {
552 let key = self.hasher.hash(&job.key, pool_size);
553 Some(key)
554 }
555
556 fn is_factory_queueing(&self) -> bool {
557 false
558 }
559}