diff --git a/README.mkdn b/README.mkdn index 05ea64e..1c78dee 100644 --- a/README.mkdn +++ b/README.mkdn @@ -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 diff --git a/src/select.lisp b/src/select.lisp index ad601b9..aa52c77 100644 --- a/src/select.lisp +++ b/src/select.lisp @@ -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)) diff --git a/tests/select.lisp b/tests/select.lisp index 9fc862a..d5bc943 100644 --- a/tests/select.lisp +++ b/tests/select.lisp @@ -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")))