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

feat: Make Endpoint::node_addr watchable and add trait Watcher & combinators #3045

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

matheus23
Copy link
Contributor

@matheus23 matheus23 commented Dec 13, 2024

Description

Goal of this PR was: make Endpoint::node_addr return something that can be watched, so you can use ep.node_addr()?.initialized().await? or ep.node_addr()?.get()? or ep.node_addr()?.updated().await?, etc.

Unfortunately, there's no Watchable<Option<NodeAddr>> anywhere, instead, a NodeAddr technically consists of a Watchable<Option<BTreeSet<DirectAddr>>> and a Watchable<Option<RelayUrl>>.

So this PR abstracts all the initialized, updated, etc. functionality of Watcher into a trait Watcher, and renames Watcher into DirectWatcher.
On top of this we can build the usual combinators like MapWatcher that transforms a Watcher<Value = A> into a Watcher<Value = B> using a function A -> B, or an OrWatcher that takes two watchers Watcher<Value = A> and a Watcher<Value = B> and creates a Watcher<Value = (A, B)>.

Breaking Changes

TODO

Notes & open questions

This PR would subsume #2732

You're probably wondering:

Why is Watcher a trait?

Two reasons:

  1. You need a way to abstract away the watcher functionality, if you want to be able to combine them in arbitrary ways, like e.g. MapWatcher or OrWatcher are doing. The alternative is to have a concrete type for special cases per-api, like e.g. a custom NodeAddrWatcher that implements essentially the MapWatcher and OrWatcher functionality inside. This works, but I'd argue is less comprehensible ("What is the difference between NodeAddrWatcher and DirectWatcher? They seem to have the same API?") and also less composable "What if I want to combine the Watchers further?".
  2. By depending on carefully-built composable structs like DirectWatcher, MapWatcher, etc. we can re-use the same default implementations of the Watcher trait, instead of having to repeat these implementations for all watchers, potentially allowing them to get out of sync or behave differently from one another.

What is the difference between Stream and Watcher?

The main difference is that Watcher combines a poll function for the next item with a function get(&self) -> Result<Self::Value, Disconnected> that allows you to always try to get the current value.

This allows the poll_updated function (which is very similar to Stream::poll_next) to gain some superpowers, e.g. the OrWatcher combinator can get the value of the other Watcher when one of its watchers has an update.

Change checklist

  • Self-review.
  • Documentation updates following the style guide, if relevant.
  • Tests if relevant.
  • All breaking changes documented.

@matheus23 matheus23 self-assigned this Dec 13, 2024
@matheus23 matheus23 marked this pull request as draft December 13, 2024 16:52
@matheus23 matheus23 force-pushed the matheus23/more-watchable branch from 3b22ade to 2529cde Compare December 13, 2024 16:53
/// Returns the currently held value.
/// This handle allows one to observe the latest state and be notified
/// of any changes to this value.
pub trait Watcher: Clone {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not a huge fan of the DirectWatcher name. I wonder if this name can be something else instead? Maybe ValueWatcher? Or even WatcherTrait? I don't know, just some random names that came out of my keyboard.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, not super happy either.

I would like to keep the clean trait Watcher name, since I'd rather have people use impl Watcher in their APIs than surface DirectWatcher... But of course that means no named types 😭

And I do think Value doesn't actually help, since every watcher is a "value" watcher.

SourceWatcher?

Not sure.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: 🏗 In progress
Development

Successfully merging this pull request may close these issues.

2 participants