@@ -46,6 +46,7 @@ pub(crate) struct ConnectionManager<'a> {
46
46
47
47
maximum_reconnection_backoff : Duration ,
48
48
reconnection_backoff : Duration ,
49
+ maximum_reconnection_attempts : u32 ,
49
50
50
51
state : ConnectionState < ' a > ,
51
52
}
@@ -62,48 +63,57 @@ impl<'a> ConnectionManager<'static> {
62
63
reconnection_backoff : Duration ,
63
64
maximum_reconnection_backoff : Duration ,
64
65
connection_timeout : Duration ,
65
- ) -> ConnectionManager < ' a > {
66
+ maximum_reconnection_attempts : u32 ,
67
+ ) -> Result < ConnectionManager < ' a > , io:: Error > {
66
68
let ( conn_tx, conn_rx) = mpsc:: unbounded ( ) ;
67
69
68
70
// the blocking call here is fine as initially we want to wait the timeout interval (at most) anyway:
69
71
let tcp_stream_res = std:: net:: TcpStream :: connect_timeout ( & address, connection_timeout) ;
70
72
73
+ // we MUST succeed in making initial connection. We don't want to end up in reconnection
74
+ // loop to something we have never managed to connect (and possibly never will)
75
+
71
76
let initial_state = match tcp_stream_res {
72
77
Ok ( stream) => {
73
78
let tokio_stream = tokio:: net:: TcpStream :: from_std ( stream) . unwrap ( ) ;
74
79
debug ! ( "managed to establish initial connection to {}" , address) ;
75
80
ConnectionState :: Writing ( ConnectionWriter :: new ( tokio_stream) )
76
81
}
77
- Err ( e) => {
78
- warn ! ( "failed to establish initial connection to {} within {:?} ({}). Going into reconnection mode" , address, connection_timeout, e) ;
79
- ConnectionState :: Reconnecting ( ConnectionReconnector :: new (
80
- address,
81
- reconnection_backoff,
82
- maximum_reconnection_backoff,
83
- ) )
84
- }
82
+ Err ( err) => return Err ( err) ,
85
83
} ;
86
84
87
- ConnectionManager {
85
+ Ok ( ConnectionManager {
88
86
conn_tx,
89
87
conn_rx,
90
88
address,
91
89
maximum_reconnection_backoff,
92
90
reconnection_backoff,
91
+ maximum_reconnection_attempts,
93
92
state : initial_state,
94
- }
93
+ } )
95
94
}
96
95
97
96
async fn run ( mut self ) {
98
97
while let Some ( msg) = self . conn_rx . next ( ) . await {
99
98
let ( framed_packet, res_ch) = msg;
100
- let res = self . handle_new_packet ( framed_packet ) . await ;
101
- if let Some ( res_ch ) = res_ch {
102
- if let Err ( e ) = res_ch . send ( res ) {
103
- error ! (
104
- "failed to send response on the channel to the caller! - {:? }" ,
105
- e
99
+
100
+ match self . handle_new_packet ( framed_packet ) . await {
101
+ None => {
102
+ warn ! (
103
+ "We reached maximum number of attempts trying to reconnect to { }" ,
104
+ self . address
106
105
) ;
106
+ return ;
107
+ }
108
+ Some ( res) => {
109
+ if let Some ( res_ch) = res_ch {
110
+ if let Err ( e) = res_ch. send ( res) {
111
+ error ! (
112
+ "failed to send response on the channel to the caller! - {:?}" ,
113
+ e
114
+ ) ;
115
+ }
116
+ }
107
117
}
108
118
}
109
119
}
@@ -122,24 +132,29 @@ impl<'a> ConnectionManager<'static> {
122
132
// Possible future TODO: `Framed<...>` is both a Sink and a Stream,
123
133
// so it is possible to read any responses we might receive (it is also duplex, so that could be
124
134
// done while writing packets themselves). But it'd require slight additions to `SphinxCodec`
125
- async fn handle_new_packet ( & mut self , packet : FramedSphinxPacket ) -> io:: Result < ( ) > {
135
+ async fn handle_new_packet ( & mut self , packet : FramedSphinxPacket ) -> Option < io:: Result < ( ) > > {
126
136
// we don't do a match here as it's possible to transition from ConnectionState::Reconnecting to ConnectionState::Writing
127
137
// in this function call. And if that happens, we want to send the packet we have received.
128
138
if let ConnectionState :: Reconnecting ( conn_reconnector) = & mut self . state {
129
139
// do a single poll rather than await for future to completely resolve
130
140
let new_connection = match futures:: poll ( conn_reconnector) . await {
131
141
Poll :: Pending => {
132
142
debug ! ( "The packet is getting dropped - there's nowhere to send it" ) ;
133
- return Err ( io:: Error :: new (
143
+ return Some ( Err ( io:: Error :: new (
134
144
io:: ErrorKind :: BrokenPipe ,
135
145
"connection is broken - reconnection is in progress" ,
136
- ) ) ;
146
+ ) ) ) ;
137
147
}
138
148
Poll :: Ready ( conn) => conn,
139
149
} ;
140
150
141
- debug ! ( "Managed to reconnect to {}!" , self . address) ;
142
- self . state = ConnectionState :: Writing ( ConnectionWriter :: new ( new_connection) ) ;
151
+ match new_connection {
152
+ Ok ( new_conn) => {
153
+ debug ! ( "Managed to reconnect to {}!" , self . address) ;
154
+ self . state = ConnectionState :: Writing ( ConnectionWriter :: new ( new_conn) ) ;
155
+ }
156
+ Err ( _) => return None ,
157
+ }
143
158
}
144
159
145
160
// we must be in writing state if we are here, either by being here from beginning or just
@@ -154,13 +169,14 @@ impl<'a> ConnectionManager<'static> {
154
169
self . address ,
155
170
self . reconnection_backoff ,
156
171
self . maximum_reconnection_backoff ,
172
+ self . maximum_reconnection_attempts ,
157
173
) ) ;
158
- Err ( io:: Error :: new (
174
+ Some ( Err ( io:: Error :: new (
159
175
io:: ErrorKind :: BrokenPipe ,
160
176
"connection is broken - reconnection is in progress" ,
161
- ) )
177
+ ) ) )
162
178
} else {
163
- Ok ( ( ) )
179
+ Some ( Ok ( ( ) ) )
164
180
} ;
165
181
}
166
182
0 commit comments