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
@@ -308,7 +316,9 @@ async def _async_configured_socket(
308316
309317
310318async def _configured_protocol_interface (
311- address : _Address , options : PoolOptions
319+ address : _Address ,
320+ options : PoolOptions ,
321+ ssl_session_cache : Optional [list [Any ]] = None ,
312322) -> AsyncNetworkingInterface :
313323 """Given (host, port) and PoolOptions, return a configured AsyncNetworkingInterface.
314324
@@ -328,6 +338,22 @@ async def _configured_protocol_interface(
328338 )
329339
330340 host = address [0 ]
341+ # asyncio does not support TLS session resumption natively (cpython#79152,
342+ # closed without a fix). On Python 3.11+ SSLProtocol.__init__ calls
343+ # wrap_bio() synchronously before the first event-loop yield, so setting
344+ # sslobject_class is race-free. Session injection is skipped on older
345+ # Python versions. (The async path always uses stdlib ssl, never PyOpenSSL.)
346+ if ssl_session_cache is not None and sys .version_info >= (3 , 11 ):
347+ session = ssl_session_cache [0 ]
348+ if session is not None :
349+ _session = session
350+
351+ class _SessionSSLObject (ssl .SSLObject ):
352+ def __init__ (self , * args : Any , ** kwargs : Any ) -> None :
353+ super ().__init__ (* args , ** kwargs )
354+ self .session = _session
355+
356+ ssl_context .sslobject_class = _SessionSSLObject # type: ignore[attr-defined]
331357 try :
332358 # We have to pass hostname / ip address to wrap_socket
333359 # to use SSLContext.check_hostname.
@@ -354,6 +380,14 @@ async def _configured_protocol_interface(
354380 and not options .tls_allow_invalid_hostnames
355381 ):
356382 ssl .match_hostname (transport .get_extra_info ("peercert" ), hostname = host ) # type:ignore[attr-defined,unused-ignore]
383+
384+ if ssl_session_cache is not None :
385+ ssl_obj = transport .get_extra_info ("ssl_object" )
386+ if ssl_obj is not None :
387+ new_session = ssl_obj .session
388+ if new_session is not None :
389+ ssl_session_cache [0 ] = new_session
390+
357391 return AsyncNetworkingInterface ((transport , protocol ))
358392 except BaseException :
359393 # Protect against cancellation, _CertificateError, or interruption
@@ -481,7 +515,11 @@ def _configured_socket(address: _Address, options: PoolOptions) -> Union[socket.
481515 return ssl_sock
482516
483517
484- def _configured_socket_interface (address : _Address , options : PoolOptions ) -> NetworkingInterface :
518+ def _configured_socket_interface (
519+ address : _Address ,
520+ options : PoolOptions ,
521+ ssl_session_cache : Optional [list [Any ]] = None ,
522+ ) -> NetworkingInterface :
485523 """Given (host, port) and PoolOptions, return a NetworkingInterface wrapping a configured socket.
486524
487525 Can raise socket.error, ConnectionFailure, or _CertificateError.
@@ -496,13 +534,14 @@ def _configured_socket_interface(address: _Address, options: PoolOptions) -> Net
496534 return NetworkingInterface (sock )
497535
498536 host = address [0 ]
537+ session = ssl_session_cache [0 ] if ssl_session_cache is not None else None
499538 try :
500539 # We have to pass hostname / ip address to wrap_socket
501540 # to use SSLContext.check_hostname.
502541 if _has_sni (True ):
503- ssl_sock = ssl_context .wrap_socket (sock , server_hostname = host )
542+ ssl_sock = ssl_context .wrap_socket (sock , server_hostname = host , session = session )
504543 else :
505- ssl_sock = ssl_context .wrap_socket (sock )
544+ ssl_sock = ssl_context .wrap_socket (sock , session = session )
506545 except _CertificateError :
507546 sock .close ()
508547 # Raise _CertificateError directly like we do after match_hostname
@@ -526,5 +565,10 @@ def _configured_socket_interface(address: _Address, options: PoolOptions) -> Net
526565 ssl_sock .close ()
527566 raise
528567
568+ if ssl_session_cache is not None :
569+ new_session = _get_ssl_session (ssl_sock )
570+ if new_session is not None :
571+ ssl_session_cache [0 ] = new_session
572+
529573 ssl_sock .settimeout (options .socket_timeout )
530574 return NetworkingInterface (ssl_sock )
0 commit comments