@@ -22,66 +22,96 @@ use std::{
2222 num:: NonZeroU8 ,
2323 pin:: Pin ,
2424 task:: { Context , Poll } ,
25+ vec:: IntoIter ,
2526} ;
2627
2728use futures:: {
29+ FutureExt ,
2830 future:: { BoxFuture , Future } ,
2931 ready,
3032 stream:: { FuturesUnordered , StreamExt } ,
3133} ;
34+ use futures_timer:: Delay ;
3235use libp2p_core:: muxing:: StreamMuxerBox ;
3336use libp2p_identity:: PeerId ;
3437
35- use crate :: { Multiaddr , transport:: TransportError } ;
38+ use crate :: { Multiaddr , connection :: pool :: dial_ranker :: rank_dials , transport:: TransportError } ;
3639
37- type Dial = BoxFuture <
40+ /// A pending outbound dial that hasn't started yet.
41+ ///
42+ /// The `fut` is created upfront by `Transport::dial()` but won't be polled
43+ /// until `delay` elapses. If `smart_dial` is enabled, `rank_dials` sets the
44+ /// `delay` and reorders the vec so preferred addresses start first. Without
45+ /// smart dial, delays are `None` and the concurrency factor limits how many
46+ /// are polled at once.
47+ pub ( crate ) struct PendingDial {
48+ pub ( crate ) addr : Multiaddr ,
49+ pub ( crate ) fut : DialFuture ,
50+ }
51+
52+ /// The async dial operation. Owns the actual transport dial and returns the
53+ /// dialed address along with the result so the pool can match failures
54+ /// back to specific addresses.
55+ pub ( crate ) type DialFuture = BoxFuture <
3856 ' static ,
3957 (
4058 Multiaddr ,
4159 Result < ( PeerId , StreamMuxerBox ) , TransportError < std:: io:: Error > > ,
4260 ) ,
4361> ;
4462
63+ /// The result of a concurrent or smart dial to a single peer.
64+ ///
65+ /// Returns the first successful address and its negotiated connection, along
66+ /// with any errors from earlier failed dials. Returns all errors if every
67+ /// dial failed.
68+ pub ( crate ) type DialResult = Result <
69+ (
70+ Multiaddr ,
71+ ( PeerId , StreamMuxerBox ) ,
72+ Vec < ( Multiaddr , TransportError < std:: io:: Error > ) > ,
73+ ) ,
74+ Vec < ( Multiaddr , TransportError < std:: io:: Error > ) > ,
75+ > ;
76+
77+ /// Drives concurrent dial attempts to a single peer, limited by a concurrency factor.
78+ ///
79+ /// Starts up to `concurrency_factor` dials simultaneously. On failure, the next
80+ /// pending dial starts immediately, keeping the concurrency window filled until
81+ /// either a dial succeeds or all pending dials are exhausted.
4582pub ( crate ) struct ConcurrentDial {
46- dials : FuturesUnordered < Dial > ,
47- pending_dials : Box < dyn Iterator < Item = Dial > + Send > ,
83+ dials : FuturesUnordered < DialFuture > ,
84+ pending_dials : IntoIter < PendingDial > ,
4885 errors : Vec < ( Multiaddr , TransportError < std:: io:: Error > ) > ,
4986}
5087
5188impl Unpin for ConcurrentDial { }
5289
5390impl ConcurrentDial {
54- pub ( crate ) fn new ( pending_dials : Vec < Dial > , concurrency_factor : NonZeroU8 ) -> Self {
91+ pub ( crate ) fn new ( pending_dials : Vec < PendingDial > , concurrency_factor : NonZeroU8 ) -> Self {
5592 let mut pending_dials = pending_dials. into_iter ( ) ;
5693
94+ // Fill the concurrency window: start up to `concurrency_factor` dials.
95+ // As each dial completes (success or failure), `dial_pending()` is called
96+ // to refill the window from remaining pending dials.
5797 let dials = FuturesUnordered :: new ( ) ;
58- for dial in pending_dials. by_ref ( ) {
59- dials . push ( dial ) ;
60- if dials . len ( ) == concurrency_factor. get ( ) as usize {
61- break ;
62- }
98+ for dial in pending_dials
99+ . by_ref ( )
100+ . take ( concurrency_factor. get ( ) as usize )
101+ {
102+ dials . push ( dial . fut ) ;
63103 }
64104
65105 Self {
66106 dials,
67107 errors : Default :: default ( ) ,
68- pending_dials : Box :: new ( pending_dials ) ,
108+ pending_dials,
69109 }
70110 }
71111}
72112
73113impl Future for ConcurrentDial {
74- type Output = Result <
75- // Either one dial succeeded, returning the negotiated [`PeerId`], the address, the
76- // muxer and the addresses and errors of the dials that failed before.
77- (
78- Multiaddr ,
79- ( PeerId , StreamMuxerBox ) ,
80- Vec < ( Multiaddr , TransportError < std:: io:: Error > ) > ,
81- ) ,
82- // Or all dials failed, thus returning the address and error for each dial.
83- Vec < ( Multiaddr , TransportError < std:: io:: Error > ) > ,
84- > ;
114+ type Output = DialResult ;
85115
86116 fn poll ( mut self : Pin < & mut Self > , cx : & mut Context ) -> Poll < Self :: Output > {
87117 loop {
@@ -93,8 +123,66 @@ impl Future for ConcurrentDial {
93123 Some ( ( addr, Err ( e) ) ) => {
94124 self . errors . push ( ( addr, e) ) ;
95125 if let Some ( dial) = self . pending_dials . next ( ) {
96- self . dials . push ( dial)
126+ self . dials . push ( dial. fut ) ;
127+ }
128+ }
129+ None => {
130+ return Poll :: Ready ( Err ( std:: mem:: take ( & mut self . errors ) ) ) ;
131+ }
132+ }
133+ }
134+ }
135+ }
136+
137+ /// Drives ranked dial attempts to a single peer with staggered delays.
138+ ///
139+ /// All dials start immediately via [`rank_dials`], which assigns delays based on
140+ /// transport priority (QUIC > TCP, IPv6 > IPv4) and Happy Eyeballs (RFC 8305).
141+ /// The delays pace the dials, giving faster transports a head start while slower
142+ /// paths wait, with all dials overlapping in flight.
143+ pub ( crate ) struct SmartDial {
144+ dials : FuturesUnordered < DialFuture > ,
145+ errors : Vec < ( Multiaddr , TransportError < std:: io:: Error > ) > ,
146+ }
147+
148+ impl Unpin for SmartDial { }
149+
150+ impl SmartDial {
151+ pub ( crate ) fn new ( pending_dials : Vec < PendingDial > ) -> Self {
152+ let pending_dials = rank_dials ( pending_dials) ;
153+
154+ let dials = FuturesUnordered :: new ( ) ;
155+ for ( delay, dial) in pending_dials {
156+ dials. push (
157+ async move {
158+ if !delay. is_zero ( ) {
159+ Delay :: new ( delay) . await ;
97160 }
161+ dial. fut . await
162+ }
163+ . boxed ( ) ,
164+ ) ;
165+ }
166+
167+ Self {
168+ dials,
169+ errors : Default :: default ( ) ,
170+ }
171+ }
172+ }
173+
174+ impl Future for SmartDial {
175+ type Output = DialResult ;
176+
177+ fn poll ( mut self : Pin < & mut Self > , cx : & mut Context ) -> Poll < Self :: Output > {
178+ loop {
179+ match ready ! ( self . dials. poll_next_unpin( cx) ) {
180+ Some ( ( addr, Ok ( output) ) ) => {
181+ let errors = std:: mem:: take ( & mut self . errors ) ;
182+ return Poll :: Ready ( Ok ( ( addr, output, errors) ) ) ;
183+ }
184+ Some ( ( addr, Err ( e) ) ) => {
185+ self . errors . push ( ( addr, e) ) ;
98186 }
99187 None => {
100188 return Poll :: Ready ( Err ( std:: mem:: take ( & mut self . errors ) ) ) ;
0 commit comments