tailhook / abstract-ns

Abstract name service traits for rust

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Another round of changes

tailhook opened this issue · comments

Just quick overview of new traits and helpers (with many details omitted):

struct Name<'x>(&'x str, Option<u16>);
trait PollResovler {
  type Future: Future<Item=Address, Error>;
  fn resolve(&self, name: Name) -> Self::Future;
}
trait Resolver: PollResolver {
  type Stream: Stream<Item=Address, Void>;
  fn subscribe(&self, name: Name) -> Self::Stream;
}
struct PollAdapter<R: PollResolver>(R);
impl PollResolver for PollAdapter {...}
impl Resolver for PollAdapter {...}

struct Router {...}
impl PollResolver for Router {
  type Future: oneshot::Receiver<Address>;
}
impl Resolver for Router {
  type Stream: tip_channel::Receiver<Addresss>;
}

Details:

  1. BoxStream and BoxFuture in Resolver has always been temporary hack, let's replace them with associated types
  2. We should split out PollResolver from Resolver
  3. What has been done in default method subscribe should be done in PollAdapter, so it's configurable (updates and poll interval)
  4. Resolver::subscribe must return infallible stream, we should consider using void crate, or put our own void type or use one from futures when that is added. This caused pain previously. The idea here is to retry resolving name as long as stream is used.
  5. Router::subscribe (the structure not trait) should spawn a stream from original resolver stream and connect it through something like a Tip channel, either from futures crate or vendored type. (or alternatively, use FuturesUnordered)
  6. Name type is a structure

The Problems It Solves

  1. Errors in subscriptions streams were pain. It's was unclear when it's valid to return error.
  2. Streams become more and more complex, and connection pool should poll the DNS subscription stream on every wake up (this is how futures work). So let's make polled structure very cheap channel.
  3. Middlewares can now be made without virtual call overhead
  4. Updating Router configuration on the fly should work
  5. Using bounded or unbounded stream between router and consumer has it's own downsides (bounded: old value may be used at any time in future, unbounded: memory leak can occur), hence the Tip channel type.
  6. Parsing Name in the previous version was pain, as well as it wasn't clear that port might be specified. The original motivation was that if you create a cache, like HashMap<NameBuf, Address> it's not possible to Borrow<Name> from NameBuf (if NameBuf(String, u16) and Name(&str, u16)), and no clear design for this was foreseen. It turns out, that it's possible to use OwningRef for that, with not that big overhead.

More Enhancements To Do

  1. The Router should be able to update the configuration and recheck each connected stream against new domain routing table. Presumably, this requires managing streams by router and connecting them through channel like described above.
  2. We should add an abstraction that accepts a Stream<Item=Vec<Name>,_> rather than a Vec<Stream<Item=Address>> so that we can update the list of names connection pool does connect to, not just resolve already listed names (this allows better on-the-fly configuration reload in servers).

Questions

  1. Associated errors? Probably it's too much pain.
  2. Should name structure have public fields? Should NameBuf be implemented? Maybe Name/NameBuf should have Arc<String> because there is large chance that it will be moved between futures multiple times.

Tagging @srijs as you mentioned you have some thoughts. Also @seanmonstar for speaking about hyperium/hyper#1174

Well, I currently tend to make Name an Name(Arc<(String, u16)>), because putting it in future means name should be static. The only use case of having a borrowed name is quickly looking into an in-process cache.

On the other hand, a clone of the name will probably stay in Resolver and in many middlewares, I guess (including PollAdapter described above, and UnionStream). So making it cheaply cloneable looks like worth it. Unless somebody has a better idea.

Well, when working on the changes I'm messing with port in a wrong way, in particular:

  • example.org:1234 means to connect to port 1234 to the IP that example.org resolves to, but
  • _xmpp-server._tcp.example.org means to connect to the port whatever SRV record points to
  • On the other hand http://example.org means connect to example.org with default port, and solving this case too, was original intention.

At the end of the day, we should decouple concepts of default port and the port to connect to in some sensible way.

Okay, just merged into a master branch host_service_split. It splits resolver trait into four and I'm more or less satisfied with the result. Will publish on crates.io soon.