├── .github ├── CODEOWNERS ├── dco.yml ├── dependabot.yml └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── deny.toml └── src ├── lib.rs └── retain.rs /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @linkerd/proxy-maintainers 2 | -------------------------------------------------------------------------------- /.github/dco.yml: -------------------------------------------------------------------------------- 1 | require: 2 | members: false 3 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: / 5 | schedule: 6 | interval: daily 7 | 8 | - package-ecosystem: github-actions 9 | directory: "/" 10 | schedule: 11 | interval: daily 12 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust PR 2 | 3 | on: pull_request 4 | 5 | env: 6 | CARGO_INCREMENTAL: 0 7 | CARGO_NET_RETRY: 10 8 | RUSTFLAGS: "-D warnings -A deprecated" 9 | RUSTUP_MAX_RETRIES: 10 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.head_ref }} 13 | cancel-in-progress: true 14 | 15 | permissions: 16 | contents: read 17 | 18 | jobs: 19 | check: 20 | timeout-minutes: 5 21 | runs-on: ubuntu-latest 22 | container: docker://ghcr.io/linkerd/dev:v45-rust 23 | steps: 24 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 25 | - uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 26 | - run: cargo fetch 27 | - run: cargo fmt -- --check 28 | - run: cargo deny --all-features check bans licenses sources 29 | - run: cargo deny --all-features check advisories 30 | continue-on-error: true 31 | - run: cargo clippy --all-targets --all-features --locked 32 | - run: cargo nextest run --no-run 33 | - run: cargo nextest run 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "drain" 3 | version = "0.2.1" 4 | authors = ["Linkerd Developers "] 5 | license = "Apache-2.0" 6 | edition = "2018" 7 | readme = "README.md" 8 | repository = "https://github.com/linkerd/drain-rs" 9 | description = """ 10 | A crate that supports graceful shutdown 11 | """ 12 | 13 | [features] 14 | default = [] 15 | retain = ["dep:tower-layer", "dep:tower-service"] 16 | 17 | [dependencies] 18 | tokio = { version = "1", features = ["macros", "sync"] } 19 | tower-layer = { version = "0.3.3", default-features = false, optional = true } 20 | tower-service = { version = "0.3.3", default-features = false, optional = true } 21 | 22 | [dev-dependencies] 23 | futures = { version = "0.3.15", default-features = false } 24 | pin-project-lite = "0.2" 25 | tokio-test = "0.4" 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # drain-rs 2 | 3 | A crate that supports graceful shutdown. 4 | -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | [graph] 2 | targets = [ 3 | { triple = "x86_64-unknown-linux-gnu" }, 4 | { triple = "aarch64-unknown-linux-gnu" }, 5 | { triple = "armv7-unknown-linux-gnu" }, 6 | ] 7 | 8 | [advisories] 9 | db-path = "~/.cargo/advisory-db" 10 | db-urls = ["https://github.com/rustsec/advisory-db"] 11 | yanked = "deny" 12 | ignore = [] 13 | 14 | [licenses] 15 | allow = [ 16 | "Apache-2.0", 17 | "MIT", 18 | "Unicode-3.0", 19 | ] 20 | confidence-threshold = 0.8 21 | exceptions = [] 22 | 23 | [bans] 24 | multiple-versions = "deny" 25 | # Wildcard dependencies are used for all workspace-local crates. 26 | wildcards = "allow" 27 | highlight = "all" 28 | deny = [] 29 | skip = [] 30 | skip-tree = [] 31 | 32 | [sources] 33 | unknown-registry = "deny" 34 | unknown-git = "deny" 35 | allow-registry = ["https://github.com/rust-lang/crates.io-index"] 36 | allow-git = [] 37 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings, rust_2018_idioms)] 2 | #![forbid(unsafe_code)] 3 | 4 | #[cfg(feature = "retain")] 5 | mod retain; 6 | 7 | #[cfg(feature = "retain")] 8 | pub use crate::retain::Retain; 9 | use std::future::Future; 10 | use tokio::sync::{mpsc, watch}; 11 | 12 | /// Creates a drain channel. 13 | /// 14 | /// The `Signal` is used to start a drain, and the `Watch` will be notified 15 | /// when a drain is signaled. 16 | pub fn channel() -> (Signal, Watch) { 17 | let (signal_tx, signal_rx) = watch::channel(()); 18 | let (drained_tx, drained_rx) = mpsc::channel(1); 19 | 20 | let signal = Signal { 21 | drained_rx, 22 | signal_tx, 23 | }; 24 | let watch = Watch { 25 | drained_tx, 26 | signal_rx, 27 | }; 28 | (signal, watch) 29 | } 30 | 31 | enum Never {} 32 | 33 | /// Send a drain command to all watchers. 34 | pub struct Signal { 35 | drained_rx: mpsc::Receiver, 36 | signal_tx: watch::Sender<()>, 37 | } 38 | 39 | /// Watch for a drain command. 40 | /// 41 | /// All `Watch` instances must be dropped for a `Signal::signal` call to 42 | /// complete. 43 | #[derive(Clone)] 44 | pub struct Watch { 45 | drained_tx: mpsc::Sender, 46 | signal_rx: watch::Receiver<()>, 47 | } 48 | 49 | #[must_use = "ReleaseShutdown should be dropped explicitly to release the runtime"] 50 | #[derive(Clone)] 51 | pub struct ReleaseShutdown(mpsc::Sender); 52 | 53 | // === impl Signal === 54 | 55 | impl Signal { 56 | /// Waits for all [`Watch`] instances to be dropped. 57 | pub async fn closed(&mut self) { 58 | self.signal_tx.closed().await; 59 | } 60 | 61 | /// Asynchronously signals all watchers to begin draining and waits for all 62 | /// handles to be dropped. 63 | pub async fn drain(mut self) { 64 | // Update the state of the signal watch so that all watchers are observe 65 | // the change. 66 | let _ = self.signal_tx.send(()); 67 | 68 | // Wait for all watchers to release their drain handle. 69 | match self.drained_rx.recv().await { 70 | None => {} 71 | Some(n) => match n {}, 72 | } 73 | } 74 | } 75 | 76 | impl std::fmt::Debug for Signal { 77 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 78 | f.debug_struct("Signal").finish_non_exhaustive() 79 | } 80 | } 81 | 82 | // === impl Watch === 83 | 84 | impl Watch { 85 | /// Returns a `ReleaseShutdown` handle after the drain has been signaled. The 86 | /// handle must be dropped when a shutdown action has been completed to 87 | /// unblock graceful shutdown. 88 | pub async fn signaled(mut self) -> ReleaseShutdown { 89 | // This future completes once `Signal::signal` has been invoked so that 90 | // the channel's state is updated. 91 | let _ = self.signal_rx.changed().await; 92 | 93 | // Return a handle that holds the drain channel, so that the signal task 94 | // is only notified when all handles have been dropped. 95 | ReleaseShutdown(self.drained_tx) 96 | } 97 | 98 | /// Return a `ReleaseShutdown` handle immediately, ignoring the release signal. 99 | /// 100 | /// This is intended to allow a task to block shutdown until it completes. 101 | pub fn ignore_signaled(self) -> ReleaseShutdown { 102 | drop(self.signal_rx); 103 | ReleaseShutdown(self.drained_tx) 104 | } 105 | 106 | /// Wrap a future and a callback that is triggered when drain is received. 107 | /// 108 | /// The callback receives a mutable reference to the original future, and 109 | /// should be used to trigger any shutdown process for it. 110 | pub async fn watch(self, mut future: A, on_drain: F) -> A::Output 111 | where 112 | A: Future + Unpin, 113 | F: FnOnce(&mut A), 114 | { 115 | tokio::select! { 116 | res = &mut future => res, 117 | shutdown = self.signaled() => { 118 | on_drain(&mut future); 119 | shutdown.release_after(future).await 120 | } 121 | } 122 | } 123 | } 124 | 125 | impl std::fmt::Debug for Watch { 126 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 127 | f.debug_struct("Watch").finish_non_exhaustive() 128 | } 129 | } 130 | 131 | impl ReleaseShutdown { 132 | /// Releases shutdown after `future` completes. 133 | pub async fn release_after(self, future: F) -> F::Output { 134 | let res = future.await; 135 | drop(self.0); 136 | res 137 | } 138 | } 139 | impl std::fmt::Debug for ReleaseShutdown { 140 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 141 | f.debug_struct("ReleaseShutdown").finish_non_exhaustive() 142 | } 143 | } 144 | 145 | #[cfg(test)] 146 | mod tests { 147 | use std::{ 148 | future::Future, 149 | pin::Pin, 150 | sync::{ 151 | atomic::{AtomicBool, Ordering::SeqCst}, 152 | Arc, 153 | }, 154 | task::{Context, Poll}, 155 | }; 156 | use tokio::sync::oneshot; 157 | use tokio_test::{assert_pending, assert_ready, task}; 158 | 159 | pin_project_lite::pin_project! { 160 | struct Fut { 161 | notified: Arc, 162 | #[pin] 163 | inner: oneshot::Receiver<()>, 164 | } 165 | } 166 | 167 | impl Fut { 168 | pub fn new() -> (Self, oneshot::Sender<()>, Arc) { 169 | let notified = Arc::new(AtomicBool::new(false)); 170 | let (tx, rx) = oneshot::channel::<()>(); 171 | let fut = Fut { 172 | notified: notified.clone(), 173 | inner: rx, 174 | }; 175 | (fut, tx, notified) 176 | } 177 | } 178 | 179 | impl Future for Fut { 180 | type Output = (); 181 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { 182 | let this = self.project(); 183 | let _ = futures::ready!(this.inner.poll(cx)); 184 | Poll::Ready(()) 185 | } 186 | } 187 | 188 | #[tokio::test] 189 | async fn watch() { 190 | let (signal, watch) = super::channel(); 191 | 192 | // Setup a future to be drained. When draining begins, `drained0` is 193 | // flipped . When `tx0` fires, the whole `watch0` future completes. 194 | let (fut0, tx0, notified0) = Fut::new(); 195 | let mut watch0 = task::spawn( 196 | watch 197 | .clone() 198 | .watch(fut0, |f| f.notified.store(true, SeqCst)), 199 | ); 200 | 201 | // Setup another future to be drained. 202 | let (fut1, tx1, notified1) = Fut::new(); 203 | let mut watch1 = task::spawn(watch.watch(fut1, |f| f.notified.store(true, SeqCst))); 204 | 205 | assert_pending!(watch0.poll()); 206 | assert_pending!(watch1.poll()); 207 | assert!(!notified0.load(SeqCst)); 208 | assert!(!notified1.load(SeqCst)); 209 | 210 | // Signal draining and ensure that none of the futures have completed. 211 | let mut drain = task::spawn(signal.drain()); 212 | 213 | assert_pending!(drain.poll()); 214 | // Verify that the draining callbacks were invoked. 215 | assert_pending!(watch0.poll()); 216 | assert!(notified0.load(SeqCst)); 217 | assert_pending!(watch1.poll()); 218 | assert!(notified1.load(SeqCst)); 219 | 220 | // Complete the first watch. 221 | tx0.send(()).expect("must send"); 222 | assert_ready!(watch0.poll()); 223 | assert_pending!(watch1.poll()); 224 | assert_pending!(drain.poll()); 225 | 226 | // Complete the second watch. 227 | tx1.send(()).expect("must send"); 228 | assert_ready!(watch1.poll()); 229 | 230 | assert_ready!(drain.poll()); 231 | } 232 | 233 | #[tokio::test] 234 | async fn drain() { 235 | let (signal, watch) = super::channel(); 236 | let mut signaled = task::spawn(async move { 237 | let release = watch.signaled().await; 238 | drop(release); 239 | }); 240 | assert_pending!(signaled.poll()); 241 | 242 | let mut drain = task::spawn(signal.drain()); 243 | assert_pending!(drain.poll()); 244 | assert_ready!(signaled.poll()); 245 | assert_ready!(drain.poll()); 246 | } 247 | 248 | #[tokio::test] 249 | async fn closed() { 250 | let (mut signal, watch) = super::channel(); 251 | let mut closed = task::spawn(async move { 252 | signal.closed().await; 253 | }); 254 | assert_pending!(closed.poll()); 255 | drop(watch); 256 | assert_ready!(closed.poll()); 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /src/retain.rs: -------------------------------------------------------------------------------- 1 | use crate::Watch; 2 | use std::{ 3 | future::Future, 4 | pin::Pin, 5 | task::{Context, Poll}, 6 | }; 7 | use tower_layer::{layer_fn, Layer}; 8 | use tower_service::Service; 9 | 10 | /// Holds a drain::Watch for as long as a request is pending. 11 | #[derive(Clone, Debug)] 12 | pub struct Retain { 13 | inner: S, 14 | drain: Watch, 15 | } 16 | 17 | // === impl Retain === 18 | 19 | impl Retain { 20 | pub fn new(drain: Watch, inner: S) -> Self { 21 | Self { drain, inner } 22 | } 23 | 24 | pub fn layer(drain: Watch) -> impl Layer + Clone { 25 | layer_fn(move |inner| Self::new(drain.clone(), inner)) 26 | } 27 | } 28 | 29 | impl Service for Retain 30 | where 31 | S: Service, 32 | S::Future: Send + 'static, 33 | { 34 | type Response = S::Response; 35 | type Error = S::Error; 36 | type Future = Pin> + Send + 'static>>; 37 | 38 | #[inline] 39 | fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { 40 | self.inner.poll_ready(cx) 41 | } 42 | 43 | #[inline] 44 | fn call(&mut self, req: Req) -> Self::Future { 45 | let call = self.inner.call(req); 46 | let drain = self.drain.clone(); 47 | Box::pin(drain.ignore_signaled().release_after(call)) 48 | } 49 | } 50 | --------------------------------------------------------------------------------