Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proxy: Figure out the plan for Hyper 1.0 migration #8733

Open
olix0r opened this issue Jun 23, 2022 · 7 comments
Open

proxy: Figure out the plan for Hyper 1.0 migration #8733

olix0r opened this issue Jun 23, 2022 · 7 comments
Assignees

Comments

@olix0r
Copy link
Member

olix0r commented Jun 23, 2022

Hyper is planning a major 1.0 milestone that will impact many of their public APIs and, therefore, the proxy. We should get a better understanding of the planned changes so that we can begin to scope and plan the required proxy changes (and so that we can provide meaningful feedback before the APIs are finalized).


"here's some links!" -kate 💐 🧢

issues and pull requests related to upgrading linkerd2 to hyper 1.0:

in particular, this PR:

@olix0r olix0r added this to the stable-2.13.0 milestone Jun 23, 2022
@hawkw
Copy link
Contributor

hawkw commented Jun 23, 2022

Some of the things that are definitely worth keeping an eye on:

  • The future of hyper's use of the Service trait: whether hyper will use tower::Service in 1.0 is currently up in the air. If tower-service isn't 1.0 in time, it definitely won't. The question then becomes whether hyper will provide its own Service trait, or whether the interface will be changed significantly, potentially to one where user code calls into hyper.

    If hyper ends up moving away from tower-service, there will definitely need to be some kind of glue layer (which could be a generic tower-hyper crate or similar); the complexity of this adapter will depend on how different the interfaces end up being.

  • I/O traits: It's unclear whether hyper 1.0 is going to use Tokio's AsyncRead/AsyncWrite traits (as it does currently), or something else.

    • It seems like the use of the Tokio traits is more likely than the version from futures, but not set in stone, especially if there's an eventual plan for AsyncRead/AsyncWrite in std.
    • There are also questions about whether the style of async I/O traits currently in tokio and in futures-io can ever efficiently support io_uring-style async IO, where ownership of the buffer is transferred to the I/O resource (and then to the OS). This is a broader ecosystem-level question, but it's worth keeping an eye on.
  • A bunch of stuff currently in hyper will move to hyper-util. This one is not a huge deal as that should generally be a pretty mechanical transformation.

@stale
Copy link

stale bot commented Sep 21, 2022

This issue has been automatically marked as stale because it has not had recent activity. It will be closed in 14 days if no further activity occurs. Thank you for your contributions.

@stale stale bot added the wontfix label Sep 21, 2022
@adleong adleong added pinned and removed wontfix labels Sep 22, 2022
@adleong adleong modified the milestones: stable-2.13.0, stable-2.14.0 Jan 19, 2023
@risingspiral risingspiral removed this from the stable-2.14.0 milestone Aug 4, 2023
@risingspiral
Copy link
Contributor

@cratelyn cratelyn self-assigned this Dec 2, 2024
cratelyn added a commit to linkerd/linkerd2-proxy that referenced this issue Dec 2, 2024
this is a prepatory chore, laying more ground to facilitate upgrading
our hyper dependency to the 1.0 major release. this commit adds the
`deprecated` cargo feature to each of the hyper dependencies in the
cargo workspace.

to prevent this from introducing a large number of compiler warnings to
the build, the `#[allow(deprecated)]` attribute is added to relevant
expressions.

these uses of deprecated interfaces will be updated in subsequent
commits. broadly, these fall into a few common cases:

* `hyper::client::conn::SendRequest` now has protocol specific `h1` and
  `h2` variants.

* `hyper::server::conn::Builder` now has protocol specific `h1` and
  `h2` variants.

* `hyper::server::conn::Http` is deprecated.

* functions like `hyper::body::aggregate(..)` and
  `hyper::body::to_bytes(..)` are deprecated.

see linkerd/linkerd2#8733 for more information
on the hyper 1.0 upgrade process.

Signed-off-by: katelyn martin <[email protected]>
@cratelyn
Copy link
Contributor

cratelyn commented Dec 2, 2024

i've been driving this work forward recently, and was kindly pointed at this issue by @olix0r. i thought i'd lay down some thoughts on the work done thus far:

first, i drove a few small exploratory spikes into upgrading the proxy here, here, and here.

most of the friction related to this upgrade will be found in the linkerd-proxy-http crate, which provides a number of core facilities wrapping hyper's client and server interfaces used throughout the rest of the proxy.

i've landed a few preparatory changes, in linkerd/linkerd2-proxy#3379, linkerd/linkerd2-proxy#3380, and linkerd/linkerd2-proxy#3382. these pulled assorted standalone bits of http/hyper infrastructure that define http_body::Body middleware out of the linkerd-proxy-http library, and into their own distinct crates.

this will let us bump linkerd-http-classify, linkerd-http-stream-timeouts, linkerd-http-retain, linkerd-http-override-authority, [..] and other linkerd-http-* crates up to hyper 1.0 in separate steps, which should both facilitate review and mitigate the risk of bugs slipping in while addressing the changes to the Body trait in particular.

🥚 🔜 🐣 next steps

next, we can use the deprecated and backports feature flags provided in the 0.14 minor release to further prepare for the upgrade.

linkerd/linkerd2-proxy#3405 adds the deprecated flag to the hyper dependencies in the cargo workspace, and #[allow(deprecated)] attributes to code using deprecated hyper interfaces.

next, we'll address those deprecations, making use of the backports feature. i've landed hyperium/hyper#3796 upstream to backport a 1.0 interface that we'll need before we can use the connection builder backported to 0.14.

cratelyn added a commit to linkerd/linkerd2-proxy that referenced this issue Dec 3, 2024
this is a prepatory chore, laying more ground to facilitate upgrading
our hyper dependency to the 1.0 major release. this commit adds the
`deprecated` cargo feature to each of the hyper dependencies in the
cargo workspace.

to prevent this from introducing a large number of compiler warnings to
the build, the `#[allow(deprecated)]` attribute is added to relevant
expressions.

these uses of deprecated interfaces will be updated in subsequent
commits. broadly, these fall into a few common cases:

* `hyper::client::conn::SendRequest` now has protocol specific `h1` and
  `h2` variants.

* `hyper::server::conn::Builder` now has protocol specific `h1` and
  `h2` variants.

* `hyper::server::conn::Http` is deprecated.

* functions like `hyper::body::aggregate(..)` and
  `hyper::body::to_bytes(..)` are deprecated.

see linkerd/linkerd2#8733 for more information
on the hyper 1.0 upgrade process.

Signed-off-by: katelyn martin <[email protected]>
@cratelyn
Copy link
Contributor

cratelyn commented Dec 3, 2024

Some of the things that are definitely worth keeping an eye on:

* **The future of `hyper`'s use of the `Service` trait**: [whether `hyper` will use `tower::Service` in 1.0](https://github.com/hyperium/hyper/blob/master/docs/ROADMAP.md#service) is currently up in the air. If `tower-service` isn't 1.0 in time, it definitely won't. The question then becomes whether `hyper` will provide its own `Service` trait, or whether the interface will be changed significantly, [potentially to one where user code calls into `hyper`](https://github.com/hyperium/hyper/blob/master/docs/ROADMAP.md#you-call-hyper-or-hyper-calls-you).
  If hyper ends up moving away from `tower-service`, there will definitely need to be some kind of glue layer (which could be a generic `tower-hyper` crate or similar); the complexity of this adapter will depend on how different the interfaces end up being.

* **I/O traits**: It's unclear [whether `hyper` 1.0 is going to use Tokio's `AsyncRead`/`AsyncWrite` traits (as it does currently), or something else](https://github.com/hyperium/hyper/blob/master/docs/ROADMAP.md#runtime-woes).
  
  * It seems like the use of the Tokio traits is more likely than the version from `futures`, but not set in stone, especially if there's an eventual plan for `AsyncRead`/`AsyncWrite` in `std`.
  * There are also questions about whether the style of async I/O traits currently in `tokio` and in `futures-io` can ever efficiently support `io_uring`-style async IO, where ownership of the buffer is transferred to the I/O resource (and then to the OS).  This is a broader ecosystem-level question, but it's worth keeping an eye on.

* **A bunch of stuff currently in `hyper` will move to `hyper-util`.** This one is not a huge deal as that should _generally_ be a pretty mechanical transformation.

for the sake of closing the loop on the details above:

  • Service trait: hyper now provides its own Service trait. glue connecting tower to hyper lives in the hyper-util library: here.

  • I/O traits: hyper provides its own Read and Write traits. glue connecting this to tokio's traits lives in hyper-util::rt::tokio.

  • hyper-util motion: legacy client builder interfaces can be found in hyper_util::client::legacy. see hyper-util::server as well.

cratelyn added a commit to linkerd/linkerd2-proxy that referenced this issue Dec 4, 2024
hyper 0.14.x provided a collection of interfaces related to collecting
and aggregating request and response bodies, which were deprecated and
removed in the 1.x major release.

this commit updates calls to `hyper::body::to_bytes(..)` and
`hyper::body::aggregate(..)`. for now, `http_body::Body` is used, but we
can use `http_body_util::BodyExt` once we've bumped our hyper dependency
to the 1.x major release.

for more information, see:

* linkerd/linkerd2#8733
* hyperium/hyper#2840
* hyperium/hyper#3020

Signed-off-by: katelyn martin <[email protected]>
cratelyn added a commit to linkerd/linkerd2-proxy that referenced this issue Dec 4, 2024
* chore(app/admin): add `http-body` dependency

before we address deprecated hyper interfaces related to `http_bodies`,
we'll want to add this dependency so that we can call `Body::collect()`.

Signed-off-by: katelyn martin <[email protected]>

* refactor(app): update deprecated hyper body calls

hyper 0.14.x provided a collection of interfaces related to collecting
and aggregating request and response bodies, which were deprecated and
removed in the 1.x major release.

this commit updates calls to `hyper::body::to_bytes(..)` and
`hyper::body::aggregate(..)`. for now, `http_body::Body` is used, but we
can use `http_body_util::BodyExt` once we've bumped our hyper dependency
to the 1.x major release.

for more information, see:

* linkerd/linkerd2#8733
* hyperium/hyper#2840
* hyperium/hyper#3020

Signed-off-by: katelyn martin <[email protected]>

---------

Signed-off-by: katelyn martin <[email protected]>
@cratelyn
Copy link
Contributor

cratelyn commented Dec 4, 2024

with linkerd/linkerd2-proxy#3405 landed, we're now in a good position to begin addressing particular deprecations in the hyper 0.14.31 interface to prepare the proxy for the hyper 1.0 upgrade.

linkerd/linkerd2-proxy#3411 is an example of one such change, handling calls to deprecated hyper::body functions such as e.g. aggregate().

cratelyn added a commit to linkerd/linkerd2-proxy that referenced this issue Dec 4, 2024
this `Server` type is not used by any tests.

this commit removes it, to help facilitate the upgrade to the hyper 1.0
major release. see <linkerd/linkerd2#8733>.

Signed-off-by: katelyn martin <[email protected]>
cratelyn added a commit to linkerd/linkerd2-proxy that referenced this issue Dec 4, 2024
this `Server` type is not used by any tests.

this commit removes it, to help facilitate the upgrade to the hyper 1.0
major release. see <linkerd/linkerd2#8733>.

Signed-off-by: katelyn martin <[email protected]>
cratelyn added a commit to linkerd/linkerd2-proxy that referenced this issue Dec 6, 2024
#3427)

this branch contains a sequence of commits that focus on addressing
deprecation warnings related to hyper::client::conn::Builder. this
branch enables the backports feature, and then replaces some of these
builders with the backported, http/2 specific,
hyper::client::conn::http2::Builder type.

relates to linkerd/linkerd2#8733.

---

* chore(proxy/http): address `h2::Connection` deprecation

this commit updates `Connection<B>` to use the backported, http/2
specific `SendRequest` type.

this commit enables the `backports` feature flag in the `hyper`
dependency.

Signed-off-by: katelyn martin <[email protected]>

* chore(proxy/http): address `server::tests` deprecations

Signed-off-by: katelyn martin <[email protected]>

* refactor(proxy/http): consolidate connect functions

`connect()` is never called elsewhere. let's avoid the misdirection and
move it into the body of `connect_h2()`.

Signed-off-by: katelyn martin <[email protected]>

---------

Signed-off-by: katelyn martin <[email protected]>
cratelyn added a commit to linkerd/linkerd2-proxy that referenced this issue Dec 6, 2024
while handling the upgrade to hyper 1.0, i noticed some small changes
that'd be nice to make.

most importantly, with respect to
linkerd/linkerd2#8733, this commit outlines
`http_util::http_request()`. hyper 1.0 provides version specific
`SendRequest` types for HTTP/1 and HTTP/2, so rather than try to be
polymorphic across both senders, we send the request at the call site.

two other commits remove `pub` attributes for functions that aren't
called externally. we'll address `run_proxy()` and `connect_client()` in
a subsequent PR, because the dependent tests use both HTTP/1 and HTTP/2. 

---

* refactor(app/test): remove `pub` from `http_util::connect_client()`

this is not used elsewhere. it can be private.

Signed-off-by: katelyn martin <[email protected]>

* refactor(app/test): remove `pub` from `http_util::run_proxy()`

Signed-off-by: katelyn martin <[email protected]>

* refactor(app/test): remove `http_util::http_request()`

this function abstracts over one statement, at the cost of needing to
name the `SendRequest` type.

because hyper's 1.0 interface provides multiple `SendRequest` types
based on the HTTP version being used. to avoid needing to deal with
polymorphism, and to implicitly address a function-level allowance of
deprecated types, we remove this function and move this statement to the
function's call sites.

Signed-off-by: katelyn martin <[email protected]>

* refactor(app/test): collect bodies with `String::from_utf8`

this function previously called `std::str::from_utf8`, before calling
`str::to_owned` on the result. because of this, there is a bit of extra
gymnastics, passing a `&body[..]` slice along after collecting the body
bytes.

this is both more baroque and less performant. this commit updates the
call, using `String::from_utf8` to collect the body, sparing a needless
`to_owned()`/`clone()` call.

Signed-off-by: katelyn martin <[email protected]>

* docs(app/test): document `io` submodule

Signed-off-by: katelyn martin <[email protected]>

* refactor(app/test): remove duplicate `io` reëxport

the same `pub use` bundle is found in the parent `lib.rs`.

this removes it, and refers to the same `mod io {}` defined above.

Signed-off-by: katelyn martin <[email protected]>

* review(app/inbound): use `ServiceExt::oneshot(..)`

#3428 (comment)

we can simplify these statements sending requests, by using
`ServiceExt::oneshot(..)`.

Signed-off-by: katelyn martin <[email protected]>

---------

Signed-off-by: katelyn martin <[email protected]>
cratelyn added a commit to linkerd/linkerd2-proxy that referenced this issue Dec 6, 2024
this addresses hyper 1.0 deprecations in the server side of the inbound
proxy's http unit test suite logic.

see <linkerd/linkerd2#8733> for more
information.

the client end of this change ends up being slightly involved, due to
changes that will need to be made in `linkerd_app_test::http_util`.
accordingly, those deprecations will be addressed in a subsequent
commit.

Signed-off-by: katelyn martin <[email protected]>
cratelyn added a commit to linkerd/linkerd2-proxy that referenced this issue Jan 22, 2025
this commit refactors the polling logic in
`PeekTrailersBody<B>::read_response`.

this commit makes some subtle changes with the migration to hyper 1.0 in
mind, to make this function more easily portable to the new signature of
`http_body::Body`.

see linkerd/linkerd2#8733 for more
information.

this commit defers the `Self` construction of the `PeekTrailersBody`
body. this means that the control flow does not need to reach through to
e.g. `body.inner` to poll the inner body being peeked. additionally, it
provides us with `let` bindings for the first data frame yielded, and
the trailers frame yielded thereafter.

this is largely cosmetic, but will make it easier to deal with the
additional matching we'll need to do when there is a single polling
function that yields us `Frame<D>` objects.

Signed-off-by: katelyn martin <[email protected]>
cratelyn added a commit to linkerd/linkerd2-proxy that referenced this issue Jan 22, 2025
note: this commit will not compile, code changes are intentionally
elided from this commit.

this commit upgrades hyper, http, tonic, prost, related dependencies,
and their assorted cargo features.

see <linkerd/linkerd2#8733>.

Signed-off-by: katelyn martin <[email protected]>
cratelyn added a commit to linkerd/linkerd2-proxy that referenced this issue Jan 22, 2025
* refactor(http/upgrade): `Http11Upgrade::insert_half` matches on `self`

this is a noöp change, to set the stage for subsequent changes to the
internal model of `Http11Upgrade`.

this `inner` field will shortly be an option, and this will make it
easier to only follow these panicking branches when the inner lock is
`Some(_)`.

Signed-off-by: katelyn martin <[email protected]>

* refactor(http/upgrade): `Http11Upgade` stores an `Option<T>`

this commit hinges on this change to the upgrade middleware's `inner`
field.

we still retain a reference-counted copy of the `Inner` state, but now
we may store `None` here.

```
 pub struct Http11Upgrade {
     half: Half,
-    inner: Arc<Inner>,
+    inner: Option<Arc<Inner>>,
 }
```

a new branch is added to the `insert_half` method that consumes the
"sender" and inserts an upgrade future; when this is `None` it will do
nothing, rather than panicking.

Signed-off-by: katelyn martin <[email protected]>

* refactor(http/upgrade): `Half` marker is `Copy`

this type is an empty flag to indicate whether an `Http11Upgrade`
extension corresponds to the server or client half of the upgrade
future channel.

this type is made copy, to facilitate making the `Http11Upgrade`
extension safely cloneable.

Signed-off-by: katelyn martin <[email protected]>

* refactor(http/upgrade): `Http11Upgrade` is `Clone`

this commit makes `Http11Upgrade` a cloneable type.

see <linkerd/linkerd2#8733>.

in the 1.0 interface of the `http` crate, request and response
extensions must now satisfy a `Clone` bound.

`Http11Upgrade` was written before this was the case, and is very
intentionally designed around the idea that it *not* be cloneable.

`insert_half()` in particular could cause the proxy to panic if it were
to clone a request or response's extensions. it might call
`insert_half()` a second time, and discover that the `TryLock<T>` had
already been set.

moreover, holding on to a copy of the extensions would prevent the
`Drop` method for `Inner` from being called. This would cause
connections that negotiate an HTTP/1.1 upgrade to deadlock due to the
`OnUpgrade` futures never being polled, and failing to create a `Duplex`
that acts as the connection's I/O transport.

this commit makes use of the alterations to `Http11Upgrade` made in
previous commits, and adds a *safe* implementation of `Clone`. by
only shallowly copying the extension, we tie the upgrade glue to a
*specific* request/response.

the extension can be cloned, but any generated copies will be inert.

Signed-off-by: katelyn martin <[email protected]>

* chore(http/upgrade): fix broken intradoc links

Signed-off-by: katelyn martin <[email protected]>

* chore(http/upgrade): add `thiserror` dependency

Signed-off-by: katelyn martin <[email protected]>

* refactor(proxy/http): use `.await` syntax

`FutureExt::map_ok()` won't work if we try to return an error from this
block. the `and_then()` adaptor is used to chain futures, and also won't
work given a synchronous closure.

this can be done with the equivalent `.await` syntax, and leaves a nicer
hole for us to propagate other errors here, shortly.

Signed-off-by: katelyn martin <[email protected]>

* review(http/upgrade): propagate `insert_half()` failures

#3540 (comment)

Signed-off-by: katelyn martin <[email protected]>
Co-Authored-By: Oliver Gould <[email protected]>

* docs(http/upgrade): tweak comment

Signed-off-by: katelyn martin <[email protected]>

---------

Signed-off-by: katelyn martin <[email protected]>
Co-authored-by: Oliver Gould <[email protected]>
cratelyn added a commit to linkerd/linkerd2-proxy that referenced this issue Jan 22, 2025
this commit refactors the polling logic in
`PeekTrailersBody<B>::read_response`.

this commit makes some subtle changes with the migration to hyper 1.0 in
mind, to make this function more easily portable to the new signature of
`http_body::Body`.

see linkerd/linkerd2#8733 for more
information.

this commit defers the `Self` construction of the `PeekTrailersBody`
body. this means that the control flow does not need to reach through to
e.g. `body.inner` to poll the inner body being peeked. additionally, it
provides us with `let` bindings for the first data frame yielded, and
the trailers frame yielded thereafter.

this is largely cosmetic, but will make it easier to deal with the
additional matching we'll need to do when there is a single polling
function that yields us `Frame<D>` objects.

Signed-off-by: katelyn martin <[email protected]>
cratelyn added a commit to linkerd/linkerd2-proxy that referenced this issue Jan 22, 2025
this is a squashed commit containing the following:

---

refactor(http/retry): decompose `WithPeekTrailersBody<B>` type alias

this commit breaks this large type out into two halves.

this is a purely cosmetic change.

Signed-off-by: katelyn martin <[email protected]>

refactor(http/retry): `PeekTrailersBody` is pin projected

we must pass our `Pin<T>`'edness down to the inner `B`-typed body for
`PeekTrailersBody` to itself implement `http_body::Body`.

this commit tweaks the existing code to rely on the `pin-project`
library. this generates a `project()` method to pin inner fields whose
`poll_data()` and `poll_trailers()` functions we delegate to.

this is a noöp change.

Signed-off-by: katelyn martin <[email protected]>

refactor(http/retry): defer construction of `PeekTrailersBody<B>`

this commit refactors the polling logic in
`PeekTrailersBody<B>::read_response`.

this commit makes some subtle changes with the migration to hyper 1.0 in
mind, to make this function more easily portable to the new signature of
`http_body::Body`.

see linkerd/linkerd2#8733 for more
information.

this commit defers the `Self` construction of the `PeekTrailersBody`
body. this means that the control flow does not need to reach through to
e.g. `body.inner` to poll the inner body being peeked. additionally, it
provides us with `let` bindings for the first data frame yielded, and
the trailers frame yielded thereafter.

this is largely cosmetic, but will make it easier to deal with the
additional matching we'll need to do when there is a single polling
function that yields us `Frame<D>` objects.

Signed-off-by: katelyn martin <[email protected]>

refactor(http/retry): `PeekTrailersBody` transforms `B`

this is a small structural change to the
`PeekTrailersBody::read_response()` function to facilitate writing some
unit tests.

rather than transforming a `Response<B>` into a
`Response<PeekTrailersBody<B>>`, we hoist the `Response::into_parts()`
and `Response::from_parts()` calls up. `read_response()` is renamed to
`read_body()`.

Signed-off-by: katelyn martin <[email protected]>
cratelyn added a commit to linkerd/linkerd2-proxy that referenced this issue Jan 22, 2025
this is a squashed commit containing the following:

---

refactor(http/retry): decompose `WithPeekTrailersBody<B>` type alias

this commit breaks this large type out into two halves.

this is a purely cosmetic change.

Signed-off-by: katelyn martin <[email protected]>

refactor(http/retry): `PeekTrailersBody` is pin projected

we must pass our `Pin<T>`'edness down to the inner `B`-typed body for
`PeekTrailersBody` to itself implement `http_body::Body`.

this commit tweaks the existing code to rely on the `pin-project`
library. this generates a `project()` method to pin inner fields whose
`poll_data()` and `poll_trailers()` functions we delegate to.

this is a noöp change.

Signed-off-by: katelyn martin <[email protected]>

refactor(http/retry): defer construction of `PeekTrailersBody<B>`

this commit refactors the polling logic in
`PeekTrailersBody<B>::read_response`.

this commit makes some subtle changes with the migration to hyper 1.0 in
mind, to make this function more easily portable to the new signature of
`http_body::Body`.

see linkerd/linkerd2#8733 for more
information.

this commit defers the `Self` construction of the `PeekTrailersBody`
body. this means that the control flow does not need to reach through to
e.g. `body.inner` to poll the inner body being peeked. additionally, it
provides us with `let` bindings for the first data frame yielded, and
the trailers frame yielded thereafter.

this is largely cosmetic, but will make it easier to deal with the
additional matching we'll need to do when there is a single polling
function that yields us `Frame<D>` objects.

Signed-off-by: katelyn martin <[email protected]>

refactor(http/retry): `PeekTrailersBody` transforms `B`

this is a small structural change to the
`PeekTrailersBody::read_response()` function to facilitate writing some
unit tests.

rather than transforming a `Response<B>` into a
`Response<PeekTrailersBody<B>>`, we hoist the `Response::into_parts()`
and `Response::from_parts()` calls up. `read_response()` is renamed to
`read_body()`.

Signed-off-by: katelyn martin <[email protected]>
cratelyn added a commit to linkerd/linkerd2-proxy that referenced this issue Jan 23, 2025
this commit reworks `PeekTrailersBody<B>`.

the most important goal here is replacing the control flow of
`read_body()`, which polls a body using `BodyExt` future combinators
`data()` and `frame()` for up to two frames, with varying levels of
persistence depending on outcomes.

to quote #3556:

> the intent of this type is to only yield the asynchronous task
> responsible for reading the body once. depending on what the inner
> body yields, and when, this can result in combinations of: no data
> frames and no trailers, no data frames with trailers, one data frame
> and no trailers, one data frame with trailers, or two data frames.
> moreover, depending on which of these are yielded, the body will call
> .await some scenarios, and only poll functions once in others.
>
> migrating this to the Frame<T> and poll_frame() style of the 1.0 Body
> interface, away from the 0.4 interface that provides distinct
> poll_data() and poll_trailers() methods, is fundamentally tricky.

this means that `PeekTrailersBody<B>` is notably difficult to port
across the http-body 0.4/1.0 upgrade boundary.

this body middleware must navigate a number of edge conditions, and once
it _has_ obtained a `Frame<T>`, make use of conversion methods to
ascertain whether it is a data or trailers frame, due to the fact that
its internal enum representation is not exposed publicly. one it has
done all of that, it must do the same thing once more to examine the
second frame.

this commit uses the compatibility facilities and backported `Frame<T>`
introduced in the previous commit, and rewrites this control flow using
a form of the `BodyExt::frame()` combinator.

this means that this middleware is forward-compatible with http-body
1.0, which will dramatically simplify the remaining migration work to be
done in #3504.

see linkerd/linkerd2#8733 for more information
and other links related to this ongoing migration work.

Signed-off-by: katelyn martin <[email protected]>
cratelyn added a commit to linkerd/linkerd2-proxy that referenced this issue Jan 24, 2025
this branch contains a sequence of commits that refactor `PeekTrailersBody<B>`.

this branch is specifically focused on making this body middleware
forward-compatible with the 1.0 interface(s) of `http_body::Body` and
`http_body_util::BodyExt`.

it does this in two main steps: (1) temporarily vendoring `http_body::Frame<T>`
and providing a compatibility shim that provides a `frame()` method for a body,
and (2) modeling `PeekTrailersBody<B>` and its peeking logic in terms of this
`Frame<'a, T>` future.

---

* feat(http/retry): add `Frame<T>` compatibility facilities

this commit introduces a `compat` submodule to `linkerd-http-retry`.

this helps us frontrun the task of replacing all of the finicky control
flow in `PeekTrailersBody<B>` using the antiquated `data()` and
`trailers()` future combinators. instead, we can perform our peeking
in terms of an approximation of `http_body_util::BodyExt::frame()`.

to accomplish this, this commit vendors a copy of the `Frame<T>` type.
we can use this to preemptively model our peek body in terms of this
type, and move to the "real" version of it when we're upgrading in
pr #3504.

additionally, this commit includes a type called
`ForwardCompatibleBody<B>`, and a variant of the `Frame<'a, T>`
combinator. these are a bit boilerplate-y, admittedly, but the pleasant
part of this is that we have, in effect, migrated the trickiest body
middleware in advance of #3504. once we upgrade to http-body 1.0, all of
these types can be removed.

https://docs.rs/http-body-util/latest/http_body_util/trait.BodyExt.html#method.frame
https://docs.rs/http-body-util/0.1.2/src/http_body_util/combinators/frame.rs.html#10

Signed-off-by: katelyn martin <[email protected]>

* refactor(http/retry): `PeekTrailersBody<B>` uses `BodyExt::frame()`

this commit reworks `PeekTrailersBody<B>`.

the most important goal here is replacing the control flow of
`read_body()`, which polls a body using `BodyExt` future combinators
`data()` and `frame()` for up to two frames, with varying levels of
persistence depending on outcomes.

to quote #3556:

> the intent of this type is to only yield the asynchronous task
> responsible for reading the body once. depending on what the inner
> body yields, and when, this can result in combinations of: no data
> frames and no trailers, no data frames with trailers, one data frame
> and no trailers, one data frame with trailers, or two data frames.
> moreover, depending on which of these are yielded, the body will call
> .await some scenarios, and only poll functions once in others.
>
> migrating this to the Frame<T> and poll_frame() style of the 1.0 Body
> interface, away from the 0.4 interface that provides distinct
> poll_data() and poll_trailers() methods, is fundamentally tricky.

this means that `PeekTrailersBody<B>` is notably difficult to port
across the http-body 0.4/1.0 upgrade boundary.

this body middleware must navigate a number of edge conditions, and once
it _has_ obtained a `Frame<T>`, make use of conversion methods to
ascertain whether it is a data or trailers frame, due to the fact that
its internal enum representation is not exposed publicly. one it has
done all of that, it must do the same thing once more to examine the
second frame.

this commit uses the compatibility facilities and backported `Frame<T>`
introduced in the previous commit, and rewrites this control flow using
a form of the `BodyExt::frame()` combinator.

this means that this middleware is forward-compatible with http-body
1.0, which will dramatically simplify the remaining migration work to be
done in #3504.

see linkerd/linkerd2#8733 for more information
and other links related to this ongoing migration work.

Signed-off-by: katelyn martin <[email protected]>

* refactor(http/retry): mock body enforces `poll_trailers()` contract

this commit addresses a `TODO` note, and tightens the enforcement of a
rule defined by the v0.4 signature of the `Body` trait.

this commit changes the mock body type, used in tests, so that it will
panic if the caller improperly polls for a trailers frame before the
final data frame has been yielded.

previously, a comment indicated that we were "fairly sure" this was
okay. while that may have been acceptable in practice, the changes in
the previous commit mean that we now properly respect these rules.

thus, a panic can be placed here, to enforce that "[is] only be called
once `poll_data()` returns `None`", per the documentation.

Signed-off-by: katelyn martin <[email protected]>

* refactor(http/retry): rename `PeekTrailersBody::Buffered`

<#3559 (comment)>

this is a nicer name than `Unknown` for this case. not to mention, we'll
want that name shortly to address the possibility of unknown frame
variants.

Signed-off-by: katelyn martin <[email protected]>

* refactor(http/retry): encapsulate `Inner<B>` enum variants

this commit makes the inner enum variants private.

#3559 (comment)

Signed-off-by: katelyn martin <[email protected]>

* refactor(http/retry): gracefully ignore unknown frames

#3559 (comment)

Signed-off-by: katelyn martin <[email protected]>

---------

Signed-off-by: katelyn martin <[email protected]>
cratelyn added a commit to linkerd/linkerd2-proxy that referenced this issue Jan 24, 2025
this is a refactor commit, outlined from #3504.

this commit is particularly interested in preparing the
`PeekTrailersBody<B>` middleware for our upgrade to hyper/http-body 1.0.
more specifically: this commit clears up the boundary concerning when it
will or will not become inert and delegate to its inner body `B`.

currently, a `PeekTrailersBody<B>` is not fully consistent about the
conditions in which it will peek the trailers of a response body: the
inner body is allowed to yield _either_ (a) **zero** DATA frames, in which
case the body will be `.await`'ed and polled until the trailers are
obtained, or (b) **one** DATA frame, so long as the inner body
immediately yields a trailer.

see linkerd/linkerd2#8733.

Signed-off-by: katelyn martin <[email protected]>
cratelyn added a commit to linkerd/linkerd2-proxy that referenced this issue Jan 24, 2025
see linkerd/linkerd2#8733.

`Body::channel()` is no longer exposed in hyper's 1.0 release.

this commit adds `#[allow(deprecated)]` attributes. an upstream patch
will mark this as deprecated.

* linkerd/linkerd2#8733
* hyperium/hyper#2962
* hyperium/hyper#2970

Signed-off-by: katelyn martin <[email protected]>
cratelyn added a commit to linkerd/linkerd2-proxy that referenced this issue Jan 24, 2025
note: this commit will not compile, code changes are intentionally
elided from this commit.

this commit upgrades hyper, http, tonic, prost, related dependencies,
and their assorted cargo features.

see <linkerd/linkerd2#8733>.

Signed-off-by: katelyn martin <[email protected]>
cratelyn added a commit to linkerd/linkerd2-proxy that referenced this issue Jan 24, 2025
see linkerd/linkerd2#8733.

this commit upgrades a test that uses defunct `data()` and `trailers()`
futures.

Signed-off-by: katelyn martin <[email protected]>
cratelyn added a commit to linkerd/linkerd2-proxy that referenced this issue Jan 24, 2025
see linkerd/linkerd2#8733.

this commit upgrades a test that uses defunct `data()` and `trailers()`
futures.

Signed-off-by: katelyn martin <[email protected]>
cratelyn added a commit to linkerd/linkerd2-proxy that referenced this issue Jan 27, 2025
see linkerd/linkerd2#8733.

pr #3559 introduced some compatibility facilities to allow us to write
code in terms of `http_body_util::BodyExt::frame()`, front-running the
upgrade to be performed in #3504.

some `ReplayBody` tests use the defunct `data()` and `trailers()`
interfaces. this branch ports _two_ such unit tests. other tests are
saved for a fast follow-on, as the `chunk(..)` and `read_to_string(..)`
helpers will need some slightly more involved tweaks.

dd4fbcd

---

* refactor(http/retry): `replays_trailers()` uses `Frame<T>`

see linkerd/linkerd2#8733.

this commit upgrades a test that uses defunct `data()` and `trailers()`
futures.

Signed-off-by: katelyn martin <[email protected]>

* refactor(http/retry): `trailers_only()` uses `Frame<T>`

see linkerd/linkerd2#8733.

this commit upgrades a test that uses defunct `data()` and `trailers()`
futures.

Signed-off-by: katelyn martin <[email protected]>

---------

Signed-off-by: katelyn martin <[email protected]>
cratelyn added a commit to linkerd/linkerd2-proxy that referenced this issue Jan 27, 2025
…#3567)

based on #3564. see linkerd/linkerd2#8733.

this branch upgrades the remaining parts of the `ReplayBody<B>` test
suite to poll bodies in terms of `Frame<T>`s. 

1eb822f

---

* refactor(http/retry): `replays_trailers()` uses `Frame<T>`

see linkerd/linkerd2#8733.

this commit upgrades a test that uses defunct `data()` and `trailers()`
futures.

Signed-off-by: katelyn martin <[email protected]>

* refactor(http/retry): `trailers_only()` uses `Frame<T>`

see linkerd/linkerd2#8733.

this commit upgrades a test that uses defunct `data()` and `trailers()`
futures.

Signed-off-by: katelyn martin <[email protected]>

* feat(http/retry): `ForwardCompatibleBody::is_end_stream()`

this commit adds a method that exposes the inner `B`-typed body's
`is_end_stream()` trait method, gated for use in tests.

Signed-off-by: katelyn martin <[email protected]>

* refactor(http/retry): `body_to_string()` helper uses `Frame<T>`

this is a refactoring commit, upgrading more of the replay body test to
work in terms of `Frame<T>`. this updates the `body_to_string()` helper
in particular.

Signed-off-by: katelyn martin <[email protected]>

* refactor(http/retry): `chunk()` helper uses `Frame<T>`

Signed-off-by: katelyn martin <[email protected]>

---------

Signed-off-by: katelyn martin <[email protected]>
cratelyn added a commit to linkerd/linkerd2-proxy that referenced this issue Jan 27, 2025
note: this commit will not compile, code changes are intentionally
elided from this commit.

this commit upgrades hyper, http, tonic, prost, related dependencies,
and their assorted cargo features.

see <linkerd/linkerd2#8733>.

Signed-off-by: katelyn martin <[email protected]>
cratelyn added a commit to linkerd/linkerd2-proxy that referenced this issue Jan 28, 2025
pr's #3564 and #3567, 1eb822f and 3204278 respectively, replaced uses
of defunct `http_body::Body` trait methods — namely, `data()` and
`trailers()`.

this commit updates two remaining uses of `data()` that were missed
in this initial pass.

see linkerd/linkerd2#8733 for more information.

Signed-off-by: katelyn martin <[email protected]>
cratelyn added a commit to linkerd/linkerd2-proxy that referenced this issue Jan 28, 2025
pr's #3564 and #3567, 1eb822f and 3204278 respectively, replaced uses
of defunct `http_body::Body` trait methods — namely, `data()` and
`trailers()`.

this commit updates two remaining uses of `data()` that were missed
in this initial pass.

see linkerd/linkerd2#8733 for more information.

Signed-off-by: katelyn martin <[email protected]>
cratelyn added a commit to linkerd/linkerd2-proxy that referenced this issue Jan 30, 2025
pr's #3564 and #3567, 1eb822f and 3204278 respectively, replaced uses
of defunct `http_body::Body` trait methods — namely, `data()` and
`trailers()`.

this commit updates two remaining uses of `data()` that were missed
in this initial pass.

see linkerd/linkerd2#8733 for more information.

Signed-off-by: katelyn martin <[email protected]>
cratelyn added a commit to linkerd/linkerd2-proxy that referenced this issue Jan 30, 2025
we have a unit test, called `eos_only_when_fully_replayed` that confirms
`Body::end_of_stream()` reports the stream ending properly.

soon, we aim to introduce additional test coverage that exercises this
when a body has trailers, as well. this will be useful for assurance
related to upgrading to http-body v1.x. see linkerd/linkerd2#8733 for
more information.

unfortunately, hyper 0.14's channel-backed body does not report itself
as having reached the end of the stream. this is an unfortunate quality
that prevents us from using `Test::new()`.

this commit adds a `TestBody` type that we can use in place of
`BoxBody::from_static(..)`, which boxes a static string, but does not
send trailers.

Signed-off-by: katelyn martin <[email protected]>
cratelyn added a commit to linkerd/linkerd2-proxy that referenced this issue Jan 30, 2025
this commit introduces additional test coverage that exercises
`is_end_stream()` when a replay body is wrapping a body with trailers.
this will be useful for assurance related to upgrading to http-body
v1.x. see linkerd/linkerd2#8733 for more information.

Signed-off-by: katelyn martin <[email protected]>
cratelyn added a commit to linkerd/linkerd2-proxy that referenced this issue Jan 30, 2025
see linkerd/linkerd2#8733.

we are currently in the process of upgrading our hyper, http, and http-body dependencies to their 1.0 major releases.

this branch introduces additional test coverage, and further refines existing test coverage, concerning how a `ReplayBody<B>` behaves when it reaches the "end of stream" of its internal `B`-typed body.

additional assertions are added to show that bodies with trailers may be replayed an arbitrary number of times, and that capacity errors occur precisely at their expected boundary. additional assertions are added to confirm that `ReplayBody::is_capped()` reports these conditions properly.

this branch also notably outlines the unit test suite into a separate file, due to its size. as a result, reviewers are encouraged to walk through this branch on a commit-by-commit basis when reading these changes.

i noticed some relatively minor issues with `is_end_stream()` and `size_hint()` while i was reviewing this middleware, in preparation to port it to http-body 1.0. i have left `TODO` comments noting where today's behavior is slightly askew, but _intentionally avoided_ fixing them here. my goal on that front is to highlight those wrinkles so that later fixes to these edges are more easily reviewable.

---

* refactor(http/retry): outline `ReplayBody<B>` unit tests

there are more than 500 lines of unit tests. let's move them into a
submodule, for convenience.

Signed-off-by: katelyn martin <[email protected]>

* nit(http/retry): reorganize replay tests

this is a small cosmetic change reording some test helpers.

there is a common convention of affixing a banner comment above groups
of `impl T {}` blocks, which is useful when top-level blocks are folded
in an editor.

similarly, there is a convention of defining structures at the top of a
file.

this commit reorganizes the replay body tests to follow each of these
conventions.

Signed-off-by: katelyn martin <[email protected]>

* nit(http/retry): test replays trailers twice

just to be extra sure!

Signed-off-by: katelyn martin <[email protected]>

* nit(http/retry): rename `trailers_only()` test

this is part of a family of other tests called `replays_one_chunk()`,
`replays_several_chunks()`, and `replays_trailers()`. let's name this
something that lines up with this convention.

Signed-off-by: katelyn martin <[email protected]>

* feat(http/retry): add a `TestBody` type

we have a unit test, called `eos_only_when_fully_replayed` that confirms
`Body::end_of_stream()` reports the stream ending properly.

soon, we aim to introduce additional test coverage that exercises this
when a body has trailers, as well. this will be useful for assurance
related to upgrading to http-body v1.x. see linkerd/linkerd2#8733 for
more information.

unfortunately, hyper 0.14's channel-backed body does not report itself
as having reached the end of the stream. this is an unfortunate quality
that prevents us from using `Test::new()`.

this commit adds a `TestBody` type that we can use in place of
`BoxBody::from_static(..)`, which boxes a static string, but does not
send trailers.

Signed-off-by: katelyn martin <[email protected]>

* feat(http/retry): add `is_end_stream()` coverage for trailers

this commit introduces additional test coverage that exercises
`is_end_stream()` when a replay body is wrapping a body with trailers.
this will be useful for assurance related to upgrading to http-body
v1.x. see linkerd/linkerd2#8733 for more information.

Signed-off-by: katelyn martin <[email protected]>

* feat(http/retry): add `is_capped()` test coverage

Signed-off-by: katelyn martin <[email protected]>

* feat(http/retry): further refine capacity test coverage

we want to show that exceeding the capacity is the point at which
replays will fail.

this commit defines some constants to further communicate and encode
this relationship between the bytes sent, and the capacity of the replay
body.

further, it shortens the second frame sent so that we ensure precisely
when a body becomes capped.

Signed-off-by: katelyn martin <[email protected]>

* feat(http/retry): add `size_hint()` test coverage

Signed-off-by: katelyn martin <[email protected]>

* chore(http/retry): add todo comments concerning eos

for now, a replaying body that will not yield trailers must be polled to
the `None` before reporting itself as reaching the end of the stream.

this isn't hugely important, but does affect some test control flow.

leave two todo comments so that if/when upgrading to hyper 1.0, it is
clear that these are not load-bearing or otherwise expected behavior,
should this behavior be rectified.

Signed-off-by: katelyn martin <[email protected]>

---------

Signed-off-by: katelyn martin <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants