/build/source/nativelink-util/src/connection_manager.rs
Line | Count | Source |
1 | | // Copyright 2024 The NativeLink Authors. All rights reserved. |
2 | | // |
3 | | // Licensed under the Functional Source License, Version 1.1, Apache 2.0 Future License (the "License"); |
4 | | // you may not use this file except in compliance with the License. |
5 | | // You may obtain a copy of the License at |
6 | | // |
7 | | // See LICENSE file for details |
8 | | // |
9 | | // Unless required by applicable law or agreed to in writing, software |
10 | | // distributed under the License is distributed on an "AS IS" BASIS, |
11 | | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | | // See the License for the specific language governing permissions and |
13 | | // limitations under the License. |
14 | | |
15 | | use core::pin::Pin; |
16 | | use core::task::{Context, Poll}; |
17 | | use core::time::Duration; |
18 | | use std::collections::VecDeque; |
19 | | use std::sync::Arc; |
20 | | |
21 | | use futures::Future; |
22 | | use futures::stream::{FuturesUnordered, StreamExt, unfold}; |
23 | | use nativelink_config::stores::Retry; |
24 | | use nativelink_error::{Code, Error, make_err}; |
25 | | use tokio::sync::{OwnedSemaphorePermit, Semaphore, mpsc, oneshot}; |
26 | | use tonic::transport::{Channel, Endpoint, channel}; |
27 | | use tracing::{debug, error, info, warn}; |
28 | | |
29 | | use crate::background_spawn; |
30 | | use crate::metrics::record_connection_acquired; |
31 | | use crate::retry::{self, Retrier, RetryResult}; |
32 | | |
33 | | /// A helper utility that enables management of a suite of connections to an |
34 | | /// upstream gRPC endpoint using Tonic. |
35 | | #[derive(Debug)] |
36 | | pub struct ConnectionManager { |
37 | | // The channel to request connections from the worker. |
38 | | worker_tx: mpsc::Sender<(String, oneshot::Sender<Connection>)>, |
39 | | } |
40 | | |
41 | | /// The index into `ConnectionManagerWorker::endpoints`. |
42 | | type EndpointIndex = usize; |
43 | | /// The identifier for a given connection to a given Endpoint, used to identify |
44 | | /// when a particular connection has failed or becomes available. |
45 | | type ConnectionIndex = usize; |
46 | | |
47 | | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] |
48 | | struct ChannelIdentifier { |
49 | | /// The index into `ConnectionManagerWorker::endpoints` that established this |
50 | | /// Channel. |
51 | | endpoint_index: EndpointIndex, |
52 | | /// A unique identifier for this particular connection to the Endpoint. |
53 | | connection_index: ConnectionIndex, |
54 | | } |
55 | | |
56 | | /// The requests that can be made from a Connection to the |
57 | | /// `ConnectionManagerWorker` such as informing it that it's been dropped or that |
58 | | /// an error occurred. |
59 | | enum ConnectionRequest { |
60 | | /// Notify that a Connection was dropped, if it was dropped while the |
61 | | /// connection was still pending, then return the pending Channel to be |
62 | | /// added back to the available channels. |
63 | | Dropped(Option<EstablishedChannel>), |
64 | | /// Notify that a Connection was established, return the Channel to the |
65 | | /// available channels. |
66 | | Connected(EstablishedChannel), |
67 | | /// Notify that there was a transport error on the given Channel, the bool |
68 | | /// specifies whether the connection was in the process of being established |
69 | | /// or not (i.e. whether it's been returned to available channels yet). |
70 | | Error((ChannelIdentifier, bool)), |
71 | | } |
72 | | |
73 | | /// The result of a Future that connects to a given Endpoint. This is a tuple |
74 | | /// of the index into the `ConnectionManagerWorker::endpoints` that this |
75 | | /// connection is for, the iteration of the connection and the result of the |
76 | | /// connection itself. |
77 | | type IndexedChannel = Result<EstablishedChannel, (ChannelIdentifier, Error)>; |
78 | | |
79 | | /// A channel that has been established to an endpoint with some metadata around |
80 | | /// it to allow identification of the Channel if it errors in order to correctly |
81 | | /// remove it. |
82 | | #[derive(Debug, Clone)] |
83 | | struct EstablishedChannel { |
84 | | /// The Channel itself that the meta data relates to. |
85 | | channel: Channel, |
86 | | /// The identifier of the channel in the worker. |
87 | | identifier: ChannelIdentifier, |
88 | | } |
89 | | |
90 | | /// The context of the worker used to manage all of the connections. This |
91 | | /// handles reconnecting to endpoints on errors and multiple connections to a |
92 | | /// given endpoint. |
93 | | struct ConnectionManagerWorker { |
94 | | /// The endpoints to establish Channels and the identifier of the last |
95 | | /// connection attempt to that endpoint. |
96 | | endpoints: Vec<(ConnectionIndex, Endpoint)>, |
97 | | /// The channel used to communicate between a Connection and the worker. |
98 | | connection_tx: mpsc::UnboundedSender<ConnectionRequest>, |
99 | | /// Gates the maximum number of in-flight `Connection` objects. |
100 | | /// Was an explicit `usize` counter; now an `Arc<Semaphore>` so the |
101 | | /// `OwnedSemaphorePermit` held by each `Connection` releases on |
102 | | /// drop (RAII), instead of relying on a `ConnectionRequest::Dropped` |
103 | | /// round-trip that could be lost on tonic transport errors or task |
104 | | /// aborts. |
105 | | available_connections: Arc<Semaphore>, |
106 | | |
107 | | /// Whether `available_connections` reflects a configured limit. When the |
108 | | /// pool is unlimited its permit count is a sentinel, not a measurement. |
109 | | bounded_connections: bool, |
110 | | /// Channels that are currently being connected. |
111 | | connecting_channels: FuturesUnordered<Pin<Box<dyn Future<Output = IndexedChannel> + Send>>>, |
112 | | /// Connected channels that are available for use. |
113 | | available_channels: VecDeque<EstablishedChannel>, |
114 | | /// Requests for a Channel when available - (reason, request) |
115 | | waiting_connections: VecDeque<(String, oneshot::Sender<Connection>)>, |
116 | | /// The retry configuration for connecting to an Endpoint, on failure will |
117 | | /// restart the retrier after a 1 second delay. |
118 | | retrier: Retrier, |
119 | | } |
120 | | |
121 | | /// The maximum number of queued requests to obtain a connection from the |
122 | | /// worker before applying back pressure to the requestor. It makes sense to |
123 | | /// keep this small since it has to wait for a response anyway. |
124 | | const WORKER_BACKLOG: usize = 8; |
125 | | |
126 | | impl ConnectionManager { |
127 | | /// Create a connection manager that creates a balance list between a given |
128 | | /// set of Endpoints. This will restrict the number of concurrent requests |
129 | | /// and automatically re-connect upon transport error. |
130 | 34 | pub fn new( |
131 | 34 | endpoints: impl IntoIterator<Item = Endpoint>, |
132 | 34 | mut connections_per_endpoint: usize, |
133 | 34 | mut max_concurrent_requests: usize, |
134 | 34 | retry: Retry, |
135 | 34 | jitter_fn: retry::JitterFn, |
136 | 34 | ) -> Self { |
137 | 34 | let (worker_tx, worker_rx) = mpsc::channel(WORKER_BACKLOG); |
138 | | // The connection messages always come from sync contexts (e.g. drop) |
139 | | // and therefore, we'd end up spawning for them if this was bounded |
140 | | // which defeats the object since there would be no backpressure |
141 | | // applied. Therefore it makes sense for this to be unbounded. |
142 | 34 | let (connection_tx, connection_rx) = mpsc::unbounded_channel(); |
143 | 34 | let endpoints = endpoints |
144 | 34 | .into_iter() |
145 | 34 | .map(|endpoint| (0, endpoint)) |
146 | 34 | .collect(); |
147 | | |
148 | | // Zero means unlimited, which becomes a semaphore holding |
149 | | // Semaphore::MAX_PERMITS. That is a sentinel, not a free-slot count, |
150 | | // so remember which case this is and report no figure when unbounded. |
151 | 34 | let bounded_connections = max_concurrent_requests != 0; |
152 | 34 | if max_concurrent_requests == 0 { |
153 | 31 | max_concurrent_requests = Semaphore::MAX_PERMITS; |
154 | 31 | } else { |
155 | 3 | max_concurrent_requests = max_concurrent_requests.min(Semaphore::MAX_PERMITS); |
156 | 3 | } |
157 | 34 | if connections_per_endpoint == 0 { |
158 | 31 | connections_per_endpoint = 1; |
159 | 31 | }3 |
160 | 34 | let worker = ConnectionManagerWorker { |
161 | 34 | endpoints, |
162 | 34 | available_connections: Arc::new(Semaphore::new(max_concurrent_requests)), |
163 | 34 | bounded_connections, |
164 | 34 | connection_tx, |
165 | 34 | connecting_channels: FuturesUnordered::new(), |
166 | 34 | available_channels: VecDeque::new(), |
167 | 34 | waiting_connections: VecDeque::new(), |
168 | 34 | retrier: Retrier::new( |
169 | 34 | Arc::new(|duration| Box::pin0 (tokio::time::sleep0 (duration0 ))), |
170 | 34 | jitter_fn, |
171 | 34 | retry, |
172 | | ), |
173 | | }; |
174 | 34 | background_spawn!("connection_manager_worker_spawn", async move {32 |
175 | 32 | worker |
176 | 32 | .service_requests(connections_per_endpoint, worker_rx, connection_rx) |
177 | 32 | .await; |
178 | 0 | }); |
179 | 34 | Self { worker_tx } |
180 | 34 | } |
181 | | |
182 | | /// Get a Connection that can be used as a `tonic::Channel`, except it |
183 | | /// performs some additional counting to reconnect on error and restrict |
184 | | /// the number of concurrent connections. |
185 | 254 | pub async fn connection(&self, reason: String) -> Result<Connection, Error> { |
186 | 254 | let (tx, rx) = oneshot::channel(); |
187 | 254 | self.worker_tx |
188 | 254 | .send((reason, tx)) |
189 | 254 | .await |
190 | 254 | .map_err(|err| make_err!0 (Code::Unavailable0 , "Requesting a new connection: {err:?}"))?0 ; |
191 | 254 | rx.await |
192 | 246 | .map_err(|err| make_err!0 (Code::Unavailable0 , "Waiting for a new connection: {err:?}")) |
193 | 246 | } |
194 | | } |
195 | | |
196 | | impl ConnectionManagerWorker { |
197 | 32 | async fn service_requests( |
198 | 32 | mut self, |
199 | 32 | connections_per_endpoint: usize, |
200 | 32 | mut worker_rx: mpsc::Receiver<(String, oneshot::Sender<Connection>)>, |
201 | 32 | mut connection_rx: mpsc::UnboundedReceiver<ConnectionRequest>, |
202 | 32 | ) { |
203 | | // Make the initial set of connections, connection failures will be |
204 | | // handled in the same way as future transport failures, so no need to |
205 | | // do anything special. |
206 | 32 | for endpoint_index in 0..self.endpoints.len() { |
207 | 36 | for _ in 0..connections_per_endpoint32 { |
208 | 36 | self.connect_endpoint(endpoint_index, None); |
209 | 36 | } |
210 | | } |
211 | | |
212 | | // The main worker loop, when select resolves one of its arms the other |
213 | | // ones are cancelled, therefore it's important that they maintain no |
214 | | // state while `await`-ing. This is enforced through the use of |
215 | | // non-async functions to do all of the work. |
216 | | loop { |
217 | 597 | tokio::select! { |
218 | 597 | request254 = worker_rx.recv() => { |
219 | 254 | let Some((reason, request)) = request else { |
220 | | // The ConnectionManager was dropped, shut down the |
221 | | // worker. |
222 | 0 | break; |
223 | | }; |
224 | 254 | self.handle_worker(reason, request); |
225 | | } |
226 | 597 | maybe_request275 = connection_rx.recv() => { |
227 | 275 | if let Some(request) = maybe_request { |
228 | 275 | self.handle_connection(request); |
229 | 275 | }0 |
230 | | } |
231 | 597 | maybe_connection_result36 = self.connect_next() => { |
232 | 36 | if let Some(connection_result) = maybe_connection_result { |
233 | 36 | self.handle_connected(connection_result); |
234 | 36 | }0 |
235 | | } |
236 | | } |
237 | | } |
238 | 0 | } |
239 | | |
240 | 597 | async fn connect_next(&mut self) -> Option<IndexedChannel> {430 |
241 | 430 | if self.connecting_channels.is_empty() { |
242 | | // Make this Future never resolve, we will get cancelled by the |
243 | | // select if there's some change in state to `self` and can re-enter |
244 | | // and evaluate `connecting_channels` again. |
245 | 367 | futures::future::pending::<()>().await; |
246 | 63 | } |
247 | 63 | self.connecting_channels.next().await |
248 | 36 | } |
249 | | |
250 | | // This must never be made async otherwise the select may cancel it. |
251 | 36 | fn handle_connected(&mut self, connection_result: IndexedChannel) { |
252 | 36 | match connection_result { |
253 | 36 | Ok(established_channel) => { |
254 | 36 | self.available_channels.push_back(established_channel); |
255 | 36 | self.maybe_available_connection(); |
256 | 36 | } |
257 | | // When the retrier runs out of attempts start again from the |
258 | | // beginning of the retry period. Never want to be in a |
259 | | // situation where we give up on an Endpoint forever. |
260 | 0 | Err((identifier, _)) => { |
261 | 0 | self.connect_endpoint(identifier.endpoint_index, Some(identifier.connection_index)); |
262 | 0 | } |
263 | | } |
264 | 36 | } |
265 | | |
266 | 36 | fn connect_endpoint(&mut self, endpoint_index: usize, connection_index: Option<usize>) { |
267 | 36 | let Some((current_connection_index, endpoint)) = self.endpoints.get_mut(endpoint_index) |
268 | | else { |
269 | | // Unknown endpoint, this should never happen. |
270 | 0 | error!(?endpoint_index, "Connection to unknown endpoint requested"); |
271 | 0 | return; |
272 | | }; |
273 | 36 | let is_backoff = connection_index.is_some(); |
274 | 36 | let connection_index = connection_index.unwrap_or_else(|| { |
275 | 36 | *current_connection_index += 1; |
276 | 36 | *current_connection_index |
277 | 36 | }); |
278 | 36 | if is_backoff { |
279 | 0 | warn!( |
280 | | ?connection_index, |
281 | 0 | endpoint = ?endpoint.uri(), |
282 | | "Connection failed, reconnecting" |
283 | | ); |
284 | | } else { |
285 | 36 | info!( |
286 | | ?connection_index, |
287 | 36 | endpoint = ?endpoint.uri(), |
288 | | "Creating new connection" |
289 | | ); |
290 | | } |
291 | 36 | let identifier = ChannelIdentifier { |
292 | 36 | endpoint_index, |
293 | 36 | connection_index, |
294 | 36 | }; |
295 | 36 | let connection_stream = unfold(endpoint.clone(), move |endpoint| async move { |
296 | 36 | let result = endpoint.connect().await.map_err(|err| {0 |
297 | 0 | make_err!( |
298 | 0 | Code::Unavailable, |
299 | | "Failed to connect to {:?}: {err:?}", |
300 | 0 | endpoint.uri() |
301 | | ) |
302 | 0 | }); |
303 | 36 | Some(( |
304 | 36 | result.map_or_else(RetryResult::Retry, RetryResult::Ok), |
305 | 36 | endpoint, |
306 | 36 | )) |
307 | 72 | }); |
308 | 36 | let retrier = self.retrier.clone(); |
309 | 36 | self.connecting_channels.push(Box::pin(async move { |
310 | 36 | if is_backoff { |
311 | | // Just in case the retry config is 0, then we need to |
312 | | // introduce some delay so we aren't in a hard loop. |
313 | 0 | tokio::time::sleep(Duration::from_secs(1)).await; |
314 | 36 | } |
315 | 36 | retrier.retry(connection_stream).await.map_or_else( |
316 | 0 | |err| Err((identifier, err)), |
317 | 36 | |channel| { |
318 | 36 | Ok(EstablishedChannel { |
319 | 36 | channel, |
320 | 36 | identifier, |
321 | 36 | }) |
322 | 36 | }, |
323 | | ) |
324 | 36 | })); |
325 | 36 | } |
326 | | |
327 | | /// Free slots, or `None` when the pool is unlimited and the permit count |
328 | | /// is a sentinel rather than a measurement. |
329 | 254 | fn free_connection_slots(&self) -> Option<usize> { |
330 | 254 | self.bounded_connections |
331 | 254 | .then(|| self.available_connections215 .available_permits215 ()) |
332 | 254 | } |
333 | | |
334 | | // This must never be made async otherwise the select may cancel it. |
335 | 254 | fn handle_worker(&mut self, reason: String, tx: oneshot::Sender<Connection>) { |
336 | 254 | let maybe_permit = self.available_connections.clone().try_acquire_owned().ok(); |
337 | 254 | if let Some(permit253 ) = maybe_permit |
338 | 253 | && let Some(channel153 ) = self.available_channels.pop_front() |
339 | | { |
340 | 153 | debug!(reason, "ConnectionManager: request running"); |
341 | 153 | record_connection_acquired("grpc", self.free_connection_slots(), false); |
342 | 153 | self.provide_channel(channel, tx, permit); |
343 | | } else { |
344 | 101 | debug!( |
345 | 101 | available_permits = self.available_connections.available_permits(), |
346 | 101 | available_channels = self.available_channels.len(), |
347 | 101 | waiting_connections = self.waiting_connections.len(), |
348 | | reason, |
349 | | "ConnectionManager: no connection available, request queued", |
350 | | ); |
351 | 101 | record_connection_acquired("grpc", self.free_connection_slots(), true); |
352 | 101 | self.waiting_connections.push_back((reason, tx)); |
353 | | } |
354 | 254 | } |
355 | | |
356 | 254 | fn provide_channel( |
357 | 254 | &self, |
358 | 254 | channel: EstablishedChannel, |
359 | 254 | tx: oneshot::Sender<Connection>, |
360 | 254 | permit: OwnedSemaphorePermit, |
361 | 254 | ) { |
362 | 254 | drop(tx.send(Connection { |
363 | 254 | tx: self.connection_tx.clone(), |
364 | 254 | pending_channel: Some(channel.channel.clone()), |
365 | 254 | channel, |
366 | 254 | _permit: permit, |
367 | 254 | })); |
368 | 254 | } |
369 | | |
370 | 311 | fn maybe_available_connection(&mut self) { |
371 | 412 | while !self.waiting_connections.is_empty() && !self.available_channels.is_empty()110 { |
372 | 101 | let Some(permit) = self.available_connections.clone().try_acquire_owned().ok() else { |
373 | 0 | break; |
374 | | }; |
375 | 101 | let Some(channel) = self.available_channels.pop_front() else { |
376 | 0 | drop(permit); |
377 | 0 | break; |
378 | | }; |
379 | 101 | let Some((reason, tx)) = self.waiting_connections.pop_front() else { |
380 | 0 | self.available_channels.push_front(channel); |
381 | 0 | drop(permit); |
382 | 0 | break; |
383 | | }; |
384 | 101 | debug!(reason, "ConnectionManager: channel available, running"); |
385 | 101 | self.provide_channel(channel, tx, permit); |
386 | | } |
387 | 311 | } |
388 | | |
389 | | // This must never be made async otherwise the select may cancel it. |
390 | 275 | fn handle_connection(&mut self, request: ConnectionRequest) { |
391 | 275 | match request { |
392 | 236 | ConnectionRequest::Dropped(maybe_channel) => { |
393 | 236 | if let Some(channel209 ) = maybe_channel { |
394 | 209 | self.available_channels.push_back(channel); |
395 | 209 | }27 |
396 | 236 | self.maybe_available_connection(); |
397 | | } |
398 | 39 | ConnectionRequest::Connected(channel) => { |
399 | 39 | self.available_channels.push_back(channel); |
400 | 39 | self.maybe_available_connection(); |
401 | 39 | } |
402 | | // Handle a transport error on a connection by making it unavailable |
403 | | // for use and establishing a new connection to the endpoint. |
404 | 0 | ConnectionRequest::Error((identifier, was_pending)) => { |
405 | 0 | let should_reconnect = if was_pending { |
406 | 0 | true |
407 | | } else { |
408 | 0 | let original_length = self.available_channels.len(); |
409 | 0 | self.available_channels |
410 | 0 | .retain(|channel| channel.identifier != identifier); |
411 | | // Only reconnect if it wasn't already disconnected. |
412 | 0 | original_length != self.available_channels.len() |
413 | | }; |
414 | 0 | if should_reconnect { |
415 | 0 | self.connect_endpoint(identifier.endpoint_index, None); |
416 | 0 | } |
417 | | } |
418 | | } |
419 | 275 | } |
420 | | } |
421 | | |
422 | | /// An instance of this is obtained for every communication with the gGRPC |
423 | | /// service. This handles the permit for limiting concurrency, and also |
424 | | /// re-connecting the underlying channel on error. It depends on users |
425 | | /// reporting all errors. |
426 | | /// NOTE: This should never be cloneable because its lifetime is linked to the |
427 | | /// semaphore permit it carries — `_permit` is released exactly once, |
428 | | /// when the `Connection` drops. |
429 | | #[derive(Debug)] |
430 | | pub struct Connection { |
431 | | /// Communication with `ConnectionManagerWorker` to inform about transport |
432 | | /// errors and when the Connection is dropped. |
433 | | tx: mpsc::UnboundedSender<ConnectionRequest>, |
434 | | /// If set, the Channel that will be returned to the worker when connection |
435 | | /// completes (success or failure) or when the Connection is dropped if that |
436 | | /// happens before connection completes. |
437 | | pending_channel: Option<Channel>, |
438 | | /// The identifier to send to `tx`. |
439 | | channel: EstablishedChannel, |
440 | | _permit: OwnedSemaphorePermit, |
441 | | } |
442 | | |
443 | | impl Drop for Connection { |
444 | 254 | fn drop(&mut self) { |
445 | 254 | let pending_channel = self |
446 | 254 | .pending_channel |
447 | 254 | .take() |
448 | 254 | .map(|channel| EstablishedChannel { |
449 | 215 | channel, |
450 | 215 | identifier: self.channel.identifier, |
451 | 215 | }); |
452 | 254 | drop(self.tx.send(ConnectionRequest::Dropped(pending_channel))); |
453 | 254 | } |
454 | | } |
455 | | |
456 | | /// A wrapper around the `channel::ResponseFuture` that forwards errors to the `tx`. |
457 | | pub struct ResponseFuture { |
458 | | /// The wrapped future that actually does the work. |
459 | | inner: channel::ResponseFuture, |
460 | | /// Communication with `ConnectionManagerWorker` to inform about transport |
461 | | /// errors. |
462 | | connection_tx: mpsc::UnboundedSender<ConnectionRequest>, |
463 | | /// The identifier to send to `connection_tx` on a transport error. |
464 | | identifier: ChannelIdentifier, |
465 | | } |
466 | | |
467 | | impl core::fmt::Debug for ResponseFuture { |
468 | 0 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
469 | 0 | f.debug_struct("ResponseFuture") |
470 | 0 | .field("inner", &self.inner) |
471 | 0 | .field("connection_tx", &self.connection_tx) |
472 | 0 | .field("identifier", &self.identifier) |
473 | 0 | .finish() |
474 | 0 | } |
475 | | } |
476 | | |
477 | | /// This is mostly copied from `tonic::transport::channel` except it wraps it |
478 | | /// to allow messaging about connection success and failure. |
479 | | impl tonic::codegen::Service<tonic::codegen::http::Request<tonic::body::Body>> for Connection { |
480 | | type Response = tonic::codegen::http::Response<tonic::body::Body>; |
481 | | type Error = tonic::transport::Error; |
482 | | type Future = ResponseFuture; |
483 | | |
484 | 39 | fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { |
485 | 39 | let result = self.channel.channel.poll_ready(cx); |
486 | 39 | if let Poll::Ready(result) = &result { |
487 | 39 | match result { |
488 | | Ok(()) => { |
489 | 39 | if let Some(pending_channel) = self.pending_channel.take() { |
490 | 39 | drop( |
491 | 39 | self.tx |
492 | 39 | .send(ConnectionRequest::Connected(EstablishedChannel { |
493 | 39 | channel: pending_channel, |
494 | 39 | identifier: self.channel.identifier, |
495 | 39 | })), |
496 | 39 | ); |
497 | 39 | }0 |
498 | | } |
499 | 0 | Err(err) => { |
500 | 0 | debug!(?err, "Error while creating connection on channel"); |
501 | 0 | drop(self.tx.send(ConnectionRequest::Error(( |
502 | 0 | self.channel.identifier, |
503 | 0 | self.pending_channel.take().is_some(), |
504 | 0 | )))); |
505 | | } |
506 | | } |
507 | 0 | } |
508 | 39 | result |
509 | 39 | } |
510 | | |
511 | 39 | fn call(&mut self, request: tonic::codegen::http::Request<tonic::body::Body>) -> Self::Future { |
512 | 39 | ResponseFuture { |
513 | 39 | inner: self.channel.channel.call(request), |
514 | 39 | connection_tx: self.tx.clone(), |
515 | 39 | identifier: self.channel.identifier, |
516 | 39 | } |
517 | 39 | } |
518 | | } |
519 | | |
520 | | /// This is mostly copied from `tonic::transport::channel` except it wraps it |
521 | | /// to allow messaging about connection failure. |
522 | | impl Future for ResponseFuture { |
523 | | type Output = |
524 | | Result<tonic::codegen::http::Response<tonic::body::Body>, tonic::transport::Error>; |
525 | | |
526 | 120 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { |
527 | 120 | let result = Pin::new(&mut self.inner).poll(cx); |
528 | 39 | if let Poll::Ready(Err(_)) = &result { |
529 | 0 | drop( |
530 | 0 | self.connection_tx |
531 | 0 | .send(ConnectionRequest::Error((self.identifier, false))), |
532 | 0 | ); |
533 | 120 | } |
534 | 120 | result |
535 | 120 | } |
536 | | } |