core/sync.try_select_raw: fix TOCTOU

Fixes a TOCTOU where the channel could be used between the call to
can_{recv,send} and {recv,send} causing an unexpected blocking
operation.

To do this we use the non-blocking try_{recv,send} and retry the check
in a loop. This guarantees non-blocking select behaviour, at the cost of
spinning if the input channels are highly contended.

Signed-off-by: Jack Mordaunt <jackmordaunt.dev@gmail.com>
This commit is contained in:
Jack Mordaunt
2025-06-05 15:38:04 -03:00
parent be873af003
commit d5b7302ac0

View File

@@ -1189,51 +1189,53 @@ try_select_raw :: proc "odin" (recvs: []^Raw_Chan, sends: []^Raw_Chan, send_msgs
candidate_count := builtin.len(recvs)+builtin.len(sends)
candidates := ([^]Select_Op)(intrinsics.alloca(candidate_count*size_of(Select_Op), align_of(Select_Op)))
count := 0
for c, i in recvs {
if can_recv(c) {
candidates[count] = {
is_recv = true,
idx = i,
for {
count := 0
for c, i in recvs {
if can_recv(c) {
candidates[count] = {
is_recv = true,
idx = i,
}
count += 1
}
count += 1
}
}
for c, i in sends {
if can_send(c) {
candidates[count] = {
is_recv = false,
idx = i,
for c, i in sends {
if can_send(c) {
candidates[count] = {
is_recv = false,
idx = i,
}
count += 1
}
count += 1
}
}
if count == 0 {
return
}
candidate_idx := rand.int_max(count) if count > 0 else 0
sel := candidates[candidate_idx]
if sel.is_recv {
status = .Recv
if !recv_raw(recvs[sel.idx], recv_out) {
if count == 0 {
return -1, .None
}
} else {
status = .Send
if !send_raw(sends[sel.idx], send_msgs[sel.idx]) {
return -1, .None
}
}
return sel.idx, status
candidate_idx := rand.int_max(count) if count > 0 else 0
sel := candidates[candidate_idx]
if sel.is_recv {
status = .Recv
if !try_recv_raw(recvs[sel.idx], recv_out) {
continue
}
} else {
status = .Send
if !try_send_raw(sends[sel.idx], send_msgs[sel.idx]) {
continue
}
}
return sel.idx, status
}
}
/*
`Raw_Queue` is a non-thread-safe queue implementation designed to store messages
of fixed size and alignment.