net: add failed addresses + details on connect errors, make connect more robust in the default non blocking mode (#15364)

This commit is contained in:
Emily Hudson 2022-08-07 08:40:05 +01:00 committed by GitHub
parent d6b594c4e8
commit fd1b6efea6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 202 additions and 41 deletions

View file

@ -1,6 +1,7 @@
module net
import time
import strings
const (
tcp_default_read_timeout = 30 * time.second
@ -24,12 +25,16 @@ pub fn dial_tcp(address string) ?&TcpConn {
return error('$err.msg(); could not resolve address $address in dial_tcp')
}
// Keep track of dialing errors that take place
mut errs := []IError{}
// Very simple dialer
for addr in addrs {
mut s := new_tcp_socket(addr.family()) or {
return error('$err.msg(); could not create new tcp socket in dial_tcp')
}
s.connect(addr) or {
errs << err
// Connection failed
s.close() or { continue }
continue
@ -41,8 +46,20 @@ pub fn dial_tcp(address string) ?&TcpConn {
write_timeout: net.tcp_default_write_timeout
}
}
// Once we've failed now try and explain why we failed to connect
// to any of these addresses
mut err_builder := strings.new_builder(1024)
err_builder.write_string('dial_tcp failed for address $address\n')
err_builder.write_string('tried addrs:\n')
for i := 0; i < errs.len; i++ {
addr := addrs[i]
why := errs[i]
err_builder.write_string('\t$addr: $why\n')
}
// failed
return error('dial_tcp failed for address $address')
return error(err_builder.str())
}
// bind local address and dail.
@ -431,34 +448,39 @@ fn (mut s TcpSocket) connect(a Addr) ? {
if res == 0 {
return
}
// The socket is nonblocking and the connection cannot be completed
// immediately. (UNIX domain sockets failed with EAGAIN instead.)
// It is possible to select(2) or poll(2) for completion by selecting
// the socket for writing. After select(2) indicates writability,
// use getsockopt(2) to read the SO_ERROR option at level SOL_SOCKET to
// determine whether connect() completed successfully (SO_ERROR is zero) or
// unsuccessfully (SO_ERROR is one of the usual error codes listed here,
// ex plaining the reason for the failure).
write_result := s.@select(.write, net.connect_timeout)?
if write_result {
ecode := error_code()
// On nix non-blocking sockets we expect einprogress
// On windows we expect res == -1 && error_code() == ewouldblock
if (is_windows && ecode == int(error_ewouldblock))
|| (!is_windows && res == -1 && ecode == int(error_einprogress)) {
// The socket is nonblocking and the connection cannot be completed
// immediately. (UNIX domain sockets failed with EAGAIN instead.)
// It is possible to select(2) or poll(2) for completion by selecting
// the socket for writing. After select(2) indicates writability,
// use getsockopt(2) to read the SO_ERROR option at level SOL_SOCKET to
// determine whether connect() completed successfully (SO_ERROR is zero) or
// unsuccessfully (SO_ERROR is one of the usual error codes listed here,
// ex plaining the reason for the failure).
write_result := s.@select(.write, net.connect_timeout)?
err := 0
len := sizeof(err)
socket_error(C.getsockopt(s.handle, C.SOL_SOCKET, C.SO_ERROR, &err, &len))?
if err != 0 {
return wrap_error(err)
xyz := C.getsockopt(s.handle, C.SOL_SOCKET, C.SO_ERROR, &err, &len)
if xyz == 0 && err == 0 {
return
}
// Succeeded
return
if write_result {
if xyz == 0 {
wrap_error(err)?
return
}
return
}
return err_timed_out
}
// Get the error
socket_error(C.connect(s.handle, voidptr(&a), a.len()))?
// otherwise we timed out
return err_connect_timed_out
wrap_error(ecode)?
return
} $else {
socket_error(C.connect(s.handle, voidptr(&a), a.len()))?
x := C.connect(s.handle, voidptr(&a), a.len())
socket_error(x)?
}
}