Skip to content

Commit

Permalink
Update documentation and tests for SELECT
Browse files Browse the repository at this point in the history
This macro always returned the values of the last form in the chosen
clause; however, this behavior was neither documented, nor verified
by the unit tests. That is now fixed.

Thank you to @Ambrevar for mentioning this, in GitHub Issue #19.
  • Loading branch information
adlai committed Oct 7, 2020
1 parent a030296 commit 1ca5f7f
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 44 deletions.
51 changes: 27 additions & 24 deletions README.mkdn
Original file line number Diff line number Diff line change
Expand Up @@ -127,30 +127,33 @@ Channel API

The syntax is:

select clause*
clause ::= (op form*)
op ::= (recv c &optional variable channel-var) | (send c value &optional channel-var)
| else | otherwise | t
c ::= An evaluated form representing a channel, or a sequence of channels.
variable ::= an unevaluated symbol RECV's return value is to be bound to.
value ::= An evaluated form representing a value to send into the channel.
channel-var ::= An unevaluated symbol that will be bound to the channel the SEND/RECV
operation succeeded on.

SELECT will first attempt to find a clause with a non-blocking op, and execute it. Execution of
the check-if-blocks-and-do-it part is atomic, but execution of the clause's body once the
SEND/RECV clause executes is NOT atomic. If all channel clauses would block, and no else clause is
provided, SELECT will block until one of the clauses is available for execution.

SELECT's non-determinism is, in fact, very non-deterministic. Clauses are chosen at random, not in
the order they are written. It's worth noting that SEND/RECV, when used on sequences of channels,
are still linear in the way they go through the sequence -- the random selection is reserved for
individual SELECT clauses.

Please note that currently, the form for the channel in the RECV and SEND clauses and for the
value in the SEND clause might be evaluated multiple times in an unspecified order. It is thus
undesirable to place forms with side-effects in these places. This is a bug and will be fixed in a
future version of ChanL.
select clause* -> result(s)
clause ::= (op form*)
op ::= (recv c &optional variable channel-var)
| (send c value &optional channel-var)
| else | otherwise | t
c ::= an evaluated form representing a channel, or a sequence of channels.
variable ::= an unevaluated symbol, bound within form* to RECV's return value.
value ::= an evaluated form returning a value to send into the channel.
channel-var ::= An unevaluated symbol that will be bound to the channel the SEND/RECV
operation succeeded on.
result(s) ::= the values returned by the last form in the selected clause.

SELECT will first attempt to find a clause with a non-blocking op, and execute it.
Selecting a clause to execute is atomic, but execution of the clause's body after
the SEND/RECV clause executes is NOT atomic. If all channel clauses would block,
and no else clause is provided, SELECT will thrash-idle (an undesirable state!)
until one of the clauses is available for execution.

SELECT's non-determinism is, in fact, very non-deterministic. Clauses are chosen at
random, not in the order they are written. It's worth noting that SEND/RECV, when
used on sequences of channels, are still linear in the way they go through the
sequence -- the random selection is reserved for individual SELECT clauses.

Please note that currently, the form for the channel in the RECV and SEND clauses
and for the value in the SEND clause might be evaluated multiple times in an
unspecified order. It is thus undesirable to place forms with side-effects in
these places. This is a bug and will be fixed in a future version of ChanL.


Thread API
Expand Down
29 changes: 16 additions & 13 deletions src/select.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,28 @@
The syntax is:
select clause*
select clause* -> result(s)
clause ::= (op form*)
op ::= (recv c &optional variable channel-var) | (send c value &optional channel-var)
op ::= (recv c &optional variable channel-var)
| (send c value &optional channel-var)
| else | otherwise | t
c ::= An evaluated form representing a channel, or a sequence of channels.
variable ::= an unevaluated symbol RECV's return value is to be bound to. Made available to form*.
value ::= An evaluated form representing a value to send into the channel.
c ::= an evaluated form representing a channel, or a sequence of channels.
variable ::= an unevaluated symbol, bound within form* to RECV's return value.
value ::= an evaluated form returning a value to send into the channel.
channel-var ::= An unevaluated symbol that will be bound to the channel the SEND/RECV
operation succeeded on.
result(s) ::= the values returned by the last form in the selected clause.
SELECT will first attempt to find a clause with a non-blocking op, and execute it. Execution of the
check-if-blocks-and-do-it part is atomic, but execution of the clause's body once the SEND/RECV
clause executes is NOT atomic. If all channel clauses would block, and no else clause is provided,
SELECT will thrash-idle (an undesirable state!) until one of the clauses is available for execution.
SELECT will first attempt to find a clause with a non-blocking op, and execute it.
Selecting a clause to execute is atomic, but execution of the clause's body after
the SEND/RECV clause executes is NOT atomic. If all channel clauses would block,
and no else clause is provided, SELECT will thrash-idle (an undesirable state!)
until one of the clauses is available for execution.
SELECT's non-determinism is, in fact, very non-deterministic. Clauses are chosen at random, not
in the order they are written. It's worth noting that SEND/RECV, when used on sequences of
channels, are still linear in the way they go through the sequence -- the random selection is
reserved for individual SELECT clauses."
SELECT's non-determinism is, in fact, very non-deterministic. Clauses are chosen at
random, not in the order they are written. It's worth noting that SEND/RECV, when
used on sequences of channels, are still linear in the way they go through the
sequence -- the random selection is reserved for individual SELECT clauses."
(unless (null clauses)
(let* ((main-clauses (remove :else clauses :key 'clause-type))
(else-clause (find :else clauses :key 'clause-type))
Expand Down
27 changes: 20 additions & 7 deletions tests/select.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,29 @@
(select ((recv channel x) x (5am:fail "SELECT ran a blocking clause"))
(otherwise (5am:pass)))
(pexec () (send channel (recv channel))) (send channel 'foo) (sleep 0.5)
(select ((recv channel x y)
(is (eq 'foo x) "SELECT didn't bind the RECVed value")
(is (eq channel y) "SELECT didn't bind the recved-from chanl"))
(otherwise (5am:fail "SELECT didn't RECV when it could've")))))
(is (equal '(1 2 3)
(multiple-value-list
(select ((recv channel x y)
(is (eq 'foo x)
"SELECT didn't bind the RECVed value")
(is (eq channel y)
"SELECT didn't bind the recved-from chanl")
(values 1 2 3))
(otherwise
(5am:fail "SELECT didn't RECV when it could've")))))
"SELECT didn't return the last form's values")))

(test select-unbuffered-send
(let ((channel (make-instance 'channel)))
(select ((send channel t) (5am:fail "SELECT ran a blocking clause"))
(otherwise (5am:pass)))
(pexec () (recv channel)) (sleep 0.5)
(select ((send channel t x)
(is (eq channel x) "SELECT didn't bind the sent-to chanl"))
(otherwise (5am:fail "SELECT didn't SEND when it could've")))))
(is (equal '(1 2 3)
(multiple-value-list
(select ((send channel t x)
(is (eq channel x)
"SELECT didn't bind the sent-to chanl")
(values 1 2 3))
(otherwise
(5am:fail "SELECT didn't SEND when it could've")))))
"SELECT didn't return the last form's values")))

0 comments on commit 1ca5f7f

Please sign in to comment.