1use crate::{
5 global_weight::GlobalWeight, peekable_fused::PeekableFused, slots::SlotReservations,
6 FutureQueueContext,
7};
8use debug_ignore::DebugIgnore;
9use fnv::FnvHashMap;
10use futures_util::{
11 ready,
12 stream::{Fuse, FusedStream, FuturesUnordered},
13 Future, Stream, StreamExt,
14};
15use pin_project_lite::pin_project;
16use std::{
17 borrow::Borrow,
18 collections::VecDeque,
19 fmt,
20 hash::Hash,
21 pin::Pin,
22 task::{Context, Poll},
23};
24
25pin_project! {
26 #[must_use = "streams do nothing unless polled"]
28 pub struct FutureQueueGrouped<St, K>
29 where
30 St: Stream,
31 St::Item: GroupedWeightedFuture,
32 {
33 #[pin]
34 stream: PeekableFused<Fuse<St>>,
35 #[pin]
36 in_progress_queue: PeekableFused<InProgressQueue<St>>,
37 global_weight: GlobalWeight,
38 slots: SlotReservations,
39 group_store: GroupStore<<St::Item as GroupedWeightedFuture>::Q, K, <St::Item as GroupedWeightedFuture>::F>,
40 extra_verify: bool,
41 }
42}
43
44type InProgressQueue<St> = FuturesUnordered<
45 FutureWithGW<
46 <<St as Stream>::Item as GroupedWeightedFuture>::Future,
47 <<St as Stream>::Item as GroupedWeightedFuture>::Q,
48 >,
49>;
50
51impl<St, K> fmt::Debug for FutureQueueGrouped<St, K>
52where
53 St: Stream + fmt::Debug,
54 St::Item: GroupedWeightedFuture,
55 <St::Item as GroupedWeightedFuture>::Future: fmt::Debug,
56 <<St::Item as GroupedWeightedFuture>::Future as Future>::Output: fmt::Debug,
57 K: fmt::Debug,
58 <St::Item as GroupedWeightedFuture>::Q: fmt::Debug,
59{
60 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61 f.debug_struct("FutureQueueGrouped")
62 .field("stream", &self.stream)
63 .field("in_progress_queue", &self.in_progress_queue)
64 .field("global_weight", &self.global_weight)
65 .field("slots", &self.slots)
66 .field("group_store", &self.group_store)
67 .field("extra_verify", &self.extra_verify)
68 .finish()
69 }
70}
71
72impl<St, K> FutureQueueGrouped<St, K>
73where
74 St: Stream,
75 St::Item: GroupedWeightedFuture,
76 <St::Item as GroupedWeightedFuture>::Q: Eq + Hash + fmt::Debug,
77 K: Eq + Hash + fmt::Debug + Borrow<<St::Item as GroupedWeightedFuture>::Q>,
78{
79 pub(super) fn new(
80 stream: St,
81 max_global_weight: usize,
82 id_data: impl IntoIterator<Item = (K, usize)>,
83 ) -> Self {
84 let id_data_store = GroupStore::new(id_data);
85 Self {
86 stream: PeekableFused::new(stream.fuse()),
87 in_progress_queue: PeekableFused::new(FuturesUnordered::new()),
88 global_weight: GlobalWeight::new(max_global_weight),
89 slots: SlotReservations::with_capacity(max_global_weight),
90 group_store: id_data_store,
91 extra_verify: false,
92 }
93 }
94
95 #[doc(hidden)]
97 pub fn set_extra_verify(&mut self, verify: bool) {
98 self.extra_verify = verify;
99 self.slots.set_check_reserved(verify);
100 for data in self.group_store.group_data.values_mut() {
101 data.slots.set_check_reserved(verify);
102 }
103 }
104
105 pub fn max_global_weight(&self) -> usize {
107 self.global_weight.max()
108 }
109
110 pub fn current_global_weight(&self) -> usize {
112 self.global_weight.current()
113 }
114
115 pub fn max_group_weight<Q>(&self, id: &Q) -> Option<usize>
117 where
118 Q: Eq + Hash + fmt::Debug + ?Sized,
119 K: Borrow<Q>,
120 {
121 self.group_store
122 .group_data
123 .get(id)
124 .map(|id_data| id_data.max_weight)
125 }
126
127 pub fn current_group_weight<Q>(&self, id: &Q) -> Option<usize>
129 where
130 Q: Eq + Hash + fmt::Debug + ?Sized,
131 K: Borrow<Q>,
132 {
133 self.group_store
134 .group_data
135 .get(id)
136 .map(|id_data| id_data.current_weight)
137 }
138
139 pub fn get_ref(&self) -> &St {
142 self.stream.get_ref().get_ref()
143 }
144
145 pub fn get_mut(&mut self) -> &mut St {
151 self.stream.get_mut().get_mut()
152 }
153
154 pub fn get_pin_mut(self: Pin<&mut Self>) -> core::pin::Pin<&mut St> {
160 self.project().stream.get_pin_mut().get_pin_mut()
161 }
162
163 pub fn into_inner(self) -> St {
168 self.stream.into_inner().into_inner()
169 }
170
171 fn verify_no_startable_queue_heads(&self) {
177 for (id, data) in &self.group_store.group_data {
178 if let Some(&(weight, _, _)) = data.queued.front() {
179 assert!(
180 !data.can_start(weight, &self.global_weight),
181 "for group {id:?}, the queued future at the head (weight {weight}) fits \
182 within both the global weight ({}/{}) and the group weight ({}/{}), \
183 but was not started",
184 self.global_weight.current(),
185 self.global_weight.max(),
186 data.current_weight,
187 data.max_weight,
188 );
189 }
190 }
191 }
192
193 #[allow(clippy::type_complexity)]
195 fn poll_pop_in_progress(
196 self: Pin<&mut Self>,
197 cx: &mut Context<'_>,
198 ) -> Poll<(
199 Option<<<St::Item as GroupedWeightedFuture>::Future as Future>::Output>,
200 bool,
201 )> {
202 let mut this = self.project();
203
204 match ready!(this.in_progress_queue.poll_next_unpin(cx)) {
205 Some((weight, global_slot, id_and_group_slot, output)) => {
206 this.global_weight.sub_weight(weight);
207 this.slots.release(global_slot);
208
209 let mut any_queued = false;
210 let in_progress_queue = this.in_progress_queue.get_ref();
211 let mut start_queued = |data: &mut GroupData<_, _>| {
212 start_queued_futures(data, this.global_weight, this.slots, in_progress_queue)
213 };
214
215 if let Some((id, group_slot)) = id_and_group_slot {
216 let data = this.group_store.get_id_mut_or_unwrap(&id);
217 data.sub_weight(&id, weight);
218 data.slots.release(group_slot);
219
220 any_queued |= start_queued(data);
228 }
229
230 for data in this.group_store.group_data.values_mut() {
232 any_queued |= start_queued(data);
233 }
234
235 Poll::Ready((Some(output), any_queued))
236 }
237 None => Poll::Ready((None, false)),
238 }
239 }
240}
241
242impl<St, K> FutureQueueGrouped<St, K>
243where
244 St: Stream,
245 St::Item: GroupedWeightedFuture,
246 <St::Item as GroupedWeightedFuture>::Q: Eq + Hash + fmt::Debug,
247 K: Eq + Hash + fmt::Debug + Borrow<<St::Item as GroupedWeightedFuture>::Q>,
248{
249 fn poll_next_impl(
250 mut self: Pin<&mut Self>,
251 cx: &mut Context<'_>,
252 ) -> Poll<Option<<<St::Item as GroupedWeightedFuture>::Future as Future>::Output>> {
253 let (return_output, mut any_queued) = match self.as_mut().poll_pop_in_progress(cx) {
257 Poll::Ready(ret) => ret,
258 Poll::Pending => (None, false),
259 };
260
261 let mut this = self.as_mut().project();
262
263 while let Poll::Ready(Some(weighted_future)) = this.stream.as_mut().poll_peek(cx) {
267 let weight = weighted_future.weight();
268 if !this.global_weight.has_space_for(weight) {
269 break;
272 }
273 let (weight, id, future_fn) = match this.stream.as_mut().poll_next(cx) {
278 Poll::Ready(Some(weighted_future)) => weighted_future.into_components(),
279 _ => unreachable!("we just peeked at this item"),
280 };
281
282 if let Some(id) = id {
283 let data = this.group_store.get_id_mut_or_unwrap(&id);
285 if data.has_space_for(weight) {
286 start_grouped_future(
287 weight,
288 id,
289 future_fn,
290 data,
291 this.global_weight,
292 this.slots,
293 this.in_progress_queue.get_ref(),
294 );
295 any_queued = true;
296 } else {
297 data.queued.push_back((weight, id, DebugIgnore(future_fn)));
298 }
299 } else {
300 this.global_weight.add_weight(weight);
302
303 let global_slot = this.slots.reserve();
304 let cx = FutureQueueContext {
305 global_slot,
306 group_slot: None,
307 };
308 let future = future_fn(cx);
309
310 this.in_progress_queue.get_ref().push(FutureWithGW::new(
311 weight,
312 global_slot,
313 None,
314 future,
315 ));
316 any_queued = true;
317 }
318 }
319
320 if any_queued {
321 let _ = this.in_progress_queue.as_mut().poll_peek(cx);
324 }
325
326 if let Some(output) = return_output {
327 Poll::Ready(Some(output))
329 } else {
330 match (
331 self.stream.is_done(),
332 self.in_progress_queue.is_terminated(),
333 ) {
334 (true, true) => {
335 debug_assert_eq!(
338 self.group_store.num_queued_futures(),
339 0,
340 "no futures should be left in any group queue"
341 );
342 Poll::Ready(None)
343 }
344 (false, true) => {
345 Poll::Pending
349 }
350 (_, false) => {
351 let (output, any_queued) = ready!(self.as_mut().poll_pop_in_progress(cx));
354 if any_queued {
355 let this = self.project();
357 let _ = this.in_progress_queue.poll_peek(cx);
358 }
359 Poll::Ready(output)
360 }
361 }
362 }
363 }
364}
365
366impl<St, K> Stream for FutureQueueGrouped<St, K>
367where
368 St: Stream,
369 St::Item: GroupedWeightedFuture,
370 <St::Item as GroupedWeightedFuture>::Q: Eq + Hash + fmt::Debug,
371 K: Eq + Hash + fmt::Debug + Borrow<<St::Item as GroupedWeightedFuture>::Q>,
372{
373 type Item = <<St::Item as GroupedWeightedFuture>::Future as Future>::Output;
374
375 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
376 let ret = self.as_mut().poll_next_impl(cx);
377 if self.extra_verify {
378 self.verify_no_startable_queue_heads();
379 }
380 ret
381 }
382
383 fn size_hint(&self) -> (usize, Option<usize>) {
384 let queue_len =
386 self.in_progress_queue.size_hint().0 + self.group_store.num_queued_futures();
387 let (lower, upper) = self.stream.size_hint();
388 let lower = lower.saturating_add(queue_len);
389 let upper = match upper {
390 Some(x) => x.checked_add(queue_len),
391 None => None,
392 };
393 (lower, upper)
394 }
395}
396
397fn start_queued_futures<Q, F, Fut>(
401 data: &mut GroupData<Q, F>,
402 global_weight: &mut GlobalWeight,
403 slots: &mut SlotReservations,
404 in_progress_queue: &FuturesUnordered<FutureWithGW<Fut, Q>>,
405) -> bool
406where
407 Q: fmt::Debug,
408 F: FnOnce(FutureQueueContext) -> Fut,
409{
410 let mut any_started = false;
411 while let Some(&(weight, _, _)) = data.queued.front() {
412 if !data.can_start(weight, global_weight) {
413 break;
414 }
415
416 let (weight, id, future_fn) = data
417 .queued
418 .pop_front()
419 .expect("front() returned Some, so pop_front() returns Some");
420 start_grouped_future(
421 weight,
422 id,
423 future_fn.0,
424 data,
425 global_weight,
426 slots,
427 in_progress_queue,
428 );
429 any_started = true;
430 }
431 any_started
432}
433
434fn start_grouped_future<Q, F, Fut>(
439 weight: usize,
440 id: Q,
441 future_fn: F,
442 data: &mut GroupData<Q, F>,
443 global_weight: &mut GlobalWeight,
444 slots: &mut SlotReservations,
445 in_progress_queue: &FuturesUnordered<FutureWithGW<Fut, Q>>,
446) where
447 Q: fmt::Debug,
448 F: FnOnce(FutureQueueContext) -> Fut,
449{
450 global_weight.add_weight(weight);
451 data.add_weight(&id, weight);
452
453 let global_slot = slots.reserve();
454 let group_slot = data.slots.reserve();
455
456 let cx = FutureQueueContext {
457 global_slot,
458 group_slot: Some(group_slot),
459 };
460 let future = future_fn(cx);
461
462 in_progress_queue.push(FutureWithGW::new(
463 weight,
464 global_slot,
465 Some((id, group_slot)),
466 future,
467 ));
468}
469
470struct GroupStore<Q, K, F> {
471 group_data: FnvHashMap<K, GroupData<Q, F>>,
472}
473
474impl<Q: fmt::Debug, K: fmt::Debug, F> fmt::Debug for GroupStore<Q, K, F> {
475 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
476 f.debug_struct("GroupStore")
477 .field("group_data", &self.group_data)
478 .finish()
479 }
480}
481
482impl<Q, K, F> GroupStore<Q, K, F>
483where
484 Q: Hash + Eq + fmt::Debug,
485 K: Eq + Hash + fmt::Debug + Borrow<Q>,
486{
487 fn new(ids: impl IntoIterator<Item = (K, usize)>) -> Self {
488 let id_data = ids
489 .into_iter()
490 .map(|(id, weight)| {
491 let data = GroupData {
492 current_weight: 0,
493 max_weight: weight,
494 slots: SlotReservations::with_capacity(weight),
495 queued: VecDeque::new(),
496 };
497 (id, data)
498 })
499 .collect();
500
501 Self {
502 group_data: id_data,
503 }
504 }
505
506 fn get_id_mut_or_unwrap(&mut self, id: &Q) -> &mut GroupData<Q, F> {
507 if self.group_data.contains_key(id) {
508 self.group_data.get_mut(id).unwrap()
512 } else {
513 panic!(
514 "unknown semaphore ID: {:?} (known IDs: {:?})",
515 id,
516 self.group_data.keys()
517 );
518 }
519 }
520
521 fn num_queued_futures(&self) -> usize {
522 self.group_data.values().map(|data| data.queued.len()).sum()
523 }
524}
525
526struct GroupData<Q, F> {
527 current_weight: usize,
528 max_weight: usize,
529 slots: SlotReservations,
530 queued: VecDeque<(usize, Q, DebugIgnore<F>)>,
531}
532
533impl<Q: fmt::Debug, F> fmt::Debug for GroupData<Q, F> {
534 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
535 f.debug_struct("GroupData")
536 .field("current_weight", &self.current_weight)
537 .field("max_weight", &self.max_weight)
538 .field("slots", &self.slots)
539 .field("queued", &self.queued)
540 .finish()
541 }
542}
543
544impl<Q: fmt::Debug, Fut> GroupData<Q, Fut> {
545 fn has_space_for(&self, weight: usize) -> bool {
546 let weight = weight.min(self.max_weight);
547 self.current_weight <= self.max_weight - weight
548 }
549
550 fn can_start(&self, weight: usize, global_weight: &GlobalWeight) -> bool {
551 global_weight.has_space_for(weight) && self.has_space_for(weight)
552 }
553
554 fn add_weight(&mut self, id: &Q, weight: usize) {
556 let weight = weight.min(self.max_weight);
557 self.current_weight = self.current_weight.checked_add(weight).unwrap_or_else(|| {
558 panic!(
559 "future_queue_grouped: for id `{:?}`, added weight {} to current {}, overflowed",
560 id, weight, self.current_weight,
561 )
562 });
563 }
564
565 fn sub_weight(&mut self, id: &Q, weight: usize) {
566 let weight = weight.min(self.max_weight);
567 self.current_weight = self.current_weight.checked_sub(weight).unwrap_or_else(|| {
568 panic!(
569 "future_queue_grouped: for id `{:?}`, sub weight {} from current {}, underflowed",
570 id, weight, self.current_weight,
571 )
572 });
573 }
574}
575
576pin_project! {
577 #[must_use = "futures do nothing unless polled"]
578 struct FutureWithGW<Fut, Q> {
579 #[pin]
580 future: Fut,
581 weight: usize,
582 global_slot: u64,
583 id_and_group_slot: Option<(Q, u64)>,
585 }
586}
587
588impl<Fut, Q> FutureWithGW<Fut, Q> {
589 pub fn new(
590 weight: usize,
591 global_slot: u64,
592 id_and_group_slot: Option<(Q, u64)>,
593 future: Fut,
594 ) -> Self {
595 Self {
596 future,
597 weight,
598 global_slot,
599 id_and_group_slot,
600 }
601 }
602}
603
604impl<Fut, Q> Future for FutureWithGW<Fut, Q>
605where
606 Fut: Future,
607{
608 type Output = (usize, u64, Option<(Q, u64)>, Fut::Output);
609 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
610 let this = self.project();
611
612 match this.future.poll(cx) {
613 Poll::Pending => Poll::Pending,
614 Poll::Ready(output) => Poll::Ready((
615 *this.weight,
616 *this.global_slot,
617 this.id_and_group_slot.take(),
618 output,
619 )),
620 }
621 }
622}
623
624pub trait GroupedWeightedFuture: private::Sealed {
629 type F: FnOnce(FutureQueueContext) -> Self::Future;
631
632 type Future: Future;
634
635 type Q;
637
638 fn weight(&self) -> usize;
640
641 fn into_components(self) -> (usize, Option<Self::Q>, Self::F);
643}
644
645impl<F, Fut, Q> private::Sealed for (usize, Option<Q>, F)
646where
647 F: FnOnce(FutureQueueContext) -> Fut,
648 Fut: Future,
649{
650}
651
652impl<F, Fut, Q> GroupedWeightedFuture for (usize, Option<Q>, F)
653where
654 F: FnOnce(FutureQueueContext) -> Fut,
655 Fut: Future,
656{
657 type F = F;
658 type Future = Fut;
659 type Q = Q;
660
661 #[inline]
662 fn weight(&self) -> usize {
663 self.0
664 }
665
666 #[inline]
667 fn into_components(self) -> (usize, Option<Self::Q>, Self::F) {
668 self
669 }
670}
671
672pub(crate) mod private {
673 pub trait Sealed {}
674}