Skip to content

Conversation

davidpdrsn
Copy link
Member

@davidpdrsn davidpdrsn commented Feb 8, 2022

In practice I've found Either be hard to use since it changes the error type to BoxError. That means if you combine two infallible services you get a service that, to the type system, is fallible. That doesn't work well with axum's error handling model which requires all services to be infallible and thus always return a response. So you end up having to add boilerplate just to please the type system.

Additionally, the fact that Either implements Future also means we cannot fully remove the dependency on pin-project since pin-project-lite doesn't support tuple enum variants, only named fields.

This PR reworks Either to address these:

  • It now requires the two services to have the same error type so no type information is lost. I did consider doing something like where B::Error: From<A::Error> but I hope this simpler model will lead to better compiler errors.
  • Changes the response future to be a struct with a private enum using pin-project-lite
  • Removes the Future impl so we can remove the dependency on pin-project

Goes without saying that this is a breaking change so we have to wait until tower 0.5 to ship this.

cc @jplatte

Fixes #594
Fixes #550

@davidpdrsn davidpdrsn added the A-util Area: The tower "util" module label Feb 8, 2022
@jplatte
Copy link
Member

jplatte commented Feb 8, 2022

Have you considered implementing Layer and Service for futures_util::future::Either instead and using that type in the relevant places? That approach seemed a little bit cleaner to me when going over the possible solutions last time.

@jplatte
Copy link
Member

jplatte commented Feb 8, 2022

Additionally, IIRC there was some talk about changing A and B to Left and Right, which should maybe be part of the same PR?

@davidpdrsn
Copy link
Member Author

Have you considered implementing Layer and Service for futures_util::future::Either instead and using that type in the relevant places? That approach seemed a little bit cleaner to me when going over the possible solutions last time.

That would mean tower gets a public dependency on futures_util which I don't think we have today 🤔

Additionally, IIRC there was some talk about changing A and B to Left and Right, which should maybe be part of the same PR?

Yeah that seems fine to me. I'll do that.

@olix0r olix0r added this to the 0.5 milestone Feb 8, 2022
@hawkw
Copy link
Member

hawkw commented Feb 8, 2022

Have you considered implementing Layer and Service for futures_util::future::Either instead and using that type in the relevant places? That approach seemed a little bit cleaner to me when going over the possible solutions last time.

That would mean tower gets a public dependency on futures_util which I don't think we have today

FWIW, tower does depend on futures_util, but it's not exposed in any public APIs as far as I know.

@davidpdrsn
Copy link
Member Author

davidpdrsn commented Feb 8, 2022

Actually implementing Service and Layer directly for futures_util::future::Either would require putting those impls in tower-service and tower-layer and probably get in the way of making them 1.0, at least until futures_util itself is 1.0. Dunno what the timeline is on that but I doubt it'll happen any time soon.

@hawkw
Copy link
Member

hawkw commented Feb 8, 2022

Actually implementing Service and Layer directly for futures_util::future::Either would require putting those impls in tower-service and tower-layer and probably get in the way of making them 1.0, at least until futures_util itself is 1.0. Dunno what the timeline is on that but I doubt it'll happen any time soon.

Oh, you're right, I didn't think through the orphan rules here. I don't think we'll be able to do that, then.

We could use futures_util::future::Either instead of the custom response future type, but I don't think it's worth making it public API for that purpose. The implementation is trivial enough that we don't need to use it from futures_util, I think the only motivation would be compatibility, and compatibility would require putting it in tower-service, which I don't think we should do.

Copy link
Member

@hawkw hawkw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems good to me (in 0.5, of course)

Co-authored-by: Eliza Weisman <eliza@buoyant.io>
@olix0r
Copy link
Collaborator

olix0r commented Feb 8, 2022

I'm generally +1 on this. we should do 0.5 sooner than later...

The CI failure seems legit, though.

@hawkw hawkw enabled auto-merge (squash) February 17, 2022 20:04
@hawkw hawkw merged commit 522687c into master Feb 17, 2022
@hawkw hawkw deleted the rework-either branch February 17, 2022 20:09
davidpdrsn added a commit to tokio-rs/axum that referenced this pull request Jan 14, 2023
Tower already has `option_layer` for conditionally applying a `Layer`
but since it uses `tower::util::Either` it always changes the error type
to `BoxError`. That requires using `HandleErrorLayer` which is
inconvenient for axum users.

That has been changed in tower
(tower-rs/tower#637) but still has some issues
(tower-rs/tower#665). Its also a breaking
change so hasn't been released yet.

In the meantime I figure we can provide our own thing in axum-extra,
since we already have an `Either` type there.
davidpdrsn added a commit to tokio-rs/axum that referenced this pull request Jan 14, 2023
Tower already has `option_layer` for conditionally applying a `Layer`
but since it uses `tower::util::Either` it always changes the error type
to `BoxError`. That requires using `HandleErrorLayer` which is
inconvenient for axum users.

That has been changed in tower
(tower-rs/tower#637) but still has some issues
(tower-rs/tower#665). Its also a breaking
change so hasn't been released yet.

In the meantime I figure we can provide our own thing in axum-extra,
since we already have an `Either` type there.
cratelyn added a commit to linkerd/linkerd2-proxy that referenced this pull request Mar 11, 2025
this commit makes a noöp change to the `SwitchReady<A, B>` machinery
provided by our `linkerd-stack` library.

this commit is a small refactor that is intended to pave the way for an
impending upgrade to tower v0.5, which notably includes breaking changes
to the `tower::util::Either<A, B>` service.

as of tower v0.5, by way of tower-rs/tower#637,
the `Either<A, B>` service is no longer itself a `Future`. so, we can
instead use the future provided by `futures`.

for more information, see:
* linkerd/linkerd2#8733
* #3504
* https://github.com/linkerd/linkerd2-proxy/pull/3504/files#r1988082658
* tower-rs/tower#637

Signed-off-by: katelyn martin <kate@buoyant.io>
cratelyn added a commit to linkerd/linkerd2-proxy that referenced this pull request Mar 11, 2025
this commit makes a noöp change to the `SwitchReady<A, B>` machinery
provided by our `linkerd-stack` library.

this commit is a small refactor that is intended to pave the way for an
impending upgrade to tower v0.5, which notably includes breaking changes
to the `tower::util::Either<A, B>` service.

as of tower v0.5, by way of tower-rs/tower#637,
the `Either<A, B>` service is no longer itself a `Future`. so, we can
instead use the future provided by `futures`.

for more information, see:
* linkerd/linkerd2#8733
* #3504
* https://github.com/linkerd/linkerd2-proxy/pull/3504/files#r1988082658
* tower-rs/tower#637

Signed-off-by: katelyn martin <kate@buoyant.io>
cratelyn added a commit to cratelyn/tower that referenced this pull request Mar 11, 2025
in tower-rs#637, breaking changes were made to the `Either<A, B>` service.

this commit adds documentation of these breaking changes to the changelog, so that users upgrading from 0.4 to 0.5 have record of what changed when, and why.
seanmonstar pushed a commit that referenced this pull request Mar 11, 2025
in #637, breaking changes were made to the `Either<A, B>` service.

this commit adds documentation of these breaking changes to the changelog, so that users upgrading from 0.4 to 0.5 have record of what changed when, and why.
cratelyn added a commit to linkerd/linkerd2-proxy that referenced this pull request Mar 14, 2025
this commit updates our tower dependency from 0.4 to 0.5.

note that this commit does not affect the `tower-service` and
`tower-layer` crates, reëxported by `tower` itself. the `Service<T>`
trait and the closely related `Layer<S>` trait have not been changed.

the `tower` crate's utilities have changed in various ways, some of
particular note for the linkerd2 proxy. see these items, excerpted from
the tower changelog:

- **retry**: **Breaking Change** `retry::Policy::retry` now accepts `&mut Req` and `&mut Res` instead of the previous mutable versions. This
  increases the flexibility of the retry policy. To update, update your method signature to include `mut` for both parameters. ([#584])
- **retry**: **Breaking Change** Change Policy to accept &mut self ([#681])
- **retry**: **Breaking Change** `Budget` is now a trait. This allows end-users to implement their own budget and bucket implementations. ([#703])
- **util**: **Breaking Change** `Either::A` and `Either::B` have been renamed `Either::Left` and `Either::Right`, respectively. ([#637])
- **util**: **Breaking Change** `Either` now requires its two services to have the same error type. ([#637])
- **util**: **Breaking Change** `Either` no longer implemenmts `Future`. ([#637])
- **buffer**: **Breaking Change** `Buffer<S, Request>` is now generic over `Buffer<Request, S::Future>.` ([#654])

see:

* <tower-rs/tower#584>
* <tower-rs/tower#681>
* <tower-rs/tower#703>
* <tower-rs/tower#637>
* <tower-rs/tower#654>

the `Either` trait bounds are particularly impactful for us. because
this runs counter to how we treat errors (skewing towards boxed errors,
in general), we temporarily vendor a version of `Either` from the 0.4
release, whose variants have been renamed to match the 0.5 interface.

updating to box the inner `A` and `B` services' errors, so we satiate
the new `A::Error = B::Error` bounds, can be addressed as a follow-on.
that's intentionally left as a separate change, due to the net size of
our patchset between this branch and #3504.

* <tower-rs/tower@v0.4.x...master>
* <https://github.com/tower-rs/tower/blob/master/tower/CHANGELOG.md>

this work is based upon #3504. for more information, see:

* linkerd/linkerd2#8733
* #3504

Signed-off-by: katelyn martin <kate@buoyant.io>
X-Ref: tower-rs/tower#815
X-Ref: tower-rs/tower#817
X-Ref: tower-rs/tower#818
X-Ref: tower-rs/tower#819
cratelyn added a commit to linkerd/linkerd2-proxy that referenced this pull request Mar 14, 2025
this commit updates our tower dependency from 0.4 to 0.5.

note that this commit does not affect the `tower-service` and
`tower-layer` crates, reëxported by `tower` itself. the `Service<T>`
trait and the closely related `Layer<S>` trait have not been changed.

the `tower` crate's utilities have changed in various ways, some of
particular note for the linkerd2 proxy. see these items, excerpted from
the tower changelog:

- **retry**: **Breaking Change** `retry::Policy::retry` now accepts `&mut Req` and `&mut Res` instead of the previous mutable versions. This
  increases the flexibility of the retry policy. To update, update your method signature to include `mut` for both parameters. ([tower-rs/tower#584])
- **retry**: **Breaking Change** Change Policy to accept &mut self ([tower-rs/tower#681])
- **retry**: **Breaking Change** `Budget` is now a trait. This allows end-users to implement their own budget and bucket implementations. ([tower-rs/tower#703])
- **util**: **Breaking Change** `Either::A` and `Either::B` have been renamed `Either::Left` and `Either::Right`, respectively. ([tower-rs/tower#637])
- **util**: **Breaking Change** `Either` now requires its two services to have the same error type. ([tower-rs/tower#637])
- **util**: **Breaking Change** `Either` no longer implemenmts `Future`. ([tower-rs/tower#637])
- **buffer**: **Breaking Change** `Buffer<S, Request>` is now generic over `Buffer<Request, S::Future>.` ([tower-rs/tower#654])

see:

* <tower-rs/tower#584>
* <tower-rs/tower#681>
* <tower-rs/tower#703>
* <tower-rs/tower#637>
* <tower-rs/tower#654>

the `Either` trait bounds are particularly impactful for us. because
this runs counter to how we treat errors (skewing towards boxed errors,
in general), we temporarily vendor a version of `Either` from the 0.4
release, whose variants have been renamed to match the 0.5 interface.

updating to box the inner `A` and `B` services' errors, so we satiate
the new `A::Error = B::Error` bounds, can be addressed as a follow-on.
that's intentionally left as a separate change, due to the net size of
our patchset between this branch and #3504.

* <tower-rs/tower@v0.4.x...master>
* <https://github.com/tower-rs/tower/blob/master/tower/CHANGELOG.md>

this work is based upon #3504. for more information, see:

* linkerd/linkerd2#8733
* #3504

Signed-off-by: katelyn martin <kate@buoyant.io>
X-Ref: tower-rs/tower#815
X-Ref: tower-rs/tower#817
X-Ref: tower-rs/tower#818
X-Ref: tower-rs/tower#819
cratelyn added a commit to linkerd/linkerd2-proxy that referenced this pull request Mar 18, 2025
this commit updates our tower dependency from 0.4 to 0.5.

note that this commit does not affect the `tower-service` and
`tower-layer` crates, reëxported by `tower` itself. the `Service<T>`
trait and the closely related `Layer<S>` trait have not been changed.

the `tower` crate's utilities have changed in various ways, some of
particular note for the linkerd2 proxy. see these items, excerpted from
the tower changelog:

- **retry**: **Breaking Change** `retry::Policy::retry` now accepts `&mut Req` and `&mut Res` instead of the previous mutable versions. This
  increases the flexibility of the retry policy. To update, update your method signature to include `mut` for both parameters. ([tower-rs/tower#584])
- **retry**: **Breaking Change** Change Policy to accept &mut self ([tower-rs/tower#681])
- **retry**: **Breaking Change** `Budget` is now a trait. This allows end-users to implement their own budget and bucket implementations. ([tower-rs/tower#703])
- **util**: **Breaking Change** `Either::A` and `Either::B` have been renamed `Either::Left` and `Either::Right`, respectively. ([tower-rs/tower#637])
- **util**: **Breaking Change** `Either` now requires its two services to have the same error type. ([tower-rs/tower#637])
- **util**: **Breaking Change** `Either` no longer implemenmts `Future`. ([tower-rs/tower#637])
- **buffer**: **Breaking Change** `Buffer<S, Request>` is now generic over `Buffer<Request, S::Future>.` ([tower-rs/tower#654])

see:

* <tower-rs/tower#584>
* <tower-rs/tower#681>
* <tower-rs/tower#703>
* <tower-rs/tower#637>
* <tower-rs/tower#654>

the `Either` trait bounds are particularly impactful for us. because
this runs counter to how we treat errors (skewing towards boxed errors,
in general), we temporarily vendor a version of `Either` from the 0.4
release, whose variants have been renamed to match the 0.5 interface.

updating to box the inner `A` and `B` services' errors, so we satiate
the new `A::Error = B::Error` bounds, can be addressed as a follow-on.
that's intentionally left as a separate change, due to the net size of
our patchset between this branch and #3504.

* <tower-rs/tower@v0.4.x...master>
* <https://github.com/tower-rs/tower/blob/master/tower/CHANGELOG.md>

this work is based upon #3504. for more information, see:

* linkerd/linkerd2#8733
* #3504

Signed-off-by: katelyn martin <kate@buoyant.io>
X-Ref: tower-rs/tower#815
X-Ref: tower-rs/tower#817
X-Ref: tower-rs/tower#818
X-Ref: tower-rs/tower#819
cratelyn added a commit to linkerd/linkerd2-proxy that referenced this pull request Mar 21, 2025
this commit updates our tower dependency from 0.4 to 0.5.

note that this commit does not affect the `tower-service` and
`tower-layer` crates, reëxported by `tower` itself. the `Service<T>`
trait and the closely related `Layer<S>` trait have not been changed.

the `tower` crate's utilities have changed in various ways, some of
particular note for the linkerd2 proxy. see these items, excerpted from
the tower changelog:

- **retry**: **Breaking Change** `retry::Policy::retry` now accepts `&mut Req` and `&mut Res` instead of the previous mutable versions. This
  increases the flexibility of the retry policy. To update, update your method signature to include `mut` for both parameters. ([tower-rs/tower#584])
- **retry**: **Breaking Change** Change Policy to accept &mut self ([tower-rs/tower#681])
- **retry**: **Breaking Change** `Budget` is now a trait. This allows end-users to implement their own budget and bucket implementations. ([tower-rs/tower#703])
- **util**: **Breaking Change** `Either::A` and `Either::B` have been renamed `Either::Left` and `Either::Right`, respectively. ([tower-rs/tower#637])
- **util**: **Breaking Change** `Either` now requires its two services to have the same error type. ([tower-rs/tower#637])
- **util**: **Breaking Change** `Either` no longer implemenmts `Future`. ([tower-rs/tower#637])
- **buffer**: **Breaking Change** `Buffer<S, Request>` is now generic over `Buffer<Request, S::Future>.` ([tower-rs/tower#654])

see:

* <tower-rs/tower#584>
* <tower-rs/tower#681>
* <tower-rs/tower#703>
* <tower-rs/tower#637>
* <tower-rs/tower#654>

the `Either` trait bounds are particularly impactful for us. because
this runs counter to how we treat errors (skewing towards boxed errors,
in general), we temporarily vendor a version of `Either` from the 0.4
release, whose variants have been renamed to match the 0.5 interface.

updating to box the inner `A` and `B` services' errors, so we satiate
the new `A::Error = B::Error` bounds, can be addressed as a follow-on.
that's intentionally left as a separate change, due to the net size of
our patchset between this branch and #3504.

* <tower-rs/tower@v0.4.x...master>
* <https://github.com/tower-rs/tower/blob/master/tower/CHANGELOG.md>

this work is based upon #3504. for more information, see:

* linkerd/linkerd2#8733
* #3504

Signed-off-by: katelyn martin <kate@buoyant.io>
X-Ref: tower-rs/tower#815
X-Ref: tower-rs/tower#817
X-Ref: tower-rs/tower#818
X-Ref: tower-rs/tower#819
cratelyn added a commit to linkerd/linkerd2-proxy that referenced this pull request Mar 21, 2025
* chore(deps)!: upgrade to tower 0.5

this commit updates our tower dependency from 0.4 to 0.5.

note that this commit does not affect the `tower-service` and
`tower-layer` crates, reëxported by `tower` itself. the `Service<T>`
trait and the closely related `Layer<S>` trait have not been changed.

the `tower` crate's utilities have changed in various ways, some of
particular note for the linkerd2 proxy. see these items, excerpted from
the tower changelog:

- **retry**: **Breaking Change** `retry::Policy::retry` now accepts `&mut Req` and `&mut Res` instead of the previous mutable versions. This
  increases the flexibility of the retry policy. To update, update your method signature to include `mut` for both parameters. ([tower-rs/tower#584])
- **retry**: **Breaking Change** Change Policy to accept &mut self ([tower-rs/tower#681])
- **retry**: **Breaking Change** `Budget` is now a trait. This allows end-users to implement their own budget and bucket implementations. ([tower-rs/tower#703])
- **util**: **Breaking Change** `Either::A` and `Either::B` have been renamed `Either::Left` and `Either::Right`, respectively. ([tower-rs/tower#637])
- **util**: **Breaking Change** `Either` now requires its two services to have the same error type. ([tower-rs/tower#637])
- **util**: **Breaking Change** `Either` no longer implemenmts `Future`. ([tower-rs/tower#637])
- **buffer**: **Breaking Change** `Buffer<S, Request>` is now generic over `Buffer<Request, S::Future>.` ([tower-rs/tower#654])

see:

* <tower-rs/tower#584>
* <tower-rs/tower#681>
* <tower-rs/tower#703>
* <tower-rs/tower#637>
* <tower-rs/tower#654>

the `Either` trait bounds are particularly impactful for us. because
this runs counter to how we treat errors (skewing towards boxed errors,
in general), we temporarily vendor a version of `Either` from the 0.4
release, whose variants have been renamed to match the 0.5 interface.

updating to box the inner `A` and `B` services' errors, so we satiate
the new `A::Error = B::Error` bounds, can be addressed as a follow-on.
that's intentionally left as a separate change, due to the net size of
our patchset between this branch and #3504.

* <tower-rs/tower@v0.4.x...master>
* <https://github.com/tower-rs/tower/blob/master/tower/CHANGELOG.md>

this work is based upon #3504. for more information, see:

* linkerd/linkerd2#8733
* #3504

Signed-off-by: katelyn martin <kate@buoyant.io>
X-Ref: tower-rs/tower#815
X-Ref: tower-rs/tower#817
X-Ref: tower-rs/tower#818
X-Ref: tower-rs/tower#819

* fix(stack/loadshed): update test affected by tower-rs/tower#635

this commit updates a test that was affected by breaking changes in
tower's `Buffer` middleware. see this excerpt from the description of
that change:

> I had to change some of the integration tests slightly as part of this
> change. This is because the buffer implementation using semaphore
> permits is _very subtly_ different from one using a bounded channel. In
> the `Semaphore`-based implementation, a semaphore permit is stored in
> the `Message` struct sent over the channel. This is so that the capacity
> is used as long as the message is in flight. However, when the worker
> task is processing a message that's been recieved from the channel,
> the permit is still not dropped. Essentially, the one message actively
> held by the worker task _also_ occupies one "slot" of capacity, so the
> actual channel capacity is one less than the value passed to the
> constructor, _once the first request has been sent to the worker_. The
> bounded MPSC changed this behavior so that capacity is only occupied
> while a request is actually in the channel, which broke some tests
> that relied on the old (and technically wrong) behavior.

bear particular attention to this:

> The bounded MPSC changed this behavior so that capacity is only
> occupied while a request is actually in the channel, which broke some
> tests that relied on the old (and technically wrong) behavior.

that pr adds an additional message to the channel in tests exercising
the laod-shedding behavior, on account of the previous (incorrect)
behavior.

https://github.com/tower-rs/tower/pull/635/files#r797108274

this commit performs the same change for our corresponding test, adding
an additional `ready()` call before we hit the buffer's limit.

Signed-off-by: katelyn martin <kate@buoyant.io>

* review: use vendored `Either` for consistency

#3744 (comment)

Signed-off-by: katelyn martin <kate@buoyant.io>

---------

Signed-off-by: katelyn martin <kate@buoyant.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-util Area: The tower "util" module
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Switch from pin-project to pin-project-lite Consider making Either uniform with futures::Either
4 participants