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:
- BoxStream and BoxFuture in Resolver has always been temporary hack, let's replace them with associated types
- We should split out
PollResolver
fromResolver
- What has been done in default method
subscribe
should be done inPollAdapter
, so it's configurable (updates and poll interval) 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.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, useFuturesUnordered
)Name
type is a structure
The Problems It Solves
- Errors in subscriptions streams were pain. It's was unclear when it's valid to return error.
- 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.
- Middlewares can now be made without virtual call overhead
- Updating
Router
configuration on the fly should work - 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.
- 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, likeHashMap<NameBuf, Address>
it's not possible toBorrow<Name>
fromNameBuf
(ifNameBuf(String, u16)
andName(&str, u16)
), and no clear design for this was foreseen. It turns out, that it's possible to useOwningRef
for that, with not that big overhead.
More Enhancements To Do
- 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.
- We should add an abstraction that accepts a
Stream<Item=Vec<Name>,_>
rather than aVec<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
- Associated errors? Probably it's too much pain.
- Should name structure have public fields? Should
NameBuf
be implemented? MaybeName
/NameBuf
should haveArc<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 port1234
to the IP thatexample.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 toexample.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.