-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Connection Abstractions #1793
Comments
Want: Pipelines support |
It's there! |
MySqlConnector would probably use this. It currently has a homemade version of something similar: the base TCP socket or Unix domain socket connection can optionally have a SslStream-based connection layered on top of it (if the connection is secure), and either of those can have protocol compression layered on top of them. (I say "maybe" because the MySQL protocol has some quirks that make it not seamlessly composable.) There are also currently server load-balancing features that might be more cleanly expressed as another The connection factory could be exposed to advanced users for many of the same scenarios you've mentioned for |
Not 3rd party, but Orleans uses the Bedrock abstractions and we will use this when it becomes available. We can help validate the APIs early, if that may be useful to you. Having both Stream and Pipe exposed is a little confusing to me - how will that work? |
@bgrainger Can you give more detail? The goal is to have a very flexible model for composability, and it would be great to know if there's a flaw we can address.
@ReubenBond Stream and Pipelines are both in broad use, and this is a way for us to enable wide adoption of the abstraction. The intended behavior is that if only one is implemented, the other should wrap that. To avoid usage errors, once one propery is used calling the other will throw an exception.
Awesome! We have some of our own validation planned already; I will ping you with details once it's in that stage. |
This is complicated to explain briefly, but I'll try. Each packet in the MySQL protocol has a monotonically increasing "sequence number" in its header. Using compression in the protocol takes an arbitrary number of uncompressed packets, compresses them all together, then writes them as the payload of one or more compressed packets, which have their own sequence numbers. You might expect that this compression would be transparent to the underlying data, but it's not. When the uncompressed packets require more than one compressed packet (because the compressed data exceeds the 2^24 byte size limit), then the sequence numbers of the uncompressed packets get reset, starting from the sequence number of the compressed packet that contains them. For example, if we have uncompressed packets 1-80, and packets 1-50 become the (compressed) payload of compressed packet 1, and packets 51-80 become the (compressed) payload of compressed packet 2, then packets 51-80 get renumbered 2-31 before compression. So there has to be coordination between the compression stream and the layers around it. |
I started testing out the non-multiplexed connection abstractions in Kestrel's Socket transport, and here are a few things I noticed.
I haven't updated Kestrel's HttpsConnectionMiddleware to be a IConnectionListenerFactory decorator, but I could try that if people are interested. I haven't tried out the multiplexed interfaces, but I think that having IMultiplexedConnection implement IConnectionStream will be problematic since neither the Pipe nor Stream properties will be usable. |
Thanks for taking time to prove this out.
I explicitly left this out to push a compositional model, but am interested in where it breaks down. Can you give some specific examples of how you'd use a setter?
FWIW we have loosely agreed to get rid of the type-based model all-together and instead have the property key just be Can you give an example of where a generic version helps? We can revisit that decision if you've found it makes a significant difference.
Beyond some Right now, it's intentionally left undefined: our experience working with QUIC revealed a very TCP-centric mental model for aborting. Trying to make an abort API generic between just those two doesn't seem possible, let alone considering any other random protocol (does a UDS have compatible concepts of abort/reset/etc.?). Do you feel like a feature will work for now (at least, for this first pass in .NET 5), or do you think it's worth some brainstorming?
See above :)
Agreed, it makes sense to add a property bag there. |
Given that filters already need to wrap the IConnection to replace the Stream or Pipe, this isn't actually so bad by comparison. I think the pain came from adapting existing Kestrel code that expects a more mutable IConnection/ConnectionContext. Kestrel's HttpsConnectionMiddleware adds ITlsApplicationProtocolFeature to ConnectionProperties. To do this HttpsConnectionMiddleware needs to wrap IConnection, IConnection.Stream/Pipe and IConnection.ConnectionProperties with custom implementations. This is more painful than just setting the Stream/Pipe on a mutable IConnection and adding a property to a mutable IConnectionProperties. This is definitely not a huge issue though. I think it's mostly a matter of preference. With the wrapping approach, you don't need lines like
It makes a bigger difference the more features/properties there are and the further down the
Kestrel doesn't special-case exception types thrown from the transport except to log ConnectionResetExceptions (which are custom to Kestrel transports) at a lower level than most other IOExceptions. Kestrel is probably somewhat unusual where it expects writing to the transport to never fail even if the connection has been closed by the client or aborted by the server app. The only way Kestrel can observe the connection failing is either by observing an exception when reading from the transport or a Kestrel will probably have to adapt any shared transport to have the unusual write-never-throws behavior, but I do think we should add a
Yeah. Abort() not taking an error code is problematic for QUIC. In Kestrel we added a new Abort() that takes an error code and default to |
Ahh clever. |
I also took a look at rewriting Kestrel’s HttpsConnectionMiddleware to use the proposed abstraction. This went well aside from one key difference from the proposed SslConnectionListenerFactory which is that HttpsConnectionMiddleware wraps the IConnectionListener instead of IConnectionListenerFactory. The reason for this is that Kestrel needs to be able to configure connection middleware on a per-remote-endpoint basis. We could theoretically have a singleton SslConnectionListenerFactory keep track of mappings between each remote endpoint and its configuration, but Kestrel already easily does this, so I see no reason to make connection middleware do this given each middleware would likely have to come up with its own hand-rolled solution. When rewriting the HttpsConnectionMiddleware, I wound up liking the wrapping the IConnection more than setting properties on ConnectionContext like we did before. It’s a little more verbose (about 33% more), but it really encourages writing low-allocation code, and it eliminates a class of bug where middleware doesn’t properly reset a property when exiting. If the middleware was simpler, I might find wrapping the IConnection a bit more onerous. One thing that was really annoying when writing both the transport and middleware was writing the logic to throw when accessing the Stream after the IDuplexPipe and vice versa. This logic needs to be written in the transport, and then rewritten at each layer that wraps the Stream/IDuplexPipe. Maybe we should write some built in type that you can construct with either a Stream or an IDuplexPipe, exposes both types as properties that throws if the other property was accessed first. One thing I really like is this design allows for the possibility of writing a connection throttling middleware since it allows intercepting the call to IConnectionListener.AcceptAsync(). If we use this, we won’t need to add yet another extensibility point to Kestrel for this functionality as suggested in dotnet/aspnetcore#13295. |
API in issue updated with feedback from @stephentoub, @davidfowl, and @halter73
CC @JamesNK Results from
|
Why do we need sync APIs and which ones exactly do you mean? |
So here's the driving concern: how do we get these interfaces into existing APIs that have sync support? For instance, We also see It leads us with a few options:
I don't like any of these options, but we need to pick one. I'm leaning toward adding synchronous support being the least worst. |
Then we never move forward because someone somewhere is pretending that async networking is actually sync. |
If needed, have a different set of interfaces Also then support can be determined at compile time; rather than either throwing or hand waving at runtime. |
That is a better idea because then someone writing a transport doesn't have to provide the sync methods. |
I think adding blocking networking APIs adds a lot of complexity for little benefit. As @Drawaes says, networking inherently async. I understand that the OS exposes blocking APIs, but I don't think that suddenly makes the blocking OK. We should always be telling developers to do non-blocking I/O if they want to build a highly scalable apps. Blocking I/O still leads to threadpool starvation much faster than non-blocking I/O even without any sync-over-async. For developers who are porting low-traffic line-of-business apps that have always managed doing blocking I/O without threadpool starvation, I think sync-over-async is likely fine. We could always go back and add |
Lots of expert-focused opinions here. I think we can all agree async is what we'd prefer, but lets make sure we are looking at it from other perspectives as well. I don't view this as an expert-only API. One of the goals of this is adoption into existing libraries. Lack of blocking APIs will hurt that goal. We also have data (as outlined in HttpClient sync issue) that many users do not understand effective async, and that they still desire high performance sync APIs. Some are okay with horizontal rather than vertical scalability if it simplifies their code. There is value in enabling this scenario. |
Lets be specific though, lets talk about specific libraries and get some code samples. You might be right but lets get some concrete data about which libraries. Can you outline the sync APIs you want to add? |
Both need a sync We can implement
|
Not sure about |
I don't quite understand the benefits here. Improved discovery ability how? Strongly typed how? Can you show a before and after example of how it improved each of these? |
Closing this issue (the PR has been merged). Remaining work is tracked in #40044. |
I have simple task - detect remote IP address to which http client is connected. I don't want to DNS resolve, but need exactly remote IP. I have found solution for .net - https://stackoverflow.com/questions/6655713/how-to-get-ip-address-of-the-server-that-httpwebrequest-connected-to but it doesn't work as it is for dot net, but not core. Answer please, my simple question. |
Reopening this since we reverted it in 5.0 |
Will this include some form of |
How many ProtocolType are we supposed to support? I would prefer: TCP, UDP, HTTP, WebSocket, gRPC, Protobuff, etc. In application point of view, they are all communication level. |
Understanding possible .NET 6 features/roadmap has not been released yet, anyone know when we might start seeing focus back on this API? |
I'm going to close this. If we decide to revisit it later, it can be re-opened, but at present it's not actionable. Thanks. |
System.Net.Connections
Connections is an abstraction for composable connection establishment. It aims to improve layering separation and provide a standard extensibility model for making network connections.
Connections targets client/server implementations, and their users with advanced needs to plug in custom functionality. The latter is a heavily requested feature for
HttpClient
, and the Kestrel team has a handful of examples of users taking advantage of this pattern.Connections brings .NET into parity with “modern” transport models such as Go’s dialers and Netty's channels. ASP.NET has a similar set of interfaces (“Bedrock Transports”) that this would supersede.
Basic API usage
At its most basic usage, System.Net.Connections is an abstraction over
Socket.Accept
andSocket.Connect
. For thisSocket
usage today:The equivalent with System.Net.Connections is:
Composability
Composability has been modeled after
Stream
. For instance:While Streams compose the raw byte stream, Connections compose the establishment of that stream:
Beyond things like TLS and sockets, library implementation can separate some connection establishment logic into clean layers, as seen in
HttpClient
here:Extensibility
Components can expose a connection factory to the user as an extension model. As an example, a user might implement a bandwidth monitoring extension for
HttpClient
:Requests for
HttpClient
extensibilityAs an example of how users would make use of this, we’ve seen many requests for extensibility in
HttpClient
:Go example
Here we use a SOCKS proxy with Go's HTTP client, via a dialer:
Netty example
And use Netty's channel pipelines to do the same:
Connection Properties
Streams have implementation-specific functionality and properties. For instance,
Socket.Shutdown()
and various properties onSslStream
. This means that, even when composing them, the user must still keep track of multiple layers:This makes it challenging to build extensibility into a library's design:
Stream
, but instead every piece of layer that they need to override.With connections, this is cleaned up by allowing each layer in a composed connection to expose, override, or hide features from the previous layer. Abstractions can be used to avoid exposing specific implementation.
Here, our TLS implementation exposes a property while passing through unknown types to previous layers.
The type keys available in properties are not discoverable :( documentation must be read.
When using an established connection, we then extract it as well as a
Socket
property which was exposed by a previous sockets layer:Connection Establishment properties
Property bags are also used when establishing new connections, to allow each layer to decide how to establish the connection. For instance,
HttpClient
can implement a factory that tests if HTTPS is being used and inject TLS into a connection:Usage Examples
Establish a new connection and send data
Listen for a new connection and receive data
Proposed API
Some thoughts:
EndPoint
parameters/properties into theIConnectionProperties
.Additional APIs
This integrates the above interfaces with HTTP, Sockets, TLS
Thoughts and Questions
HttpClient
and did not observe this complexity. I believe there are not a large number of use cases one would want to introduce layers for, and so layering is unlikely to become complex enough for this to be a problem in most apps.Func<(string host, int port), Stream>
)?HttpClient
has a strong need for a connection establishment abstraction. All but one need could be solved by the simpler one.SmtpClient
,SqlConnection
, and so on to make use of this?Future QUIC amendments
QUIC is not yet out of draft status, so QUIC-specific APIs were not a focus for this proposal. However, current knowledge of QUIC did help shape the APIs to make adapting it easier. Here are some thoughts based on current experience:
QuicStream.Dispose
should do, for instance.IConnectionStream
andIConnection
are split in the proposed API specifically to later support anIMultiplexedConnection
that creates multipleIConnectionStream
.IMultiplexedConnection
, we might choose to merge the two APIs and haveIConnection
force users to explicitly open the one bidirectionalIConnectionStream
for the connection.A QUIC extension to this might look like:
Alternately, the
IConnection
andIMultiplexedConnection
APIs might be merged, reducing surface area significantly. The TCP version would simply throw if opening/accepting more than once. This API might look like:However, beyond API surface reduction there isn't clear practical reason to merge them. Given how QUIC is significantly different from TCP, it isn't clear that libraries would see correct reuse of filtering
IConnectionFactory
implementations.The text was updated successfully, but these errors were encountered: