This test ensures that contending threads racing to try_send against a
single blocking read will result in exactly one winner without any
senders blocking.
This changes the semantics of try_send to be consistently non-blocking.
That is, if the buffered is full OR there are no readers it returns
false.
The previous behaviour was such that it would block in the latter case
of no reader, and it would wait for a reader. That is problematic
because it produces inconsistent behaviour between buffered and
unbuffered channels which is astonishing and adds complexity to the
caller.
To illustrate the problem with the old behaviour, consider the
try_select operation: if a send-channel happens to be unbuffered the
try_select (which wants to never block) can now block, that unbuffered
send channel is selected (at random) and there is no reader on the other
side. Thus we have unpredictable blocking behaviour, which breaks the
guarantee that try_select never blocks.
If you want a blocking send you can just call "send" (the blocking
variant).
In addition, there is some reader/writer math done inside
can_{send,recv} such that they only report true if there is sufficient
reader/writer capacity. If there is contention we need to ensure that
each reader is paired to exactly one writer.
Consider try_send: if there is a single reader we can send. If there is
a single reader and a single writer, then we cannot send, as that reader
will be paired with the existing writer. Therefore can_send is only true
if there are more readers than writers at the time of check.
NOTE: The original tests don't need to use wait-looping with thread.yield()
or heuristic sleep. Instead we can just use blocking channel operations
rather than non-blocking operations.
These tests will race access to __global_context_for_test, which can
cause the test suite to flake. Even though only a single test actually
references the variable, the logic in try_select consumes it.
This test harness ensures consistent non-blocking semantics and
validates that we have solved the toctou condition.
The __global_context_for_test is a bit of a hack to fuse together the
test supplied proc and the executing logic in packaage chan.
This is necessary because we need to allow the test guarantee against a
rare condition: where a third-party thread steals a value between the
validity checks can_{send,recv} and the channel operation
try_{send,recv}.
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 follows the convention where non-blocking operations are prefixed
with "try" to indicate as much.
Since select_raw in it's current form doesn't block, it should be
try_select_raw, and allow select_raw to name a blocking implementation.
This fixes a flaw in the original implementation: the returned index is
actually useless to the caller.
This is because the index returned refers to the internal "candidate"
list. This list is dynamic, and may not have all of the input channels
(if they weren't ready according to chan.can_{recv,send}). That means
the index is not guaranteed to mean anything to the caller.
The fix introduced here is to return the index into the input slice
(recvs,sends) and an enum to specify which input slice that is.
If no selection was made, then (-1, .None) is returned to communicate as
much.
Signed-off-by: Jack Mordaunt <jackmordaunt.dev@gmail.com>
free on tlsf poisons the entire block, while alloc might only unpoison a
part of it (cause it's size is aligned up). This causes free to
potentially poison an already poisoned portion, which is a
use-after-poison.
Because this is "fine" and intended, I opted to just
@no_sanitize_address it.
The `*_ptr` and `peek_*` procedures did the same thing, except `peek_*`
was over-cautiously putting the index through a modulo when all
assignments to `q.offset` are already wrapped.
`core:math/big` has been verified against Python's big integer implementation long enough.
Turn it into a regular regression test using the `core:testing` framework, testing against
a generated corpus of test vectors.