276
R. Hamers and S.-S. Jongmans
The API. The API consists of Clojure functions that act as proxies for Clojure’s own functions to send, receive, close channels, and construct channels. The
workflow is shown in Fig. 12: first, the programmer writes an implementation
I using Clojure’s own functions; then, by loading library discourje.core.async
instead of clojure.core.async, the programmer adds instrumentation to the implementation that allows channel actions to be monitored. As the signatures of
Discourje’s send, receive, and close functions are identical to Clojure’s, adding
instrumentation in this way is non-invasive and nearly effortless; the only changes
needed, pertain to channel creation (Sect. 2, Fig. 6), since we require the programmer also to specify which roles will use the channel and associate a monitor
(this is the practical embodiment of function † in Sect. 3, page 275).
4
Discourje’s send function works as follows. When invoked, first, it waits until
the underlying channel c is not full (recall channels in Clojure are bounded and
blocking). Then, at time t 1 , it calls the monitor associated with c to check if
the send is allowed. If yes, at time t 2 , it calls the monitor to update accordingly
and the “actual send” happens through c; if no, only an exception is thrown.
If, between t 1 and t 2 , multiple threads call the monitor to update, only one will
succeed; the others need to retry from the start. Discourje’s receive and close
functions work similarly. In this way, Discourje detects safety violations in a way
that is both sound (if an exception is thrown, the violating action really was not
allowed) and complete (if no exception is thrown, all actions were really allowed).
Extensions. We implemented a number of extensions to the basic framework:
– Multi-cast: Adding to Clojure’s send, receive, and close functions, the API
also contains a multi-cast function to send the same value through n>1 channels, along with special monitoring support in the DSL (more efficient than
monitoring individual communications). Also, the API contains a “multireceive” function that optionally synchronizes all receivers of a multi-cast.
– Java interoperability: Clojure compiles to Java bytecode and runs on the
JVM; this enabled us to extend Discourje to Java. Specifically, we wrote a
thin Java wrapper around Discourje, so Java programmers can easily construct and use Discourje channels, write specifications, and have them monitored from inside their Java programs, regardless of the threading mechanism
(e.g., classical Java threads, thread pools, and parallel streams can be used).
5 Evaluation
General setup. We developed Discourje for two primary usage types:
4 We currently support the following main channel operations of clojure.core.async:
sending, receiving, and closing. Discourje works out-of-the-box for all Clojure programs, except those that use unsupported clojure.core.async features; mixing
Discourje with other concurrency libraries is fine (Sect. 2).
An interesting next step is to also support clojure.core.async’s transducers (operations on data-in-transit): to our knowledge, no existing work on MPST supports
transducers, so supporting those requires significant new theoretical work.
R. Hamers and S.-S. Jongmans
The API. The API consists of Clojure functions that act as proxies for Clojure’s own functions to send, receive, close channels, and construct channels. The
workflow is shown in Fig. 12: first, the programmer writes an implementation
I using Clojure’s own functions; then, by loading library discourje.core.async
instead of clojure.core.async, the programmer adds instrumentation to the implementation that allows channel actions to be monitored. As the signatures of
Discourje’s send, receive, and close functions are identical to Clojure’s, adding
instrumentation in this way is non-invasive and nearly effortless; the only changes
needed, pertain to channel creation (Sect. 2, Fig. 6), since we require the programmer also to specify which roles will use the channel and associate a monitor
(this is the practical embodiment of function † in Sect. 3, page 275).
4
Discourje’s send function works as follows. When invoked, first, it waits until
the underlying channel c is not full (recall channels in Clojure are bounded and
blocking). Then, at time t 1 , it calls the monitor associated with c to check if
the send is allowed. If yes, at time t 2 , it calls the monitor to update accordingly
and the “actual send” happens through c; if no, only an exception is thrown.
If, between t 1 and t 2 , multiple threads call the monitor to update, only one will
succeed; the others need to retry from the start. Discourje’s receive and close
functions work similarly. In this way, Discourje detects safety violations in a way
that is both sound (if an exception is thrown, the violating action really was not
allowed) and complete (if no exception is thrown, all actions were really allowed).
Extensions. We implemented a number of extensions to the basic framework:
– Multi-cast: Adding to Clojure’s send, receive, and close functions, the API
also contains a multi-cast function to send the same value through n>1 channels, along with special monitoring support in the DSL (more efficient than
monitoring individual communications). Also, the API contains a “multireceive” function that optionally synchronizes all receivers of a multi-cast.
– Java interoperability: Clojure compiles to Java bytecode and runs on the
JVM; this enabled us to extend Discourje to Java. Specifically, we wrote a
thin Java wrapper around Discourje, so Java programmers can easily construct and use Discourje channels, write specifications, and have them monitored from inside their Java programs, regardless of the threading mechanism
(e.g., classical Java threads, thread pools, and parallel streams can be used).
5 Evaluation
General setup. We developed Discourje for two primary usage types:
4 We currently support the following main channel operations of clojure.core.async:
sending, receiving, and closing. Discourje works out-of-the-box for all Clojure programs, except those that use unsupported clojure.core.async features; mixing
Discourje with other concurrency libraries is fine (Sect. 2).
An interesting next step is to also support clojure.core.async’s transducers (operations on data-in-transit): to our knowledge, no existing work on MPST supports
transducers, so supporting those requires significant new theoretical work.
