Skip to content

Commit 13c10bf

Browse files
bpo-44011: New asyncio ssl implementation (#31275)
* bpo-44011: New asyncio ssl implementation Co-Authored-By: Andrew Svetlov <[email protected]> * fix warning * fix typo Co-authored-by: Andrew Svetlov <[email protected]>
1 parent 3be1a44 commit 13c10bf

12 files changed

+2478
-527
lines changed

Lib/asyncio/base_events.py

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ async def restore(self):
269269
class Server(events.AbstractServer):
270270

271271
def __init__(self, loop, sockets, protocol_factory, ssl_context, backlog,
272-
ssl_handshake_timeout):
272+
ssl_handshake_timeout, ssl_shutdown_timeout=None):
273273
self._loop = loop
274274
self._sockets = sockets
275275
self._active_count = 0
@@ -278,6 +278,7 @@ def __init__(self, loop, sockets, protocol_factory, ssl_context, backlog,
278278
self._backlog = backlog
279279
self._ssl_context = ssl_context
280280
self._ssl_handshake_timeout = ssl_handshake_timeout
281+
self._ssl_shutdown_timeout = ssl_shutdown_timeout
281282
self._serving = False
282283
self._serving_forever_fut = None
283284

@@ -309,7 +310,8 @@ def _start_serving(self):
309310
sock.listen(self._backlog)
310311
self._loop._start_serving(
311312
self._protocol_factory, sock, self._ssl_context,
312-
self, self._backlog, self._ssl_handshake_timeout)
313+
self, self._backlog, self._ssl_handshake_timeout,
314+
self._ssl_shutdown_timeout)
313315

314316
def get_loop(self):
315317
return self._loop
@@ -463,6 +465,7 @@ def _make_ssl_transport(
463465
*, server_side=False, server_hostname=None,
464466
extra=None, server=None,
465467
ssl_handshake_timeout=None,
468+
ssl_shutdown_timeout=None,
466469
call_connection_made=True):
467470
"""Create SSL transport."""
468471
raise NotImplementedError
@@ -965,6 +968,7 @@ async def create_connection(
965968
proto=0, flags=0, sock=None,
966969
local_addr=None, server_hostname=None,
967970
ssl_handshake_timeout=None,
971+
ssl_shutdown_timeout=None,
968972
happy_eyeballs_delay=None, interleave=None):
969973
"""Connect to a TCP server.
970974
@@ -1000,6 +1004,10 @@ async def create_connection(
10001004
raise ValueError(
10011005
'ssl_handshake_timeout is only meaningful with ssl')
10021006

1007+
if ssl_shutdown_timeout is not None and not ssl:
1008+
raise ValueError(
1009+
'ssl_shutdown_timeout is only meaningful with ssl')
1010+
10031011
if happy_eyeballs_delay is not None and interleave is None:
10041012
# If using happy eyeballs, default to interleave addresses by family
10051013
interleave = 1
@@ -1075,7 +1083,8 @@ async def create_connection(
10751083

10761084
transport, protocol = await self._create_connection_transport(
10771085
sock, protocol_factory, ssl, server_hostname,
1078-
ssl_handshake_timeout=ssl_handshake_timeout)
1086+
ssl_handshake_timeout=ssl_handshake_timeout,
1087+
ssl_shutdown_timeout=ssl_shutdown_timeout)
10791088
if self._debug:
10801089
# Get the socket from the transport because SSL transport closes
10811090
# the old socket and creates a new SSL socket
@@ -1087,7 +1096,8 @@ async def create_connection(
10871096
async def _create_connection_transport(
10881097
self, sock, protocol_factory, ssl,
10891098
server_hostname, server_side=False,
1090-
ssl_handshake_timeout=None):
1099+
ssl_handshake_timeout=None,
1100+
ssl_shutdown_timeout=None):
10911101

10921102
sock.setblocking(False)
10931103

@@ -1098,7 +1108,8 @@ async def _create_connection_transport(
10981108
transport = self._make_ssl_transport(
10991109
sock, protocol, sslcontext, waiter,
11001110
server_side=server_side, server_hostname=server_hostname,
1101-
ssl_handshake_timeout=ssl_handshake_timeout)
1111+
ssl_handshake_timeout=ssl_handshake_timeout,
1112+
ssl_shutdown_timeout=ssl_shutdown_timeout)
11021113
else:
11031114
transport = self._make_socket_transport(sock, protocol, waiter)
11041115

@@ -1189,7 +1200,8 @@ async def _sendfile_fallback(self, transp, file, offset, count):
11891200
async def start_tls(self, transport, protocol, sslcontext, *,
11901201
server_side=False,
11911202
server_hostname=None,
1192-
ssl_handshake_timeout=None):
1203+
ssl_handshake_timeout=None,
1204+
ssl_shutdown_timeout=None):
11931205
"""Upgrade transport to TLS.
11941206
11951207
Return a new transport that *protocol* should start using
@@ -1212,6 +1224,7 @@ async def start_tls(self, transport, protocol, sslcontext, *,
12121224
self, protocol, sslcontext, waiter,
12131225
server_side, server_hostname,
12141226
ssl_handshake_timeout=ssl_handshake_timeout,
1227+
ssl_shutdown_timeout=ssl_shutdown_timeout,
12151228
call_connection_made=False)
12161229

12171230
# Pause early so that "ssl_protocol.data_received()" doesn't
@@ -1397,6 +1410,7 @@ async def create_server(
13971410
reuse_address=None,
13981411
reuse_port=None,
13991412
ssl_handshake_timeout=None,
1413+
ssl_shutdown_timeout=None,
14001414
start_serving=True):
14011415
"""Create a TCP server.
14021416
@@ -1420,6 +1434,10 @@ async def create_server(
14201434
raise ValueError(
14211435
'ssl_handshake_timeout is only meaningful with ssl')
14221436

1437+
if ssl_shutdown_timeout is not None and ssl is None:
1438+
raise ValueError(
1439+
'ssl_shutdown_timeout is only meaningful with ssl')
1440+
14231441
if host is not None or port is not None:
14241442
if sock is not None:
14251443
raise ValueError(
@@ -1492,7 +1510,8 @@ async def create_server(
14921510
sock.setblocking(False)
14931511

14941512
server = Server(self, sockets, protocol_factory,
1495-
ssl, backlog, ssl_handshake_timeout)
1513+
ssl, backlog, ssl_handshake_timeout,
1514+
ssl_shutdown_timeout)
14961515
if start_serving:
14971516
server._start_serving()
14981517
# Skip one loop iteration so that all 'loop.add_reader'
@@ -1506,17 +1525,23 @@ async def create_server(
15061525
async def connect_accepted_socket(
15071526
self, protocol_factory, sock,
15081527
*, ssl=None,
1509-
ssl_handshake_timeout=None):
1528+
ssl_handshake_timeout=None,
1529+
ssl_shutdown_timeout=None):
15101530
if sock.type != socket.SOCK_STREAM:
15111531
raise ValueError(f'A Stream Socket was expected, got {sock!r}')
15121532

15131533
if ssl_handshake_timeout is not None and not ssl:
15141534
raise ValueError(
15151535
'ssl_handshake_timeout is only meaningful with ssl')
15161536

1537+
if ssl_shutdown_timeout is not None and not ssl:
1538+
raise ValueError(
1539+
'ssl_shutdown_timeout is only meaningful with ssl')
1540+
15171541
transport, protocol = await self._create_connection_transport(
15181542
sock, protocol_factory, ssl, '', server_side=True,
1519-
ssl_handshake_timeout=ssl_handshake_timeout)
1543+
ssl_handshake_timeout=ssl_handshake_timeout,
1544+
ssl_shutdown_timeout=ssl_shutdown_timeout)
15201545
if self._debug:
15211546
# Get the socket from the transport because SSL transport closes
15221547
# the old socket and creates a new SSL socket

Lib/asyncio/constants.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,17 @@
1515
# The default timeout matches that of Nginx.
1616
SSL_HANDSHAKE_TIMEOUT = 60.0
1717

18+
# Number of seconds to wait for SSL shutdown to complete
19+
# The default timeout mimics lingering_time
20+
SSL_SHUTDOWN_TIMEOUT = 30.0
21+
1822
# Used in sendfile fallback code. We use fallback for platforms
1923
# that don't support sendfile, or for TLS connections.
2024
SENDFILE_FALLBACK_READBUFFER_SIZE = 1024 * 256
2125

26+
FLOW_CONTROL_HIGH_WATER_SSL_READ = 256 # KiB
27+
FLOW_CONTROL_HIGH_WATER_SSL_WRITE = 512 # KiB
28+
2229
# The enum should be here to break circular dependencies between
2330
# base_events and sslproto
2431
class _SendfileMode(enum.Enum):

Lib/asyncio/events.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@ async def create_connection(
303303
flags=0, sock=None, local_addr=None,
304304
server_hostname=None,
305305
ssl_handshake_timeout=None,
306+
ssl_shutdown_timeout=None,
306307
happy_eyeballs_delay=None, interleave=None):
307308
raise NotImplementedError
308309

@@ -312,6 +313,7 @@ async def create_server(
312313
flags=socket.AI_PASSIVE, sock=None, backlog=100,
313314
ssl=None, reuse_address=None, reuse_port=None,
314315
ssl_handshake_timeout=None,
316+
ssl_shutdown_timeout=None,
315317
start_serving=True):
316318
"""A coroutine which creates a TCP server bound to host and port.
317319
@@ -352,6 +354,10 @@ async def create_server(
352354
will wait for completion of the SSL handshake before aborting the
353355
connection. Default is 60s.
354356
357+
ssl_shutdown_timeout is the time in seconds that an SSL server
358+
will wait for completion of the SSL shutdown procedure
359+
before aborting the connection. Default is 30s.
360+
355361
start_serving set to True (default) causes the created server
356362
to start accepting connections immediately. When set to False,
357363
the user should await Server.start_serving() or Server.serve_forever()
@@ -370,7 +376,8 @@ async def sendfile(self, transport, file, offset=0, count=None,
370376
async def start_tls(self, transport, protocol, sslcontext, *,
371377
server_side=False,
372378
server_hostname=None,
373-
ssl_handshake_timeout=None):
379+
ssl_handshake_timeout=None,
380+
ssl_shutdown_timeout=None):
374381
"""Upgrade a transport to TLS.
375382
376383
Return a new transport that *protocol* should start using
@@ -382,13 +389,15 @@ async def create_unix_connection(
382389
self, protocol_factory, path=None, *,
383390
ssl=None, sock=None,
384391
server_hostname=None,
385-
ssl_handshake_timeout=None):
392+
ssl_handshake_timeout=None,
393+
ssl_shutdown_timeout=None):
386394
raise NotImplementedError
387395

388396
async def create_unix_server(
389397
self, protocol_factory, path=None, *,
390398
sock=None, backlog=100, ssl=None,
391399
ssl_handshake_timeout=None,
400+
ssl_shutdown_timeout=None,
392401
start_serving=True):
393402
"""A coroutine which creates a UNIX Domain Socket server.
394403
@@ -410,6 +419,9 @@ async def create_unix_server(
410419
ssl_handshake_timeout is the time in seconds that an SSL server
411420
will wait for the SSL handshake to complete (defaults to 60s).
412421
422+
ssl_shutdown_timeout is the time in seconds that an SSL server
423+
will wait for the SSL shutdown to finish (defaults to 30s).
424+
413425
start_serving set to True (default) causes the created server
414426
to start accepting connections immediately. When set to False,
415427
the user should await Server.start_serving() or Server.serve_forever()
@@ -420,7 +432,8 @@ async def create_unix_server(
420432
async def connect_accepted_socket(
421433
self, protocol_factory, sock,
422434
*, ssl=None,
423-
ssl_handshake_timeout=None):
435+
ssl_handshake_timeout=None,
436+
ssl_shutdown_timeout=None):
424437
"""Handle an accepted connection.
425438
426439
This is used by servers that accept connections outside of

Lib/asyncio/proactor_events.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -642,11 +642,13 @@ def _make_ssl_transport(
642642
self, rawsock, protocol, sslcontext, waiter=None,
643643
*, server_side=False, server_hostname=None,
644644
extra=None, server=None,
645-
ssl_handshake_timeout=None):
645+
ssl_handshake_timeout=None,
646+
ssl_shutdown_timeout=None):
646647
ssl_protocol = sslproto.SSLProtocol(
647648
self, protocol, sslcontext, waiter,
648649
server_side, server_hostname,
649-
ssl_handshake_timeout=ssl_handshake_timeout)
650+
ssl_handshake_timeout=ssl_handshake_timeout,
651+
ssl_shutdown_timeout=ssl_shutdown_timeout)
650652
_ProactorSocketTransport(self, rawsock, ssl_protocol,
651653
extra=extra, server=server)
652654
return ssl_protocol._app_transport
@@ -812,7 +814,8 @@ def _write_to_self(self):
812814

813815
def _start_serving(self, protocol_factory, sock,
814816
sslcontext=None, server=None, backlog=100,
815-
ssl_handshake_timeout=None):
817+
ssl_handshake_timeout=None,
818+
ssl_shutdown_timeout=None):
816819

817820
def loop(f=None):
818821
try:
@@ -826,7 +829,8 @@ def loop(f=None):
826829
self._make_ssl_transport(
827830
conn, protocol, sslcontext, server_side=True,
828831
extra={'peername': addr}, server=server,
829-
ssl_handshake_timeout=ssl_handshake_timeout)
832+
ssl_handshake_timeout=ssl_handshake_timeout,
833+
ssl_shutdown_timeout=ssl_shutdown_timeout)
830834
else:
831835
self._make_socket_transport(
832836
conn, protocol,

Lib/asyncio/selector_events.py

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,15 @@ def _make_ssl_transport(
7070
self, rawsock, protocol, sslcontext, waiter=None,
7171
*, server_side=False, server_hostname=None,
7272
extra=None, server=None,
73-
ssl_handshake_timeout=constants.SSL_HANDSHAKE_TIMEOUT):
73+
ssl_handshake_timeout=constants.SSL_HANDSHAKE_TIMEOUT,
74+
ssl_shutdown_timeout=constants.SSL_SHUTDOWN_TIMEOUT,
75+
):
7476
ssl_protocol = sslproto.SSLProtocol(
75-
self, protocol, sslcontext, waiter,
76-
server_side, server_hostname,
77-
ssl_handshake_timeout=ssl_handshake_timeout)
77+
self, protocol, sslcontext, waiter,
78+
server_side, server_hostname,
79+
ssl_handshake_timeout=ssl_handshake_timeout,
80+
ssl_shutdown_timeout=ssl_shutdown_timeout
81+
)
7882
_SelectorSocketTransport(self, rawsock, ssl_protocol,
7983
extra=extra, server=server)
8084
return ssl_protocol._app_transport
@@ -146,15 +150,17 @@ def _write_to_self(self):
146150

147151
def _start_serving(self, protocol_factory, sock,
148152
sslcontext=None, server=None, backlog=100,
149-
ssl_handshake_timeout=constants.SSL_HANDSHAKE_TIMEOUT):
153+
ssl_handshake_timeout=constants.SSL_HANDSHAKE_TIMEOUT,
154+
ssl_shutdown_timeout=constants.SSL_SHUTDOWN_TIMEOUT):
150155
self._add_reader(sock.fileno(), self._accept_connection,
151156
protocol_factory, sock, sslcontext, server, backlog,
152-
ssl_handshake_timeout)
157+
ssl_handshake_timeout, ssl_shutdown_timeout)
153158

154159
def _accept_connection(
155160
self, protocol_factory, sock,
156161
sslcontext=None, server=None, backlog=100,
157-
ssl_handshake_timeout=constants.SSL_HANDSHAKE_TIMEOUT):
162+
ssl_handshake_timeout=constants.SSL_HANDSHAKE_TIMEOUT,
163+
ssl_shutdown_timeout=constants.SSL_SHUTDOWN_TIMEOUT):
158164
# This method is only called once for each event loop tick where the
159165
# listening socket has triggered an EVENT_READ. There may be multiple
160166
# connections waiting for an .accept() so it is called in a loop.
@@ -185,20 +191,22 @@ def _accept_connection(
185191
self.call_later(constants.ACCEPT_RETRY_DELAY,
186192
self._start_serving,
187193
protocol_factory, sock, sslcontext, server,
188-
backlog, ssl_handshake_timeout)
194+
backlog, ssl_handshake_timeout,
195+
ssl_shutdown_timeout)
189196
else:
190197
raise # The event loop will catch, log and ignore it.
191198
else:
192199
extra = {'peername': addr}
193200
accept = self._accept_connection2(
194201
protocol_factory, conn, extra, sslcontext, server,
195-
ssl_handshake_timeout)
202+
ssl_handshake_timeout, ssl_shutdown_timeout)
196203
self.create_task(accept)
197204

198205
async def _accept_connection2(
199206
self, protocol_factory, conn, extra,
200207
sslcontext=None, server=None,
201-
ssl_handshake_timeout=constants.SSL_HANDSHAKE_TIMEOUT):
208+
ssl_handshake_timeout=constants.SSL_HANDSHAKE_TIMEOUT,
209+
ssl_shutdown_timeout=constants.SSL_SHUTDOWN_TIMEOUT):
202210
protocol = None
203211
transport = None
204212
try:
@@ -208,7 +216,8 @@ async def _accept_connection2(
208216
transport = self._make_ssl_transport(
209217
conn, protocol, sslcontext, waiter=waiter,
210218
server_side=True, extra=extra, server=server,
211-
ssl_handshake_timeout=ssl_handshake_timeout)
219+
ssl_handshake_timeout=ssl_handshake_timeout,
220+
ssl_shutdown_timeout=ssl_shutdown_timeout)
212221
else:
213222
transport = self._make_socket_transport(
214223
conn, protocol, waiter=waiter, extra=extra,

0 commit comments

Comments
 (0)