4747 from pymongo .pyopenssl_context import _sslConn
4848 from pymongo .typings import _Address
4949
50+
51+ def _get_ssl_session (ssl_sock : Any ) -> Optional [Any ]:
52+ """Return the TLS session from an SSL socket, handling both PyOpenSSL and stdlib ssl."""
53+ if hasattr (ssl_sock , "get_session" ):
54+ return ssl_sock .get_session ()
55+ return getattr (ssl_sock , "session" , None )
56+
57+
5058try :
5159 from fcntl import F_GETFD , F_SETFD , FD_CLOEXEC , fcntl
5260
@@ -297,7 +305,9 @@ async def _async_configured_socket(
297305
298306
299307async def _configured_protocol_interface (
300- address : _Address , options : PoolOptions
308+ address : _Address ,
309+ options : PoolOptions ,
310+ ssl_session_cache : Optional [list [Any ]] = None ,
301311) -> AsyncNetworkingInterface :
302312 """Given (host, port) and PoolOptions, return a configured AsyncNetworkingInterface.
303313
@@ -317,6 +327,22 @@ async def _configured_protocol_interface(
317327 )
318328
319329 host = address [0 ]
330+ # asyncio does not support TLS session resumption natively (cpython#79152,
331+ # closed without a fix). On Python 3.11+ SSLProtocol.__init__ calls
332+ # wrap_bio() synchronously before the first event-loop yield, so setting
333+ # sslobject_class is race-free. Session injection is skipped on older
334+ # Python versions. (The async path always uses stdlib ssl, never PyOpenSSL.)
335+ if ssl_session_cache is not None and sys .version_info >= (3 , 11 ):
336+ session = ssl_session_cache [0 ]
337+ if session is not None :
338+ _session = session
339+
340+ class _SessionSSLObject (ssl .SSLObject ):
341+ def __init__ (self , * args : Any , ** kwargs : Any ) -> None :
342+ super ().__init__ (* args , ** kwargs )
343+ self .session = _session
344+
345+ ssl_context .sslobject_class = _SessionSSLObject # type: ignore[attr-defined]
320346 try :
321347 # We have to pass hostname / ip address to wrap_socket
322348 # to use SSLContext.check_hostname.
@@ -336,6 +362,7 @@ async def _configured_protocol_interface(
336362 # mismatch, will be turned into ServerSelectionTimeoutErrors later.
337363 details = _get_timeout_details (options )
338364 _raise_connection_failure (address , exc , "SSL handshake failed: " , timeout_details = details )
365+
339366 if (
340367 ssl_context .verify_mode
341368 and not ssl_context .check_hostname
@@ -347,6 +374,13 @@ async def _configured_protocol_interface(
347374 transport .abort ()
348375 raise
349376
377+ if ssl_session_cache is not None :
378+ ssl_obj = transport .get_extra_info ("ssl_object" )
379+ if ssl_obj is not None :
380+ new_session = ssl_obj .session
381+ if new_session is not None :
382+ ssl_session_cache [0 ] = new_session
383+
350384 return AsyncNetworkingInterface ((transport , protocol ))
351385
352386
@@ -469,7 +503,11 @@ def _configured_socket(address: _Address, options: PoolOptions) -> Union[socket.
469503 return ssl_sock
470504
471505
472- def _configured_socket_interface (address : _Address , options : PoolOptions ) -> NetworkingInterface :
506+ def _configured_socket_interface (
507+ address : _Address ,
508+ options : PoolOptions ,
509+ ssl_session_cache : Optional [list [Any ]] = None ,
510+ ) -> NetworkingInterface :
473511 """Given (host, port) and PoolOptions, return a NetworkingInterface wrapping a configured socket.
474512
475513 Can raise socket.error, ConnectionFailure, or _CertificateError.
@@ -484,13 +522,14 @@ def _configured_socket_interface(address: _Address, options: PoolOptions) -> Net
484522 return NetworkingInterface (sock )
485523
486524 host = address [0 ]
525+ session = ssl_session_cache [0 ] if ssl_session_cache is not None else None
487526 try :
488527 # We have to pass hostname / ip address to wrap_socket
489528 # to use SSLContext.check_hostname.
490529 if _has_sni (True ):
491- ssl_sock = ssl_context .wrap_socket (sock , server_hostname = host )
530+ ssl_sock = ssl_context .wrap_socket (sock , server_hostname = host , session = session )
492531 else :
493- ssl_sock = ssl_context .wrap_socket (sock )
532+ ssl_sock = ssl_context .wrap_socket (sock , session = session )
494533 except _CertificateError :
495534 sock .close ()
496535 # Raise _CertificateError directly like we do after match_hostname
@@ -514,5 +553,10 @@ def _configured_socket_interface(address: _Address, options: PoolOptions) -> Net
514553 ssl_sock .close ()
515554 raise
516555
556+ if ssl_session_cache is not None :
557+ new_session = _get_ssl_session (ssl_sock )
558+ if new_session is not None :
559+ ssl_session_cache [0 ] = new_session
560+
517561 ssl_sock .settimeout (options .socket_timeout )
518562 return NetworkingInterface (ssl_sock )
0 commit comments