/build/source/nativelink-util/src/connection_manager.rs
Line | Count | Source |
1 | | // Copyright 2024 Trace Machina, Inc. 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 | 51 | pub fn new( |
131 | 51 | endpoints: impl IntoIterator<Item = Endpoint>, |
132 | 51 | mut connections_per_endpoint: usize, |
133 | 51 | mut max_concurrent_requests: usize, |
134 | 51 | retry: Retry, |
135 | 51 | jitter_fn: retry::JitterFn, |
136 | 51 | ) -> Self { |
137 | 51 | 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 | 51 | let (connection_tx, connection_rx) = mpsc::unbounded_channel(); |
143 | 51 | let endpoints = endpoints |
144 | 51 | .into_iter() |
145 | 51 | .map(|endpoint| (0, endpoint)) |
146 | 51 | .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 | 51 | let bounded_connections = max_concurrent_requests != 0; |
152 | 51 | if max_concurrent_requests == 0 { |
153 | 48 | max_concurrent_requests = Semaphore::MAX_PERMITS; |
154 | 48 | } else { |
155 | 3 | max_concurrent_requests = max_concurrent_requests.min(Semaphore::MAX_PERMITS); |
156 | 3 | } |
157 | 51 | if connections_per_endpoint == 0 { |
158 | 48 | connections_per_endpoint = 1; |
159 | 48 | }3 |
160 | 51 | let worker = ConnectionManagerWorker { |
161 | 51 | endpoints, |
162 | 51 | available_connections: Arc::new(Semaphore::new(max_concurrent_requests)), |
163 | 51 | bounded_connections, |
164 | 51 | connection_tx, |
165 | 51 | connecting_channels: FuturesUnordered::new(), |
166 | 51 | available_channels: VecDeque::new(), |
167 | 51 | waiting_connections: VecDeque::new(), |
168 | 51 | retrier: Retrier::new( |
169 | 51 | Arc::new(|duration| Box::pin0 (tokio::time::sleep0 (duration0 ))), |
170 | 51 | jitter_fn, |
171 | 51 | retry, |
172 | | ) |
173 | 51 | .with_client("connection"), |
174 | | }; |
175 | 51 | background_spawn!("connection_manager_worker_spawn", async move {47 |
176 | 47 | worker |
177 | 47 | .service_requests(connections_per_endpoint, worker_rx, connection_rx) |
178 | 47 | .await; |
179 | 7 | }); |
180 | 51 | Self { worker_tx } |
181 | 51 | } |
182 | | |
183 | | /// Get a Connection that can be used as a `tonic::Channel`, except it |
184 | | /// performs some additional counting to reconnect on error and restrict |
185 | | /// the number of concurrent connections. |
186 | 271 | pub async fn connection(&self, reason: String) -> Result<Connection, Error>0 { |
187 | 271 | let (tx, rx) = oneshot::channel(); |
188 | 271 | self.worker_tx |
189 | 271 | .send((reason, tx)) |
190 | 271 | .await |
191 | 271 | .map_err(|err| make_err!0 (Code::Unavailable0 , "Requesting a new connection: {err:?}"))?0 ; |
192 | 271 | rx.await |
193 | 263 | .map_err(|err| make_err!0 (Code::Unavailable0 , "Waiting for a new connection: {err:?}")) |
194 | 263 | } |
195 | | } |
196 | | |
197 | | impl ConnectionManagerWorker { |
198 | 0 | async fn service_requests( |
199 | 0 | mut self, |
200 | 0 | connections_per_endpoint: usize, |
201 | 0 | mut worker_rx: mpsc::Receiver<(String, oneshot::Sender<Connection>)>, |
202 | 0 | mut connection_rx: mpsc::UnboundedReceiver<ConnectionRequest>, |
203 | 47 | ) { |
204 | | // Make the initial set of connections, connection failures will be |
205 | | // handled in the same way as future transport failures, so no need to |
206 | | // do anything special. |
207 | 47 | for endpoint_index in 0..self.endpoints.len() { |
208 | 51 | for _ in 0..connections_per_endpoint47 { |
209 | 51 | self.connect_endpoint(endpoint_index, None); |
210 | 51 | } |
211 | | } |
212 | | |
213 | | // The main worker loop, when select resolves one of its arms the other |
214 | | // ones are cancelled, therefore it's important that they maintain no |
215 | | // state while `await`-ing. This is enforced through the use of |
216 | | // non-async functions to do all of the work. |
217 | | loop { |
218 | 666 | tokio::select! { |
219 | 666 | request278 = worker_rx.recv() => { |
220 | 278 | let Some((reason271 , request271 )) = request else { |
221 | | // The ConnectionManager was dropped, shut down the |
222 | | // worker. |
223 | 7 | break; |
224 | | }; |
225 | 271 | self.handle_worker(reason, request); |
226 | | } |
227 | 666 | maybe_request298 = connection_rx.recv() => { |
228 | 298 | if let Some(request) = maybe_request { |
229 | 298 | self.handle_connection(request); |
230 | 298 | }0 |
231 | | } |
232 | 666 | maybe_connection_result50 = self.connect_next() => { |
233 | 50 | if let Some(connection_result) = maybe_connection_result { |
234 | 50 | self.handle_connected(connection_result); |
235 | 50 | }0 |
236 | | } |
237 | | } |
238 | | } |
239 | 7 | } |
240 | | |
241 | 508 | async fn connect_next(&mut self) -> Option<IndexedChannel>0 { |
242 | 508 | if self.connecting_channels.is_empty() { |
243 | | // Make this Future never resolve, we will get cancelled by the |
244 | | // select if there's some change in state to `self` and can re-enter |
245 | | // and evaluate `connecting_channels` again. |
246 | 415 | futures::future::pending::<()>().await; |
247 | 93 | } |
248 | 93 | self.connecting_channels.next().await |
249 | 50 | } |
250 | | |
251 | | // This must never be made async otherwise the select may cancel it. |
252 | 50 | fn handle_connected(&mut self, connection_result: IndexedChannel) { |
253 | 50 | match connection_result { |
254 | 50 | Ok(established_channel) => { |
255 | 50 | self.available_channels.push_back(established_channel); |
256 | 50 | self.maybe_available_connection(); |
257 | 50 | } |
258 | | // When the retrier runs out of attempts start again from the |
259 | | // beginning of the retry period. Never want to be in a |
260 | | // situation where we give up on an Endpoint forever. |
261 | 0 | Err((identifier, _)) => { |
262 | 0 | self.connect_endpoint(identifier.endpoint_index, Some(identifier.connection_index)); |
263 | 0 | } |
264 | | } |
265 | 50 | } |
266 | | |
267 | 51 | fn connect_endpoint(&mut self, endpoint_index: usize, connection_index: Option<usize>) { |
268 | 51 | let Some((current_connection_index, endpoint)) = self.endpoints.get_mut(endpoint_index) |
269 | | else { |
270 | | // Unknown endpoint, this should never happen. |
271 | 0 | error!(?endpoint_index, "Connection to unknown endpoint requested"); |
272 | 0 | return; |
273 | | }; |
274 | 51 | let is_backoff = connection_index.is_some(); |
275 | 51 | let connection_index = connection_index.unwrap_or_else(|| { |
276 | 51 | *current_connection_index += 1; |
277 | 51 | *current_connection_index |
278 | 51 | }); |
279 | 51 | if is_backoff { |
280 | 0 | warn!( |
281 | | ?connection_index, |
282 | 0 | endpoint = ?endpoint.uri(), |
283 | | "Connection failed, reconnecting" |
284 | | ); |
285 | | } else { |
286 | 51 | info!( |
287 | | ?connection_index, |
288 | 51 | endpoint = ?endpoint.uri(), |
289 | | "Creating new connection" |
290 | | ); |
291 | | } |
292 | 51 | let identifier = ChannelIdentifier { |
293 | 51 | endpoint_index, |
294 | 51 | connection_index, |
295 | 51 | }; |
296 | 51 | let connection_stream = unfold(endpoint.clone(), move |endpoint| async move { |
297 | 51 | let result50 = endpoint.connect().await.map_err50 (|err| {0 |
298 | 0 | make_err!( |
299 | 0 | Code::Unavailable, |
300 | | "Failed to connect to {:?}: {err:?}", |
301 | 0 | endpoint.uri() |
302 | | ) |
303 | 0 | }); |
304 | 50 | Some(( |
305 | 50 | result.map_or_else(RetryResult::Retry, RetryResult::Ok), |
306 | 50 | endpoint, |
307 | 50 | )) |
308 | 101 | }); |
309 | 51 | let retrier = self.retrier.clone(); |
310 | 51 | self.connecting_channels.push(Box::pin(async move { |
311 | 51 | if is_backoff { |
312 | | // Just in case the retry config is 0, then we need to |
313 | | // introduce some delay so we aren't in a hard loop. |
314 | 0 | tokio::time::sleep(Duration::from_secs(1)).await; |
315 | 51 | } |
316 | 51 | retrier.retry(connection_stream).await.map_or_else50 ( |
317 | 0 | |err| Err((identifier, err)), |
318 | 50 | |channel| { |
319 | 50 | Ok(EstablishedChannel { |
320 | 50 | channel, |
321 | 50 | identifier, |
322 | 50 | }) |
323 | 50 | }, |
324 | | ) |
325 | 50 | })); |
326 | 51 | } |
327 | | |
328 | | /// Free slots, or `None` when the pool is unlimited and the permit count |
329 | | /// is a sentinel rather than a measurement. |
330 | 271 | fn free_connection_slots(&self) -> Option<usize> { |
331 | 271 | self.bounded_connections |
332 | 271 | .then(|| self.available_connections215 .available_permits215 ()) |
333 | 271 | } |
334 | | |
335 | | // This must never be made async otherwise the select may cancel it. |
336 | 271 | fn handle_worker(&mut self, reason: String, tx: oneshot::Sender<Connection>) { |
337 | 271 | let maybe_permit = self.available_connections.clone().try_acquire_owned().ok(); |
338 | 271 | if let Some(permit270 ) = maybe_permit |
339 | 270 | && let Some(channel157 ) = self.available_channels.pop_front() |
340 | | { |
341 | 157 | debug!(reason, "ConnectionManager: request running"); |
342 | 157 | record_connection_acquired("grpc", self.free_connection_slots(), false); |
343 | 157 | self.provide_channel(channel, tx, permit); |
344 | | } else { |
345 | 114 | debug!( |
346 | 114 | available_permits = self.available_connections.available_permits(), |
347 | 114 | available_channels = self.available_channels.len(), |
348 | 114 | waiting_connections = self.waiting_connections.len(), |
349 | | reason, |
350 | | "ConnectionManager: no connection available, request queued", |
351 | | ); |
352 | 114 | record_connection_acquired("grpc", self.free_connection_slots(), true); |
353 | 114 | self.waiting_connections.push_back((reason, tx)); |
354 | | } |
355 | 271 | } |
356 | | |
357 | 271 | fn provide_channel( |
358 | 271 | &self, |
359 | 271 | channel: EstablishedChannel, |
360 | 271 | tx: oneshot::Sender<Connection>, |
361 | 271 | permit: OwnedSemaphorePermit, |
362 | 271 | ) { |
363 | 271 | drop(tx.send(Connection { |
364 | 271 | tx: self.connection_tx.clone(), |
365 | 271 | pending_channel: Some(channel.channel.clone()), |
366 | 271 | channel, |
367 | 271 | _permit: permit, |
368 | 271 | })); |
369 | 271 | } |
370 | | |
371 | 348 | fn maybe_available_connection(&mut self) { |
372 | 462 | while !self.waiting_connections.is_empty() && !self.available_channels.is_empty()123 { |
373 | 114 | let Some(permit) = self.available_connections.clone().try_acquire_owned().ok() else { |
374 | 0 | break; |
375 | | }; |
376 | 114 | let Some(channel) = self.available_channels.pop_front() else { |
377 | 0 | drop(permit); |
378 | 0 | break; |
379 | | }; |
380 | 114 | let Some((reason, tx)) = self.waiting_connections.pop_front() else { |
381 | 0 | self.available_channels.push_front(channel); |
382 | 0 | drop(permit); |
383 | 0 | break; |
384 | | }; |
385 | 114 | debug!(reason, "ConnectionManager: channel available, running"); |
386 | 114 | self.provide_channel(channel, tx, permit); |
387 | | } |
388 | 348 | } |
389 | | |
390 | | // This must never be made async otherwise the select may cancel it. |
391 | 298 | fn handle_connection(&mut self, request: ConnectionRequest) { |
392 | 298 | match request { |
393 | 242 | ConnectionRequest::Dropped(maybe_channel) => { |
394 | 242 | if let Some(channel209 ) = maybe_channel { |
395 | 209 | self.available_channels.push_back(channel); |
396 | 209 | }33 |
397 | 242 | self.maybe_available_connection(); |
398 | | } |
399 | 56 | ConnectionRequest::Connected(channel) => { |
400 | 56 | self.available_channels.push_back(channel); |
401 | 56 | self.maybe_available_connection(); |
402 | 56 | } |
403 | | // Handle a transport error on a connection by making it unavailable |
404 | | // for use and establishing a new connection to the endpoint. |
405 | 0 | ConnectionRequest::Error((identifier, was_pending)) => { |
406 | 0 | let should_reconnect = if was_pending { |
407 | 0 | true |
408 | | } else { |
409 | 0 | let original_length = self.available_channels.len(); |
410 | 0 | self.available_channels |
411 | 0 | .retain(|channel| channel.identifier != identifier); |
412 | | // Only reconnect if it wasn't already disconnected. |
413 | 0 | original_length != self.available_channels.len() |
414 | | }; |
415 | 0 | if should_reconnect { |
416 | 0 | self.connect_endpoint(identifier.endpoint_index, None); |
417 | 0 | } |
418 | | } |
419 | | } |
420 | 298 | } |
421 | | } |
422 | | |
423 | | /// An instance of this is obtained for every communication with the gGRPC |
424 | | /// service. This handles the permit for limiting concurrency, and also |
425 | | /// re-connecting the underlying channel on error. It depends on users |
426 | | /// reporting all errors. |
427 | | /// NOTE: This should never be cloneable because its lifetime is linked to the |
428 | | /// semaphore permit it carries — `_permit` is released exactly once, |
429 | | /// when the `Connection` drops. |
430 | | #[derive(Debug)] |
431 | | pub struct Connection { |
432 | | /// Communication with `ConnectionManagerWorker` to inform about transport |
433 | | /// errors and when the Connection is dropped. |
434 | | tx: mpsc::UnboundedSender<ConnectionRequest>, |
435 | | /// If set, the Channel that will be returned to the worker when connection |
436 | | /// completes (success or failure) or when the Connection is dropped if that |
437 | | /// happens before connection completes. |
438 | | pending_channel: Option<Channel>, |
439 | | /// The identifier to send to `tx`. |
440 | | channel: EstablishedChannel, |
441 | | _permit: OwnedSemaphorePermit, |
442 | | } |
443 | | |
444 | | impl Drop for Connection { |
445 | 271 | fn drop(&mut self) { |
446 | 271 | let pending_channel = self |
447 | 271 | .pending_channel |
448 | 271 | .take() |
449 | 271 | .map(|channel| EstablishedChannel { |
450 | 215 | channel, |
451 | 215 | identifier: self.channel.identifier, |
452 | 215 | }); |
453 | 271 | drop(self.tx.send(ConnectionRequest::Dropped(pending_channel))); |
454 | 271 | } |
455 | | } |
456 | | |
457 | | /// A wrapper around the `channel::ResponseFuture` that forwards errors to the `tx`. |
458 | | pub struct ResponseFuture { |
459 | | /// The wrapped future that actually does the work. |
460 | | inner: channel::ResponseFuture, |
461 | | /// Communication with `ConnectionManagerWorker` to inform about transport |
462 | | /// errors. |
463 | | connection_tx: mpsc::UnboundedSender<ConnectionRequest>, |
464 | | /// The identifier to send to `connection_tx` on a transport error. |
465 | | identifier: ChannelIdentifier, |
466 | | } |
467 | | |
468 | | impl core::fmt::Debug for ResponseFuture { |
469 | 0 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
470 | 0 | f.debug_struct("ResponseFuture") |
471 | 0 | .field("inner", &self.inner) |
472 | 0 | .field("connection_tx", &self.connection_tx) |
473 | 0 | .field("identifier", &self.identifier) |
474 | 0 | .finish() |
475 | 0 | } |
476 | | } |
477 | | |
478 | | /// This is mostly copied from `tonic::transport::channel` except it wraps it |
479 | | /// to allow messaging about connection success and failure. |
480 | | impl tonic::codegen::Service<tonic::codegen::http::Request<tonic::body::Body>> for Connection { |
481 | | type Response = tonic::codegen::http::Response<tonic::body::Body>; |
482 | | type Error = tonic::transport::Error; |
483 | | type Future = ResponseFuture; |
484 | | |
485 | 56 | fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { |
486 | 56 | let result = self.channel.channel.poll_ready(cx); |
487 | 56 | if let Poll::Ready(result) = &result { |
488 | 56 | match result { |
489 | | Ok(()) => { |
490 | 56 | if let Some(pending_channel) = self.pending_channel.take() { |
491 | 56 | drop( |
492 | 56 | self.tx |
493 | 56 | .send(ConnectionRequest::Connected(EstablishedChannel { |
494 | 56 | channel: pending_channel, |
495 | 56 | identifier: self.channel.identifier, |
496 | 56 | })), |
497 | 56 | ); |
498 | 56 | }0 |
499 | | } |
500 | 0 | Err(err) => { |
501 | 0 | debug!(?err, "Error while creating connection on channel"); |
502 | 0 | drop(self.tx.send(ConnectionRequest::Error(( |
503 | 0 | self.channel.identifier, |
504 | 0 | self.pending_channel.take().is_some(), |
505 | 0 | )))); |
506 | | } |
507 | | } |
508 | 0 | } |
509 | 56 | result |
510 | 56 | } |
511 | | |
512 | 56 | fn call(&mut self, request: tonic::codegen::http::Request<tonic::body::Body>) -> Self::Future { |
513 | 56 | ResponseFuture { |
514 | 56 | inner: self.channel.channel.call(request), |
515 | 56 | connection_tx: self.tx.clone(), |
516 | 56 | identifier: self.channel.identifier, |
517 | 56 | } |
518 | 56 | } |
519 | | } |
520 | | |
521 | | /// This is mostly copied from `tonic::transport::channel` except it wraps it |
522 | | /// to allow messaging about connection failure. |
523 | | impl Future for ResponseFuture { |
524 | | type Output = |
525 | | Result<tonic::codegen::http::Response<tonic::body::Body>, tonic::transport::Error>; |
526 | | |
527 | 171 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { |
528 | 171 | let result = Pin::new(&mut self.inner).poll(cx); |
529 | 56 | if let Poll::Ready(Err(_)) = &result { |
530 | 0 | drop( |
531 | 0 | self.connection_tx |
532 | 0 | .send(ConnectionRequest::Error((self.identifier, false))), |
533 | 0 | ); |
534 | 171 | } |
535 | 171 | result |
536 | 171 | } |
537 | | } |