@@ -390,6 +390,7 @@ async def open_connection( # pylint: disable=too-many-locals
390390 shell : Optional [ShellCallback ] = None ,
391391 connect_minwait : float = 2.0 ,
392392 connect_maxwait : float = 3.0 ,
393+ connect_timeout : Optional [float ] = None ,
393394 waiter_closed : Optional [asyncio .Future [None ]] = None ,
394395 _waiter_connected : Optional [asyncio .Future [None ]] = None ,
395396 limit : Optional [int ] = None ,
@@ -445,6 +446,11 @@ async def open_connection( # pylint: disable=too-many-locals
445446 otherwise confused by our demands, the shell continues anyway after the
446447 greater of this value has elapsed. A client that is not answering
447448 option negotiation will delay the start of the shell by this amount.
449+ :param connect_timeout: Timeout in seconds for the TCP connection to be
450+ established. When ``None`` (default), no timeout is applied and the
451+ connection attempt may block indefinitely. When specified, a
452+ :exc:`ConnectionError` is raised if the connection is not established
453+ within the given time.
448454
449455 :param force_binary: When ``True``, the encoding is used regardless
450456 of BINARY mode negotiation.
@@ -480,14 +486,22 @@ def connection_factory() -> client_base.BaseClient:
480486 send_environ = send_environ ,
481487 )
482488
483- _ , protocol = await asyncio .get_event_loop ().create_connection (
484- connection_factory ,
485- host or "localhost" ,
486- port ,
487- family = family ,
488- flags = flags ,
489- local_addr = local_addr ,
490- )
489+ try :
490+ _ , protocol = await asyncio .wait_for (
491+ asyncio .get_event_loop ().create_connection (
492+ connection_factory ,
493+ host or "localhost" ,
494+ port ,
495+ family = family ,
496+ flags = flags ,
497+ local_addr = local_addr ,
498+ ),
499+ timeout = connect_timeout ,
500+ )
501+ except asyncio .TimeoutError as exc :
502+ raise ConnectionError (
503+ f"TCP connection to { host or 'localhost' } :{ port } " f" timed out after { connect_timeout } s"
504+ ) from exc
491505
492506 await protocol ._waiter_connected # pylint: disable=protected-access
493507
@@ -518,6 +532,7 @@ async def run_client() -> None:
518532 "force_binary" : args ["force_binary" ],
519533 "encoding_errors" : args ["encoding_errors" ],
520534 "connect_minwait" : args ["connect_minwait" ],
535+ "connect_timeout" : args ["connect_timeout" ],
521536 "send_environ" : args ["send_environ" ],
522537 }
523538
@@ -564,6 +579,12 @@ def _get_argument_parser() -> argparse.ArgumentParser:
564579 type = float ,
565580 help = "timeout for pending negotiation" ,
566581 )
582+ parser .add_argument (
583+ "--connect-timeout" ,
584+ default = None ,
585+ type = float ,
586+ help = "timeout for TCP connection (seconds, default: no timeout)" ,
587+ )
567588 parser .add_argument (
568589 "--send-environ" ,
569590 default = "TERM,LANG,COLUMNS,LINES,COLORTERM" ,
@@ -586,6 +607,7 @@ def _transform_args(args: argparse.Namespace) -> Dict[str, Any]:
586607 "force_binary" : args .force_binary ,
587608 "encoding_errors" : args .encoding_errors ,
588609 "connect_minwait" : args .connect_minwait ,
610+ "connect_timeout" : args .connect_timeout ,
589611 "send_environ" : tuple (v .strip () for v in args .send_environ .split ("," ) if v .strip ()),
590612 }
591613
0 commit comments