@@ -263,6 +263,7 @@ def initialize(address, port = nil, tls: false, starttls: :auto, tls_verify: tru
263263 @tls_verify = tls_verify
264264 @tls_hostname = tls_hostname
265265 @ssl_context_params = ssl_context_params
266+ @tcpsocket_supports_open_timeout = nil
266267 end
267268
268269 # If +true+, verify th server's certificate.
@@ -659,18 +660,44 @@ def finish
659660
660661 private
661662
662- def tcp_socket ( address , port )
663- TCPSocket . open address , port
663+ def tcp_socket ( address , port , open_timeout : nil )
664+ if open_timeout
665+ TCPSocket . open ( address , port , nil , nil , open_timeout : open_timeout )
666+ else
667+ TCPSocket . open ( address , port )
668+ end
669+ end
670+
671+ def timeouted_connect
672+ if @tcpsocket_supports_open_timeout == nil || @tcpsocket_supports_open_timeout == true
673+ # Try to use built-in open_timeout in TCPSocket.open if:
674+ # - The current Ruby runtime is known to support it, or
675+ # - It is unknown whether the current Ruby runtime supports it (so we'll try).
676+ begin
677+ sock = tcp_socket ( @address , @port , open_timeout : @open_timeout )
678+ @tcpsocket_supports_open_timeout = true
679+ return sock
680+ rescue Errno ::ETIMEDOUT => e
681+ raise Net ::OpenTimeout . new ( e ) # for compatibility with previous versions
682+ rescue ArgumentError => e
683+ raise unless e . message . include? ( 'unknown keyword: :open_timeout' ) || e . message . include? ( 'wrong number of arguments (given 5, expected 2..4)' )
684+ @tcpsocket_supports_open_timeout = false
685+ end
686+ end
687+
688+ # This Ruby runtime is known not to support `TCPSocket(open_timeout:)`.
689+ # Directly fall back to Timeout.timeout to avoid performance penalty incured by rescue.
690+ Timeout . timeout ( @open_timeout , Net ::OpenTimeout ) do
691+ tcp_socket ( @address , @port )
692+ end
664693 end
665694
666695 def do_start ( helo_domain , user , secret , authtype )
667696 raise IOError , 'SMTP session already started' if @started
668697 if user || secret || authtype
669698 check_auth_args authtype , user , secret
670699 end
671- s = Timeout . timeout ( @open_timeout , Net ::OpenTimeout ) do
672- tcp_socket ( @address , @port )
673- end
700+ s = timeouted_connect
674701 logging "Connection opened: #{ @address } :#{ @port } "
675702 @socket = new_internet_message_io ( tls? ? tlsconnect ( s , @ssl_context_tls ) : s )
676703 check_response critical { recv_response ( ) }
0 commit comments