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

Revisit the "optional nursery" pattern #31

Closed
mehaase opened this issue Sep 18, 2018 · 3 comments
Closed

Revisit the "optional nursery" pattern #31

mehaase opened this issue Sep 18, 2018 · 3 comments
Assignees

Comments

@mehaase
Copy link
Contributor

mehaase commented Sep 18, 2018

The client API was recently changed from a class to an async context manager function and the nursery argument was made optional. The server API should offer similar features:

  • Add a serve_websocket(…) async context manager to mirror the client's open_websocket(…).
  • The context manager yields a WebSocketServer instance.
  • Make nursery an optional argument to serve_websocket()
@mehaase mehaase self-assigned this Sep 28, 2018
@mehaase
Copy link
Contributor Author

mehaase commented Sep 28, 2018

After fiddling with some different API designs (and thinking about how to close a server—see #7), I decided that a context manager doesn't make sense for WebSocketServer, because:

  • A Trio server (i.e. serve_tcp(…)) cannot be closed; it must be cancelled.
  • A WebSocketServer should follow the same pattern: cancelled, not closed.
  • If WebSocketServer does not have any close method or any cleanup to perform, then it should not be a context manager.

I think the ideal server API looks like this:

server = WebSocketServer(…)
async with trio.open_nursery() as nursery:
    await nursery.start(server.listen)
    # any other stuff that runs alongside the server
    await trio.sleep_forever()

To end the server, the caller must use regular Trio cancellation, e.g. nursery.cancel_scope.cancel(). This approach shifts concern for cancelling handlers to the caller. The caller can choose to clean up each handler carefully (perhaps calling conn.close() in each one) or just cancelling the entire scope.

New issue scope:

  1. Add the ability to specify a nursery for the server. Create an internal nursery if one is not supplied.

mehaase added a commit that referenced this issue Sep 28, 2018
* New listen_in_nursery() API for server.
* Add unit tests for implicit vs explicit nursery.
* Update example server.
mehaase added a commit that referenced this issue Sep 28, 2018
This is more consistent with the Trio API.
@mehaase mehaase mentioned this issue Sep 28, 2018
@mehaase mehaase changed the title Redesign server API Revisit the "optional nursery" pattern Sep 29, 2018
@mehaase
Copy link
Contributor Author

mehaase commented Sep 29, 2018

After getting feedback on PR #34 from Nathaniel and John (both in the code review and also on Gitter), I am going to reject that PR and start over. I changed the title of this ticket to indicate that the "optional nursery" pattern needs to be revisited in the client (#20) and server.

Client nursery

The purpose of the "optional nursery" pattern is to simplify cases with lots of connections (i.e. deep nesting of context managers) or where connections are added and removed dynamically. Therefore, the context manager that I implemented in #28 missed the mark, because even though it has an optional nursery argument, it still requires async with syntax.

The API needs to have two functions: a context manager that creates an internal nursery, and a a regular async def that requires a nursery argument. (See the example on this StackOverflow answer.)

Server nursery

Unlike the client, the server doesn't have much need for the "optional nursery" pattern, because the server doesn't need to be launched in its own async with block. To stay consistent with Trio, however, the server should take an optional "handler nursery" and pass it down to serve_tcp(). If not None, then all handler tasks will be spawned inside of that "handler nursery".

While we're at it, each connection should be launched inside of a new nursery as well. I.e., the nursery tree for two connections might look like this:

* handler nursery
  * conn #1 nursery
    * reader task
    * handler task
  * conn #2 nursery
    * reader task
    * handler task

This makes it easy to cancel the tasks associated with a single connection without affecting other connections.

mehaase added a commit that referenced this issue Oct 1, 2018
In addition to open_websocket and open_websocket_url, which are
both context managers that do not have a nursery argument, I have
added connect_websocket() and connect_websocket_url(), which take
a nursery argument and are not context managers.
mehaase added a commit that referenced this issue Oct 1, 2018
This is passed down to serve_tcp(). Add tests to cover this feature.
@mehaase
Copy link
Contributor Author

mehaase commented Oct 1, 2018

This is done:

  • client supports optional nursery pattern
  • server does not support this pattern
  • server passes handler_nursery argument down to serve_tcp()

It turned out that the server already creates a new nursery for each connection, so no changes there.

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

No branches or pull requests

1 participant