52
52
from .client_proto import ResponseHandler
53
53
from .client_reqrep import ClientRequest , Fingerprint , _merge_ssl_params
54
54
from .helpers import (
55
+ _SENTINEL ,
55
56
ceil_timeout ,
56
57
is_ip_address ,
57
58
noop ,
@@ -231,15 +232,19 @@ def closed(self) -> bool:
231
232
class _TransportPlaceholder :
232
233
"""placeholder for BaseConnector.connect function"""
233
234
234
- __slots__ = ("closed" ,)
235
+ __slots__ = ("closed" , "transport" )
235
236
236
237
def __init__ (self , closed_future : asyncio .Future [Optional [Exception ]]) -> None :
237
238
"""Initialize a placeholder for a transport."""
238
239
self .closed = closed_future
240
+ self .transport = None
239
241
240
242
def close (self ) -> None :
241
243
"""Close the placeholder."""
242
244
245
+ def abort (self ) -> None :
246
+ """Abort the placeholder (does nothing)."""
247
+
243
248
244
249
class BaseConnector :
245
250
"""Base connector class.
@@ -469,9 +474,14 @@ def _cleanup_closed(self) -> None:
469
474
timeout_ceil_threshold = self ._timeout_ceil_threshold ,
470
475
)
471
476
472
- def close (self ) -> Awaitable [None ]:
473
- """Close all opened transports."""
474
- if not (waiters := self ._close ()):
477
+ def close (self , * , abort_ssl : bool = False ) -> Awaitable [None ]:
478
+ """Close all opened transports.
479
+
480
+ :param abort_ssl: If True, SSL connections will be aborted immediately
481
+ without performing the shutdown handshake. This provides
482
+ faster cleanup at the cost of less graceful disconnection.
483
+ """
484
+ if not (waiters := self ._close (abort_ssl = abort_ssl )):
475
485
# If there are no connections to close, we can return a noop
476
486
# awaitable to avoid scheduling a task on the event loop.
477
487
return _DeprecationWaiter (noop ())
@@ -484,7 +494,7 @@ def close(self) -> Awaitable[None]:
484
494
task = self ._loop .create_task (coro )
485
495
return _DeprecationWaiter (task )
486
496
487
- def _close (self ) -> List [Awaitable [object ]]:
497
+ def _close (self , * , abort_ssl : bool = False ) -> List [Awaitable [object ]]:
488
498
waiters : List [Awaitable [object ]] = []
489
499
490
500
if self ._closed :
@@ -506,12 +516,26 @@ def _close(self) -> List[Awaitable[object]]:
506
516
507
517
for data in self ._conns .values ():
508
518
for proto , _ in data :
509
- proto .close ()
519
+ if (
520
+ abort_ssl
521
+ and proto .transport
522
+ and proto .transport .get_extra_info ("sslcontext" ) is not None
523
+ ):
524
+ proto .abort ()
525
+ else :
526
+ proto .close ()
510
527
if closed := proto .closed :
511
528
waiters .append (closed )
512
529
513
530
for proto in self ._acquired :
514
- proto .close ()
531
+ if (
532
+ abort_ssl
533
+ and proto .transport
534
+ and proto .transport .get_extra_info ("sslcontext" ) is not None
535
+ ):
536
+ proto .abort ()
537
+ else :
538
+ proto .close ()
515
539
if closed := proto .closed :
516
540
waiters .append (closed )
517
541
@@ -881,11 +905,12 @@ class TCPConnector(BaseConnector):
881
905
socket_factory - A SocketFactoryType function that, if supplied,
882
906
will be used to create sockets given an
883
907
AddrInfoType.
884
- ssl_shutdown_timeout - Grace period for SSL shutdown handshake on TLS
885
- connections. Default is 0.1 seconds. This usually
886
- allows for a clean SSL shutdown by notifying the
887
- remote peer of connection closure, while avoiding
888
- excessive delays during connector cleanup.
908
+ ssl_shutdown_timeout - DEPRECATED. Will be removed in aiohttp 4.0.
909
+ Grace period for SSL shutdown handshake on TLS
910
+ connections. Default is 0 seconds (immediate abort).
911
+ This parameter allowed for a clean SSL shutdown by
912
+ notifying the remote peer of connection closure,
913
+ while avoiding excessive delays during connector cleanup.
889
914
Note: Only takes effect on Python 3.11+.
890
915
"""
891
916
@@ -913,7 +938,7 @@ def __init__(
913
938
happy_eyeballs_delay : Optional [float ] = 0.25 ,
914
939
interleave : Optional [int ] = None ,
915
940
socket_factory : Optional [SocketFactoryType ] = None ,
916
- ssl_shutdown_timeout : Optional [ float ] = 0.1 ,
941
+ ssl_shutdown_timeout : Union [ _SENTINEL , None , float ] = sentinel ,
917
942
):
918
943
super ().__init__ (
919
944
keepalive_timeout = keepalive_timeout ,
@@ -946,26 +971,57 @@ def __init__(
946
971
self ._interleave = interleave
947
972
self ._resolve_host_tasks : Set ["asyncio.Task[List[ResolveResult]]" ] = set ()
948
973
self ._socket_factory = socket_factory
949
- self ._ssl_shutdown_timeout = ssl_shutdown_timeout
974
+ self ._ssl_shutdown_timeout : Optional [float ]
975
+ # Handle ssl_shutdown_timeout with warning for Python < 3.11
976
+ if ssl_shutdown_timeout is sentinel :
977
+ self ._ssl_shutdown_timeout = 0
978
+ else :
979
+ # Deprecation warning for ssl_shutdown_timeout parameter
980
+ warnings .warn (
981
+ "The ssl_shutdown_timeout parameter is deprecated and will be removed in aiohttp 4.0" ,
982
+ DeprecationWarning ,
983
+ stacklevel = 2 ,
984
+ )
985
+ if (
986
+ sys .version_info < (3 , 11 )
987
+ and ssl_shutdown_timeout is not None
988
+ and ssl_shutdown_timeout != 0
989
+ ):
990
+ warnings .warn (
991
+ f"ssl_shutdown_timeout={ ssl_shutdown_timeout } is ignored on Python < 3.11; "
992
+ "only ssl_shutdown_timeout=0 is supported. The timeout will be ignored." ,
993
+ RuntimeWarning ,
994
+ stacklevel = 2 ,
995
+ )
996
+ self ._ssl_shutdown_timeout = ssl_shutdown_timeout
950
997
951
- def _close (self ) -> List [Awaitable [object ]]:
998
+ def _close (self , * , abort_ssl : bool = False ) -> List [Awaitable [object ]]:
952
999
"""Close all ongoing DNS calls."""
953
1000
for fut in chain .from_iterable (self ._throttle_dns_futures .values ()):
954
1001
fut .cancel ()
955
1002
956
- waiters = super ()._close ()
1003
+ waiters = super ()._close (abort_ssl = abort_ssl )
957
1004
958
1005
for t in self ._resolve_host_tasks :
959
1006
t .cancel ()
960
1007
waiters .append (t )
961
1008
962
1009
return waiters
963
1010
964
- async def close (self ) -> None :
965
- """Close all opened transports."""
1011
+ async def close (self , * , abort_ssl : bool = False ) -> None :
1012
+ """
1013
+ Close all opened transports.
1014
+
1015
+ :param abort_ssl: If True, SSL connections will be aborted immediately
1016
+ without performing the shutdown handshake. If False (default),
1017
+ the behavior is determined by ssl_shutdown_timeout:
1018
+ - If ssl_shutdown_timeout=0: connections are aborted
1019
+ - If ssl_shutdown_timeout>0: graceful shutdown is performed
1020
+ """
966
1021
if self ._resolver_owner :
967
1022
await self ._resolver .close ()
968
- await super ().close ()
1023
+ # Use abort_ssl param if explicitly set, otherwise use ssl_shutdown_timeout default
1024
+ await super ().close (abort_ssl = abort_ssl or self ._ssl_shutdown_timeout == 0 )
969
1025
970
1026
@property
971
1027
def family (self ) -> int :
@@ -1200,7 +1256,7 @@ async def _wrap_create_connection(
1200
1256
# Add ssl_shutdown_timeout for Python 3.11+ when SSL is used
1201
1257
if (
1202
1258
kwargs .get ("ssl" )
1203
- and self ._ssl_shutdown_timeout is not None
1259
+ and self ._ssl_shutdown_timeout
1204
1260
and sys .version_info >= (3 , 11 )
1205
1261
):
1206
1262
kwargs ["ssl_shutdown_timeout" ] = self ._ssl_shutdown_timeout
@@ -1343,10 +1399,7 @@ async def _start_tls_connection(
1343
1399
):
1344
1400
try :
1345
1401
# ssl_shutdown_timeout is only available in Python 3.11+
1346
- if (
1347
- sys .version_info >= (3 , 11 )
1348
- and self ._ssl_shutdown_timeout is not None
1349
- ):
1402
+ if sys .version_info >= (3 , 11 ) and self ._ssl_shutdown_timeout :
1350
1403
tls_transport = await self ._loop .start_tls (
1351
1404
underlying_transport ,
1352
1405
tls_proto ,
@@ -1367,7 +1420,10 @@ async def _start_tls_connection(
1367
1420
# We need to close the underlying transport since
1368
1421
# `start_tls()` probably failed before it had a
1369
1422
# chance to do this:
1370
- underlying_transport .close ()
1423
+ if self ._ssl_shutdown_timeout == 0 :
1424
+ underlying_transport .abort ()
1425
+ else :
1426
+ underlying_transport .close ()
1371
1427
raise
1372
1428
if isinstance (tls_transport , asyncio .Transport ):
1373
1429
fingerprint = self ._get_fingerprint (req )
0 commit comments