-
Notifications
You must be signed in to change notification settings - Fork 78
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
Restructure HttpServer's API #81
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for taking this on! I've got some thoughts below on the new shutdown interfaces.
Two other things to do once we're settled on the interface changes:
- This will obviously be a breaking change. Dropshot doesn't have much of a release process yet, but we do have several consumers now, so I want to start doing one. Could you draft a few sentences explaining to a consumer how to update their program for the new API? You can just leave that here in the PR and I'll add that to the changelog when I create one.
- Although this is a separate repo and we do have other consumers now, the expectation is that Dropshot is still pretty tightly coupled to oxide-api-prototype. Could you try updating oxide-api-prototype for these changes? That will be good validation that the new interface still works where we need it.
Thanks again!
Here's my attempt:
As an example: // Old Version:
let mut server = HttpServer::new( /* Arguments are the same between versions */ )
.map_err(|error| format!("failed to start server: {}", error))?;
let server_task = server.run();
server.wait_for_shutdown(server_task).await;
// New Version
let server = HttpServerStarter::new( /* Arguments are the same between versions */ )
.map_err(|error| format!("failed to start server: {}", error))?
.start();
server.await;
Sure, I have a branch here: oxidecomputer/omicron#34 Won't work on GitHub until this is merged, but locally, this passes all tests and simplifies the code slightly. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for doing this. This is much cleaner than what was in master before. This was probably some of the earliest code in Dropshot and some of my own earliest Rust code! I had a couple of small questions but they're not blockers.
Thanks also for updating oxide-api-prototype. That code look cleaner too.
Lastly, could you update CHANGELOG.adoc for this so that other people using Dropshot know how to update their code?
Before merging, I think it might be worthwhile having a discussion about the operation of the server future.
I think it might make more sense to:
Although this wouldn't explicitly change the API, it would change expectations, in that it would require callers to explicitly await the server. let server = HttpServerStarter(...).start();
// Users expect the server to be running here, without additional prompting.
client.make_request().await;
server.close().await; However, removing the "spawned tokio task" from the internal representation would require users to do the following: let server = HttpServerStarter(...).start();
// Users should not expect the server to respond to requests unless
// it is await-ed (as it is here).
select! {
_ = client.make_request().fuse() => ...,
_ = server => ...,
}
server.close().await; Do you have an opinion here? |
Good catch. This change feels like it would make the API unnecessarily harder to use. In practice I think most consumers will want this to be in a separate task. It seems annoying to make them all do that individually. I hear what you're saying about this violating the principle of least surprise about futures, though. Maybe this is an argument for decoupling the server from the wait-for-shutdown future. As I understand it, this isn't so much a problem in master today because |
¯\(ツ)/¯ I am a little split - I think that a "needs-to-be-polled-to-do-anything" future would be more verbose, but also more explicit.
I'm not sure I understand this - I was thinking that we'd have If we split the future in two, when would the "running server" future complete? Why would that be different from the "wait for future" case?
I think my takeaway here is going to be:
|
TL;DR: Split HttpServer in twain to make the state-machine logic a little better.
Old API:
❌ wait_for_shutdown
required an external "join_handle" - it never actually acted on the
self
object of the server, and there are no guarantees the providedJoinHandle
matches the one fromrun
.❌ run can be invoked multiple times, which would cause a panic on the second invocation.
New API:
✔️ Invoking
start
consumesself
, and may only be invoked once, creating a newHttpServer
object which represents the running server.✔️ The running HttpServer may only be terminated once by calling
close
(taking no arguments), which signals the server to stop immediately, and waits for it to exit.✔️ The HttpServer implements
Future
, and may beawait
-ed to query the completion of the server.