@@ -128,16 +128,19 @@ class ConnectionParams:
128128class ConnectionState (enum .Enum ):
129129 # The connection is initialized, but connect() wasn't called yet
130130 INITIALIZED = 0
131+ # The host has been resolved, but the socket hasn't been opened yet
132+ HOST_RESOLVED = 1
131133 # The socket has been opened, but the handshake and login haven't been completed
132- SOCKET_OPENED = 1
134+ SOCKET_OPENED = 2
133135 # The handshake has been completed, messages can be exchanged
134- HANDSHAKE_COMPLETE = 2
136+ HANDSHAKE_COMPLETE = 3
135137 # The connection has been established, authenticated data can be exchanged
136- CONNECTED = 3
137- CLOSED = 4
138+ CONNECTED = 4
139+ CLOSED = 5
138140
139141
140142CONNECTION_STATE_INITIALIZED = ConnectionState .INITIALIZED
143+ CONNECTION_STATE_HOST_RESOLVED = ConnectionState .HOST_RESOLVED
141144CONNECTION_STATE_SOCKET_OPENED = ConnectionState .SOCKET_OPENED
142145CONNECTION_STATE_HANDSHAKE_COMPLETE = ConnectionState .HANDSHAKE_COMPLETE
143146CONNECTION_STATE_CONNECTED = ConnectionState .CONNECTED
@@ -192,6 +195,7 @@ class APIConnection:
192195 """
193196
194197 __slots__ = (
198+ "_addrs_info" ,
195199 "_debug_enabled" ,
196200 "_expected_disconnect" ,
197201 "_fatal_exception" ,
@@ -206,6 +210,7 @@ class APIConnection:
206210 "_ping_timer" ,
207211 "_pong_timer" ,
208212 "_read_exception_futures" ,
213+ "_resolve_host_future" ,
209214 "_send_pending_ping" ,
210215 "_socket" ,
211216 "_start_connect_future" ,
@@ -247,6 +252,7 @@ def __init__(
247252 self ._keep_alive_interval = keepalive
248253 self ._keep_alive_timeout = keepalive * KEEP_ALIVE_TIMEOUT_RATIO
249254
255+ self ._resolve_host_future : asyncio .Future [None ] | None = None
250256 self ._start_connect_future : asyncio .Future [None ] | None = None
251257 self ._finish_connect_future : asyncio .Future [None ] | None = None
252258 self ._fatal_exception : Exception | None = None
@@ -258,6 +264,7 @@ def __init__(
258264 self ._debug_enabled = debug_enabled
259265 self .received_name : str = ""
260266 self .connected_address : str | None = None
267+ self ._addrs_info : list [hr .AddrInfo ] = []
261268
262269 def set_log_name (self , name : str ) -> None :
263270 """Set the friendly log name for this connection."""
@@ -286,6 +293,7 @@ def _cleanup(self) -> None:
286293 fut .set_exception (new_exc )
287294 self ._read_exception_futures .clear ()
288295
296+ self ._set_resolve_host_future ()
289297 self ._set_start_connect_future ()
290298 self ._set_finish_connect_future ()
291299
@@ -572,32 +580,61 @@ def _async_pong_not_received(self) -> None:
572580 )
573581 )
574582
575- async def _do_connect (self ) -> None :
576- """Do the actual connect process."""
577- addrs_info = await hr .async_resolve_host (
578- self ._params .addresses ,
579- self ._params .port ,
580- self ._params .zeroconf_manager ,
581- )
582- await self ._connect_socket_connect (addrs_info )
583+ async def start_resolve_host (self ) -> None :
584+ """Start the host resolution process.
585+
586+ This part of the process resolves the hostnames to IP addresses
587+ and prepares the connection for the next step.
588+ """
589+ if self .connection_state is not CONNECTION_STATE_INITIALIZED :
590+ raise RuntimeError (
591+ "Connection can only be used once, connection is not in init state"
592+ )
593+
594+ self ._resolve_host_future = self ._loop .create_future ()
595+ try :
596+ async with interrupt (
597+ self ._resolve_host_future , ConnectionInterruptedError , None
598+ ):
599+ self ._addrs_info = await hr .async_resolve_host (
600+ self ._params .addresses ,
601+ self ._params .port ,
602+ self ._params .zeroconf_manager ,
603+ )
604+ except (Exception , CancelledError ) as ex :
605+ # If the task was cancelled, we need to clean up the connection
606+ # and raise the CancelledError as APIConnectionError
607+ self ._cleanup ()
608+ raise self ._wrap_fatal_connection_exception ("resolving" , ex )
609+ finally :
610+ self ._set_resolve_host_future ()
611+ self ._set_connection_state (CONNECTION_STATE_HOST_RESOLVED )
612+
613+ def _set_resolve_host_future (self ) -> None :
614+ if (
615+ self ._resolve_host_future is not None
616+ and not self ._resolve_host_future .done ()
617+ ):
618+ self ._resolve_host_future .set_result (None )
619+ self ._resolve_host_future = None
583620
584621 async def start_connection (self ) -> None :
585622 """Start the connection process.
586623
587624 This part of the process establishes the socket connection but
588625 does not initialize the frame helper or send the hello message.
589626 """
590- if self .connection_state is not CONNECTION_STATE_INITIALIZED :
627+ if self .connection_state is not CONNECTION_STATE_HOST_RESOLVED :
591628 raise RuntimeError (
592- "Connection can only be used once, connection is not in init state "
629+ "Connection must be in HOST_RESOLVED state to start connection "
593630 )
594631
595632 self ._start_connect_future = self ._loop .create_future ()
596633 try :
597634 async with interrupt (
598635 self ._start_connect_future , ConnectionInterruptedError , None
599636 ):
600- await self ._do_connect ( )
637+ await self ._connect_socket_connect ( self . _addrs_info )
601638 except (Exception , CancelledError ) as ex :
602639 # If the task was cancelled, we need to clean up the connection
603640 # and raise the CancelledError as APIConnectionError
0 commit comments