Skip to content

Commit

Permalink
net: fix non-blocking read/write (#20438)
Browse files Browse the repository at this point in the history
  • Loading branch information
kbkpbot committed Feb 8, 2024
1 parent 410bd9d commit a9ebab0
Show file tree
Hide file tree
Showing 9 changed files with 260 additions and 94 deletions.
72 changes: 64 additions & 8 deletions vlib/net/common.c.v
Expand Up @@ -33,20 +33,76 @@ pub struct ShutdownConfig {
// By default it shuts it down in both directions, both for reading
// and for writing. You can change that using `net.shutdown(handle, how: .read)`
// or `net.shutdown(handle, how: .write)`
// In non-blocking mode, `shutdown()` may not succeed immediately,
// so `select` is also used to make sure that the function doesn't return an incorrect result.
pub fn shutdown(handle int, config ShutdownConfig) int {
$if windows {
return C.shutdown(handle, int(config.how))
res := C.shutdown(handle, int(config.how))
$if !net_nonblocking_sockets ? {
return res
} $else {
return C.shutdown(handle, int(config.how))
if res == 0 {
return 0
}
ecode := error_code()
if (is_windows && ecode == int(error_ewouldblock)) || (!is_windows && res == -1
&& ecode in [int(error_einprogress), int(error_eagain), C.EINTR]) {
write_result := select_deadline(handle, .write, time.now().add(connect_timeout)) or {
false
}
err := 0
len := sizeof(err)
xyz := C.getsockopt(handle, C.SOL_SOCKET, C.SO_ERROR, &err, &len)
if xyz == 0 && err == 0 {
return 0
}
if write_result {
if xyz == 0 {
return err
}
return 0
}
}
return -ecode
}
}

// close a socket, given its file descriptor `handle`.
// In non-blocking mode, if `close()` does not succeed immediately,
// it causes an error to be propagated to `TcpSocket.close()`, which is not intended.
// Therefore, `select` is used just like `connect()`.
pub fn close(handle int) ! {
$if windows {
socket_error(C.closesocket(handle))!
res := $if windows {
C.closesocket(handle)
} $else {
C.close(handle)
}
$if !net_nonblocking_sockets ? {
socket_error(res)!
return
} $else {
socket_error(C.close(handle))!
if res == 0 {
return
}
ecode := error_code()
if (is_windows && ecode == int(error_ewouldblock)) || (!is_windows && res == -1
&& ecode in [int(error_einprogress), int(error_eagain), C.EINTR]) {
write_result := select_deadline(handle, .write, time.now().add(connect_timeout))!
err := 0
len := sizeof(err)
xyz := C.getsockopt(handle, C.SOL_SOCKET, C.SO_ERROR, &err, &len)
if xyz == 0 && err == 0 {
return
}
if write_result {
if xyz == 0 {
wrap_error(err)!
return
}
return
}
return err_timed_out
}
wrap_error(ecode)!
}
}

Expand Down Expand Up @@ -95,8 +151,8 @@ fn select_deadline(handle int, test Select, deadline time.Time) !bool {
for infinite || time.now() <= deadline {
timeout := if infinite { net.infinite_timeout } else { deadline - time.now() }
ready := @select(handle, test, timeout) or {
if err.code() == 4 {
// Spurious wakeup from signal, keep waiting
if err.code() == C.EINTR {
// errno is 4, Spurious wakeup from signal, keep waiting
continue
}

Expand Down
2 changes: 2 additions & 0 deletions vlib/net/net_nix.c.v
Expand Up @@ -21,8 +21,10 @@ fn init() {
}

pub const msg_nosignal = 0x4000
pub const msg_dontwait = C.MSG_DONTWAIT

pub const error_ewouldblock = C.EWOULDBLOCK
pub const error_einprogress = C.EINPROGRESS
pub const error_eagain = C.EAGAIN

fn C.unlink(&char) int
3 changes: 3 additions & 0 deletions vlib/net/net_windows.c.v
Expand Up @@ -10,8 +10,11 @@ const is_windows = true
// Constants that windows needs
pub const fionbio = C.FIONBIO
pub const msg_nosignal = 0
pub const msg_dontwait = 0

pub const error_ewouldblock = WsaError.wsaewouldblock
pub const error_einprogress = WsaError.wsaeinprogress
pub const error_eagain = WsaError.wsaewouldblock // on windows, is also wsaewouldblock

const wsa_v22 = 0x202

Expand Down
17 changes: 17 additions & 0 deletions vlib/net/socket.v
Expand Up @@ -9,3 +9,20 @@ pub:
pub fn (s &Socket) address() !Addr {
return addr_from_socket_handle(s.handle)
}

// set_blocking will change the state of the socket to either blocking,
// when state is true, or non blocking (false).
pub fn set_blocking(handle int, state bool) ! {
$if windows {
t := if state { u32(0) } else { u32(1) }
socket_error(C.ioctlsocket(handle, fionbio, &t))!
} $else {
mut flags := C.fcntl(handle, C.F_GETFL, 0)
if state {
flags &= ~C.O_NONBLOCK
} else {
flags |= C.O_NONBLOCK
}
socket_error(C.fcntl(handle, C.F_SETFL, flags))!
}
}

0 comments on commit a9ebab0

Please sign in to comment.