diff options
author | Yury Selivanov <yury@magic.io> | 2018-01-25 23:08:09 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-01-25 23:08:09 (GMT) |
commit | c9070d03f5169ad6e171e641b7fa8feab18bf229 (patch) | |
tree | 4bad875e6f68874a980e5289a45893f4335c5afb /Lib/asyncio | |
parent | 1aa094f74039cd20fdc7df56c68f6848c18ce4dd (diff) | |
download | cpython-c9070d03f5169ad6e171e641b7fa8feab18bf229.zip cpython-c9070d03f5169ad6e171e641b7fa8feab18bf229.tar.gz cpython-c9070d03f5169ad6e171e641b7fa8feab18bf229.tar.bz2 |
bpo-32662: Implement Server.start_serving() and Server.serve_forever() (#5312)
* bpo-32662: Implement Server.start_serving() and Server.serve_forever()
New methods:
* Server.start_serving(),
* Server.serve_forever(), and
* Server.is_serving().
Add 'start_serving' keyword parameter to loop.create_server() and
loop.create_unix_server().
Diffstat (limited to 'Lib/asyncio')
-rw-r--r-- | Lib/asyncio/base_events.py | 102 | ||||
-rw-r--r-- | Lib/asyncio/events.py | 48 | ||||
-rw-r--r-- | Lib/asyncio/unix_events.py | 12 |
3 files changed, 132 insertions, 30 deletions
diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py index e722cf2..94eb308 100644 --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -157,47 +157,106 @@ def _run_until_complete_cb(fut): class Server(events.AbstractServer): - def __init__(self, loop, sockets): + def __init__(self, loop, sockets, protocol_factory, ssl_context, backlog, + ssl_handshake_timeout): self._loop = loop - self.sockets = sockets + self._sockets = sockets self._active_count = 0 self._waiters = [] + self._protocol_factory = protocol_factory + self._backlog = backlog + self._ssl_context = ssl_context + self._ssl_handshake_timeout = ssl_handshake_timeout + self._serving = False + self._serving_forever_fut = None def __repr__(self): return f'<{self.__class__.__name__} sockets={self.sockets!r}>' def _attach(self): - assert self.sockets is not None + assert self._sockets is not None self._active_count += 1 def _detach(self): assert self._active_count > 0 self._active_count -= 1 - if self._active_count == 0 and self.sockets is None: + if self._active_count == 0 and self._sockets is None: self._wakeup() + def _wakeup(self): + waiters = self._waiters + self._waiters = None + for waiter in waiters: + if not waiter.done(): + waiter.set_result(waiter) + + def _start_serving(self): + if self._serving: + return + self._serving = True + for sock in self._sockets: + sock.listen(self._backlog) + self._loop._start_serving( + self._protocol_factory, sock, self._ssl_context, + self, self._backlog, self._ssl_handshake_timeout) + + def get_loop(self): + return self._loop + + def is_serving(self): + return self._serving + + @property + def sockets(self): + if self._sockets is None: + return [] + return list(self._sockets) + def close(self): - sockets = self.sockets + sockets = self._sockets if sockets is None: return - self.sockets = None + self._sockets = None + for sock in sockets: self._loop._stop_serving(sock) + + self._serving = False + + if (self._serving_forever_fut is not None and + not self._serving_forever_fut.done()): + self._serving_forever_fut.cancel() + self._serving_forever_fut = None + if self._active_count == 0: self._wakeup() - def get_loop(self): - return self._loop + async def start_serving(self): + self._start_serving() - def _wakeup(self): - waiters = self._waiters - self._waiters = None - for waiter in waiters: - if not waiter.done(): - waiter.set_result(waiter) + async def serve_forever(self): + if self._serving_forever_fut is not None: + raise RuntimeError( + f'server {self!r} is already being awaited on serve_forever()') + if self._sockets is None: + raise RuntimeError(f'server {self!r} is closed') + + self._start_serving() + self._serving_forever_fut = self._loop.create_future() + + try: + await self._serving_forever_fut + except futures.CancelledError: + try: + self.close() + await self.wait_closed() + finally: + raise + finally: + self._serving_forever_fut = None async def wait_closed(self): - if self.sockets is None or self._waiters is None: + if self._sockets is None or self._waiters is None: return waiter = self._loop.create_future() self._waiters.append(waiter) @@ -1059,7 +1118,8 @@ class BaseEventLoop(events.AbstractEventLoop): ssl=None, reuse_address=None, reuse_port=None, - ssl_handshake_timeout=None): + ssl_handshake_timeout=None, + start_serving=True): """Create a TCP server. The host parameter can be a string, in that case the TCP server is @@ -1149,12 +1209,14 @@ class BaseEventLoop(events.AbstractEventLoop): raise ValueError(f'A Stream Socket was expected, got {sock!r}') sockets = [sock] - server = Server(self, sockets) for sock in sockets: - sock.listen(backlog) sock.setblocking(False) - self._start_serving(protocol_factory, sock, ssl, server, backlog, - ssl_handshake_timeout) + + server = Server(self, sockets, protocol_factory, + ssl, backlog, ssl_handshake_timeout) + if start_serving: + server._start_serving() + if self._debug: logger.info("%r is serving", server) return server diff --git a/Lib/asyncio/events.py b/Lib/asyncio/events.py index 5c68d4c..7aa3de0 100644 --- a/Lib/asyncio/events.py +++ b/Lib/asyncio/events.py @@ -164,13 +164,39 @@ class AbstractServer: """Stop serving. This leaves existing connections open.""" raise NotImplementedError + def get_loop(self): + """Get the event loop the Server object is attached to.""" + raise NotImplementedError + + def is_serving(self): + """Return True if the server is accepting connections.""" + raise NotImplementedError + + async def start_serving(self): + """Start accepting connections. + + This method is idempotent, so it can be called when + the server is already being serving. + """ + raise NotImplementedError + + async def serve_forever(self): + """Start accepting connections until the coroutine is cancelled. + + The server is closed when the coroutine is cancelled. + """ + raise NotImplementedError + async def wait_closed(self): """Coroutine to wait until service is closed.""" raise NotImplementedError - def get_loop(self): - """ Get the event loop the Server object is attached to.""" - raise NotImplementedError + async def __aenter__(self): + return self + + async def __aexit__(self, *exc): + self.close() + await self.wait_closed() class AbstractEventLoop: @@ -279,7 +305,8 @@ class AbstractEventLoop: *, family=socket.AF_UNSPEC, flags=socket.AI_PASSIVE, sock=None, backlog=100, ssl=None, reuse_address=None, reuse_port=None, - ssl_handshake_timeout=None): + ssl_handshake_timeout=None, + start_serving=True): """A coroutine which creates a TCP server bound to host and port. The return value is a Server object which can be used to stop @@ -319,6 +346,11 @@ class AbstractEventLoop: will wait for completion of the SSL handshake before aborting the connection. Default is 10s, longer timeouts may increase vulnerability to DoS attacks (see https://support.f5.com/csp/article/K13834) + + start_serving set to True (default) causes the created server + to start accepting connections immediately. When set to False, + the user should await Server.start_serving() or Server.serve_forever() + to make the server to start accepting connections. """ raise NotImplementedError @@ -343,7 +375,8 @@ class AbstractEventLoop: async def create_unix_server( self, protocol_factory, path=None, *, sock=None, backlog=100, ssl=None, - ssl_handshake_timeout=None): + ssl_handshake_timeout=None, + start_serving=True): """A coroutine which creates a UNIX Domain Socket server. The return value is a Server object, which can be used to stop @@ -363,6 +396,11 @@ class AbstractEventLoop: ssl_handshake_timeout is the time in seconds that an SSL server will wait for the SSL handshake to complete (defaults to 10s). + + start_serving set to True (default) causes the created server + to start accepting connections immediately. When set to False, + the user should await Server.start_serving() or Server.serve_forever() + to make the server to start accepting connections. """ raise NotImplementedError diff --git a/Lib/asyncio/unix_events.py b/Lib/asyncio/unix_events.py index 9b9d004..a4d892a 100644 --- a/Lib/asyncio/unix_events.py +++ b/Lib/asyncio/unix_events.py @@ -250,7 +250,8 @@ class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop): async def create_unix_server( self, protocol_factory, path=None, *, sock=None, backlog=100, ssl=None, - ssl_handshake_timeout=None): + ssl_handshake_timeout=None, + start_serving=True): if isinstance(ssl, bool): raise TypeError('ssl argument must be an SSLContext or None') @@ -302,11 +303,12 @@ class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop): raise ValueError( f'A UNIX Domain Stream Socket was expected, got {sock!r}') - server = base_events.Server(self, [sock]) - sock.listen(backlog) sock.setblocking(False) - self._start_serving(protocol_factory, sock, ssl, server, - ssl_handshake_timeout=ssl_handshake_timeout) + server = base_events.Server(self, [sock], protocol_factory, + ssl, backlog, ssl_handshake_timeout) + if start_serving: + server._start_serving() + return server async def _sock_sendfile_native(self, sock, file, offset, count): |