Module xchan.occ - an occam model of Oyvind's 'XCHAN' (CPA 2012)

an occam model of Oyvind's 'XCHAN' (CPA 2012).

We model an XCHAN (buffered or unbuffered) with an occam process:

:
                  -----------------------
           ready  |                     |
         ----<----|                     |   out
                  |      xchan (n)      |---->----
         ---->----|                     |
            in    |                     |
                  -----------------------

Application messages flow from 'in' to 'out'. The device signals TRUE down the 'ready' channel when, and only when, it will accept input: this signal must be taken by a writing process before sending anything. Events on 'ready' and 'in' strictly alternate, starting with 'ready'. The buffering capacity is 'n' (>= 0).

A reading process simply reads from the output channel of the device. Warning: a current implementation restriction means reading from a zero-buffered XCHAN must be done twice, discarding the first item.

A writing process has three choices: an asynchronous write (that immediately returns with an indication of whether the write succeeded), a synchronous write (that does not return until the write succeeds), or ALT on the 'ready' channel and other events (until the 'ready' is signalled and, then, write the message).

This module provides two versions of XCHAN: xchan.a and xchan.b. They differ only in behaviour if their buffering capacity is set to zero: xchan.b is better (but slightly more expensive).

The structure of this module is described in MODULE.STRUCTURE.

Index

Declarations

xchan.occ:113Constant MODULE.STRUCTURE

VAL INT MODULE.STRUCTURE

First, the data type carried by the XCHAN (STUFF) is declared, together with a constant of that type (DUMMY.STUFF) (which can have any value). Because occam-pi does not currently support generic types, these must be edited to the type required by the application using the XCHAN.

Next come the basic mechanisms for writing to XCHANs: xchan.async.write, xchan.blocking.write and PATTERN.A. Beware that these do not work for the second (better) implementation of an unbuffered XCHAN - see below.

The simplest XCHAN implementation is for a buffered XCHAN with capacity 1 (xchan.one) and this comes next.

Then follow a series of blocking buffer processes (buffer.one, buffer.a, buffer.b, buffer.c and buffer.d). These are private processes, used in the implementation below.

A buffered XCHAN with capacity greater than 1 is just a one-place XCHAN (xchan.one) pipelined into a blocking buffer (buffer.d), that provides the rest of the capacity. This is wrapped into (xchan.buffered.a), which is a buffered XCHAN with capacity greater than or equal to 1.

A modest optimisation on the above follows: xchan.buffered.b. This is preceded by two private support processes: buffer.one.bool and x.ring.buffer.

Now follow two implementations for an unbuffered XCHAN: simple unbuffered (xchan.zero.a) and better unbuffered (xchan.zero.b). The latter requires small changes in the mechanisms for writing to it: xchan.async.write.b, xchan.blocking.write.b and PATTERN.B.

Unfortunately, both the above unbuffered XCHAN implementations require extended output: a feature not yet supported by occam-pi. There is a simple WORK.AROUND, given next by xchan.zero.a2 and xchan.zero.b2 (for the simple and better behaviour, respectively). However, these require a change to the way the unbuffered XCHAN is read (xchan.zero.sync.read or PATTERN.C).

Note: the goal is to have the same mechanisms for reading and writing XCHANs, regardless of whether they are buffered or unbuffered. If occam-pi supported extended output, this could be achieved: reading would be an occam primitive read (?), from the output channel of the XCHAN, and writing would use either xchan.async.write.b, xchan.blocking.write.b or PATTERN.B.

As things stand, an occam primitive read (?) can only be used for buffered XCHANs but the read-discard-read-keep pattern (xchan.zero.sync.read or PATTERN.C) is needed for unbuffered XCHANs.

For writing, xchan.async.write.b, xchan.blocking.write.b or PATTERN.B can be used for all XCHANs. However, xchan.async.write, xchan.blocking.write or PATTERN.A are marginally more efficient for buffered XCHANs and for the first version unbuffered XCHANs (xchan.zero.a2, simple behaviour), but cannot be used the second version (xchan.zero.b2, better behaviour).

Finally, xchan.a and xchan.b are processes implementing XCHANs of any capacity (i.e. buffered or unbuffered), offering different implementation choices taking into account the points in the last two paragraphs. Please see their documentation for how they must be used.

xchan.occ:120Data type STUFF

DATA TYPE STUFF

occam-pi currently has no generic types, so we must define code to operate on some specific type. To build an XCHAN for another type, change this declaration to what you want. See also DUMMY.STUFF.

xchan.occ:129Constant DUMMY.STUFF

VAL STUFF DUMMY.STUFF

This is currently needed to support reading from an unbuffered XCHAN (see WORK.AROUND, xchan.zero.a2 and xchan.zero.b2).

Any value of the STUFF type may be chosen for this constant. Implementor's note: choose a value with minimal memory footprint.

xchan.occ:145Process xchan.async.write

PROC xchan.async.write (VAL STUFF data, BOOL success, CHAN BOOL ready.xchan?, CHAN STUFF to.xchan!)

This is an asynchronous write for an XCHAN. It never blocks and returns with an indication of whether it was able to perform the write.

Commonly, this is the first thing tried by a writing process: if it fails, then the ALTing pattern on the ready channel may be engaged (PATTERN.A) rather than continued attempts to write using this process.

Parameters:

VAL STUFF data This is the message to be written.
BOOL success This indicates whether the write happened.
CHAN BOOL ready.xchan This is the ready channel from the XCHAN device.
CHAN STUFF to.xchan This is the input channel to the XCHAN device.

xchan.occ:169Process xchan.blocking.write

PROC xchan.blocking.write (VAL STUFF data, CHAN BOOL ready.xchan?, CHAN STUFF to.xchan!)

This is a synchronous write for an XCHAN. It will block until the XCHAN is able to take the message.

This procedure would not normally be used (since a primitive channel or conventional blocking FIFO process would be more efficient). It is included for completeness.

Parameters:

VAL STUFF data This is the message to be written.
CHAN BOOL ready.xchan This is the ready channel from the XCHAN device.
CHAN STUFF to.xchan This is the input channel to the XCHAN device.

xchan.occ:202Constant PATTERN.A

VAL INT PATTERN.A

This is the third choice for writing to an XCHAN: wait for the device to become ready, whilst servicing other events. For example:

:
    -- Pattern 'A'
    INITIAL BOOL wanting.to.write IS TRUE:
    WHILE wanting.to.write
      ALT                        -- or PRI ALT
        BOOL any:
        ready.xchan ? any
          SEQ
            to.xchan ! data
            wanting.to.write := FALSE
        ...  process other guards (which may change 'data')

The writer may adopt this pattern at any time: there is no obligation to try an xchan.async.write first.

Note that there is no obligation on the writer to send the data it originally had; it is free to discard that and send, for example, data acquired since it started waiting.

xchan.occ:216Process xchan.one

PROC xchan.one (CHAN BOOL ready!, CHAN STUFF in?, out!)

This is a one-place buffered XCHAN process.

Its behaviour is exactly that of an auto-prompter, a common occam idiom.

Parameters:

CHAN BOOL ready This is signalled (with TRUE) when, and only when, data on the channel can be taken. This signal must be taken before data may be sent.
CHAN STUFF in Data input
CHAN STUFF out Data output

xchan.occ:231Constant BUFFERED.XCHAN.CAPACITY

VAL INT BUFFERED.XCHAN.CAPACITY

To build a buffered XCHAN process with application-defined capacity, we just need a one-place buffered XCHAN process (xchan.one) pipelined with a standard blocking buffer (with capacity one less than required for the buffered XCHAN). First, we build the latter.

xchan.occ:240Process buffer.one

PROC buffer.one (CHAN STUFF in?, out!)

This is a standard one-place blocking buffer, commonly known as an id-process. It just copies input to output.

Parameters:

CHAN STUFF in Data input
CHAN STUFF out Data output

xchan.occ:265Process buffer.a

PROC buffer.a (VAL INT max, CHAN STUFF in?, out!)

This is a standard blocking buffer process with application-defined capacity, implemented as a pipeline of one-place blocking buffers (buffer.one). For this implementation, the capacity must be more than one.

Warning: this process does not compile (because occam-pi runtime sized channel arrays currently have to be built from arrays of mobile channel-ends - see buffer.b). It is presented here for easier understanding of its code (and because occam-pi will eventually compile it).

Parameters:

VAL INT max The maximum capacity of this buffer (max >= 1).
CHAN STUFF in Data input
CHAN STUFF out Data output

xchan.occ:289Channel type STUFF.CHAN

CHAN TYPE STUFF.CHAN

To implement in a way that compiles, we must build the channel array from mobile channel-ends. This declares the needed mobile channel type (a trivial structure with one field). This is a private declaration, used only for the implementation of buffer.b.

xchan.occ:303Process buffer.b

PROC buffer.b (VAL INT max, CHAN STUFF in?, out!)

This is a standard blocking buffer process with application-defined capacity, implemented as a pipeline of one-place blocking buffers (buffer.one). This implementation will compile and run correctly.

Parameters:

VAL INT max The maximum capacity of this buffer (max >= 1).
CHAN STUFF in Data input
CHAN STUFF out Data output

xchan.occ:333Constant BUFFER.SERIAL

VAL INT BUFFER.SERIAL

We can build a buffer process in a more serial way (that will be much more efficient if implemented by software). In concept, it is slightly more complicated (but only slightly) than a pipeline of one-place blocking buffers. It is taken from the "Concurrency Design and Practice" course at the University of Kent.

xchan.occ:345Process buffer.c

PROC buffer.c (VAL INT max, CHAN STUFF in?, out!, CHAN BOOL request?)

This is a standard blocking buffer process with application-defined capacity, implemented as classic ring buffer. However, this needs a request channel that the reader process must signal before reading.

Parameters:

VAL INT max The maximum capacity of this buffer (max >= 1).
CHAN STUFF in Data input
CHAN STUFF out Data output
CHAN BOOL request The reader must signal (value irrelevant) on this before reading.

xchan.occ:386Process buffer.d

PROC buffer.d (VAL INT max, CHAN STUFF in?, out!)

This is a standard blocking buffer process with application-defined capacity, implemented as classic ring buffer. It eliminates the need for a request channel by pipelining buffer.c with an auto-prompter (which is, of course, xchan.one).

Parameters:

VAL INT max The maximum capacity of this buffer (max >= 1).
CHAN STUFF in Data input
CHAN STUFF out Data output

xchan.occ:413Process xchan.buffered.a

PROC xchan.buffered.a (VAL INT max, CHAN BOOL ready!, CHAN STUFF in?, out!)

This is a one-buffered XCHAN process with application-defined capacity.

It is built from a one-place buffered XCHAN process (xchan.one) pipelined with a standard blocking buffer (buffer.d).

Parameters:

VAL INT max The maximum capacity of this XCHAN (max >= 1).
CHAN BOOL ready This is signalled (with TRUE) when, and only when, data on the channel can be taken. This signal must be taken before data may be sent.
CHAN STUFF in Data input
CHAN STUFF out Data output

xchan.occ:435Constant OPTIMISED.BUFFERED.XCHAN

VAL INT OPTIMISED.BUFFERED.XCHAN

Note: messages passing through xchan.buffered.a pass through three hops (for capacities greater than 2). The version originally devised (xchan.buffered.b) makes messages pass through only two hops. However, it makes the ready signal also pass through two hops - so may not be any faster! First, we need an id-process for those ready signals (buffer.one.bool) and, then, a ring buffer folded with XCHAN code (x.ring.buffer).

xchan.occ:444Process buffer.one.bool

PROC buffer.one.bool (CHAN BOOL in?, out!)

This is a standard one-place blocking buffer, commonly known as an id-process. It just copies input to output.

Parameters:

CHAN BOOL in Data input
CHAN BOOL out Data output

xchan.occ:477Process x.ring.buffer

PROC x.ring.buffer (VAL INT max, CHAN STUFF in?, out!, CHAN BOOL prompt?, ready!)

Standard ring buffer modified to provide an XCHAN ready signal.

This is a service process for the xchan.buffered.b (below). It should not be used directly by systems.

There must be an xchan.one auto-prompter driving the prompt and out channels. There must be a buffer.one.bool id-process forwarding ready signals.

A 'ready' signal is offered if and only if space is available to buffer another item of data. Events 'ready' and 'in' must strictly alternate, starting with 'ready'.

To write to 'in', a 'ready' signal (forwarded by 'id.bool') must first be accepted by the writer. Disregarding this protocol leads to this process STOPping and probable deadlock.

Parameters:

VAL INT max Size of the buffer (>= 1)
CHAN STUFF in Data input
CHAN STUFF out Data output
CHAN BOOL prompt Reader must prompt for output
CHAN BOOL ready Writer must take this signal before writing

xchan.occ:543Process xchan.buffered.b

PROC xchan.buffered.b (VAL INT max, CHAN BOOL ready!, CHAN STUFF in?, out!)

This is a one-buffered XCHAN process with application-defined capacity.

Historical note: this was the original version (just before CPA 2012).

Parameters:

VAL INT max The maximum capacity of this XCHAN (max >= 1).
CHAN BOOL ready This is signalled (with TRUE) when, and only when, data on the channel can be taken. This signal must be taken before data may be sent.
CHAN STUFF in Data input
CHAN STUFF out Data output

xchan.occ:561Constant ZERO.BUFFERED

VAL INT ZERO.BUFFERED

Next come zero-buffered XCHANs.

xchan.occ:587Process xchan.zero.a

PROC xchan.zero.a (CHAN BOOL ready!, CHAN STUFF in?, out!)

This is a zero-buffered XCHAN (simple behaviour).

It fishes for a reader by offering an extended output (out !!). When a reader is caught, it fishes for a writer by signalling on ready. When it has caught both, the data is transferred. No buffering is introduced by this process in the connection between its writer and reader.

Its weakness is that a reader is sought before there is any indication that a write is pending. This is addressed in xchan.zero.b.

Warning: this process will not compile since extended output is not yet supported by occam-pi. See xchan.zero.a2 for a work-around.

Parameters:

CHAN BOOL ready This is signalled (with TRUE) when, and only when, a reader is committed to read. This signal must be taken before data may be sent - the sender is guaranteed that the reader will accept.
CHAN STUFF in Data input
CHAN STUFF out Data output

xchan.occ:640Process xchan.zero.b

PROC xchan.zero.b (CHAN BOOL ready!, CHAN STUFF in?, out!)

This is a zero-buffered XCHAN (better behaviour).

It fixes the weakness noted in the documentation for xchan.zero.a. However, it requires slightly different logic for an application process that writes to it (xchan.async.write.b, xchan.blocking.write.b and PATTERN.B). Note: these revised processes and pattern may also be used with all other versions of buffered and unbuffered XCHANs (with only a slight overhead cost).

For a writer to this process, the values from its ready channel are significant. This is because this process first fishes for writer by sending a FALSE signal on ready. As normal, when and only when the writer has something to send, it waits for a signal on ready. However, if that signal was FALSE, the writer must keep waiting until it gets a TRUE. All this waiting can, of course, be done whilst processing other events (using ALT). Meanwhile a writer, by accepting the FALSE, lets this process know that it has something to send and this process then fishes for a reader (using extended output, out !!). Once found, the reader is committed and this process now sends TRUE on ready to encourage the writer to write something (which need not, of course, be what it originally had to send). The writer writes, this process forwards, the reader reads and no buffering semantics have been introduced.

The reader from an xchan.zero.b just does a normal read, as before. Disregarding the new writer protocol leads to deadlock. Checking that a writer has followed this protocol can be done by a simple visual check of the code (to ensure a write follows, and only follows, a TRUE on 'ready') or, automatically, by a specialised tool or simple model check.

Warning: this process will not compile since extended output is not yet supported by occam-pi. See xchan.zero.b2 for a work-around.

Parameters:

CHAN BOOL ready This is signalled with TRUE when, and only when, a reader is committed to read. Prior to that, a FALSE is signalled that should only be accepted when a writer has something to write. The writer must still wait for the TRUE signal before writing - when this happens, the writer is guaranteed that the reader will read.
CHAN STUFF in Data input
CHAN STUFF out Data output

xchan.occ:675Process xchan.async.write.b

PROC xchan.async.write.b (VAL STUFF data, BOOL success, CHAN BOOL ready.xchan?, CHAN STUFF to.xchan!)

This is an asynchronous write for a xchan.zero.b (a zero-buffered XCHAN). It never blocks and returns with an indication of whether it was able to perform the write.

A writer could simply keep using this process when it has data to send. There will be at least one FALSE result (maybe many) before a TRUE. It is up to the writer whether to keep sending the same data until success or fresh data. When a write has succeeded, the writer can be assured the reader has taken it (or is about to take it).

Commonly, this is the first thing tried by a writing process: if it fails, then the ALTing PATTERN.B on the ready channel may be engaged (rather than continued attempts to write using this process).

Parameters:

VAL STUFF data This is the message to be written.
BOOL success This indicates whether the write happened.
CHAN BOOL ready.xchan This is the ready channel from the XCHAN device.
CHAN STUFF to.xchan This is the input channel to the XCHAN device.

xchan.occ:706Process xchan.blocking.write.b

PROC xchan.blocking.write.b (VAL STUFF data, CHAN BOOL ready.xchan?, CHAN STUFF to.xchan!)

This is a synchronous write for an XCHAN. It will block until the XCHAN is able to take the message.

This procedure would not normally be used (since a primitive channel or conventional blocking FIFO process would be more efficient). It is included here for completeness.

Note: the loop in the code is not needed (see the comments). However, if this process is used for writing to buffered XCHANS or the previous version of an unbuffered XCHAN (xchan.zero.a), the comments do not apply: there will only be one TRUE signal and this coding still works.

Parameters:

VAL STUFF data This is the message to be written.
CHAN BOOL ready.xchan This is the ready channel from the XCHAN device.
CHAN STUFF to.xchan This is the input channel to the XCHAN device.

xchan.occ:749Constant PATTERN.B

VAL INT PATTERN.B

More useful may be the following strategy. When a writer has something to send, listen on the ready channel from the XCHAN: this lets the zero-buffered channel know that a writer is waiting. Ignore FALSE readys. When TRUE is received from ready, send the data. The writer can be assured the reader has committed to take it:

:
    -- Pattern 'B'
    INITIAL BOOL wanting.to.write IS TRUE:
    WHILE wanting.to.write
      ALT                             -- or PRI ALT
        BOOL ok:
        ready.xchan ? ok
          IF
            ok                        -- means a reader is waiting
              SEQ
                to.xchan ! data
                wanting.to.write := FALSE
              TRUE
                SKIP
        ...  process other guards (which may change 'data')

The writer may adopt this pattern at any time: there is no obligation to try an xchan.async.write.b first.

Note that there is no obligation on the writer to send the data it originally had; it is free to discard that and send, for example, data acquired since it started waiting.

xchan.occ:757Constant WORK.AROUND

VAL INT WORK.AROUND

Here are the work-arounds for processes xchan.zero.a and xchan.zero.b (which do not compile because occam-pi does not yet support extended output). They require readers to read twice, discarding the first item (DUMMY.STUFF).

xchan.occ:780Process xchan.zero.a2

PROC xchan.zero.a2 (CHAN BOOL ready!, CHAN STUFF in?, out!)

This is a zero-buffered XCHAN (simple behaviour).

See xchan.zero.a for documentation. This process works around the current non-implementation of extended output in occam-pi by applying the standard transformation (given in OEP 42).

However, the transformation also requires a reader from this XCHAN to read twice, discarding the first item received (DUMMY.STUFF). See xchan.zero.sync.read and PATTERN.C.

Parameters:

CHAN BOOL ready This is signalled (value irrelevant) when, and only when, a reader is committed to read. This signal must be taken before data may be sent - the sender is guaranteed that the reader will accept.
CHAN STUFF in Data input
CHAN STUFF out This must always be read twice, discarding the first item received (DUMMY.STUFF). The first read may be part of an ALT; however, the second read must be committed.

xchan.occ:812Process xchan.zero.b2

PROC xchan.zero.b2 (CHAN BOOL ready!, CHAN STUFF in?, out!)

This is a zero-buffered XCHAN (better behaviour).

See xchan.zero.b for documentation. This process works around the current non-implementation of extended output in occam-pi by applying the standard transformation (given in OEP 42).

However, the transformation also requires a reader from this XCHAN to read twice, discarding the first item received (DUMMY.STUFF). See xchan.zero.sync.read and PATTERN.C.

Parameters:

CHAN BOOL ready This is signalled with TRUE when, and only when, a reader is committed to read. Prior to that, a FALSE is signalled that should only be accepted when a writer has something to write. The writer must still wait for the TRUE signal before writing - when this happens, the writer is guaranteed that the reader will read.
CHAN STUFF in Data input
CHAN STUFF out This must always be read twice, discarding the first item received. The first read may be part of an ALT; however, the second read must be committed.

xchan.occ:833Process xchan.zero.sync.read

PROC xchan.zero.sync.read (STUFF data, CHAN STUFF from.xchan?)

This is a trivial process to perform a blocking read from the current work arounds for zero buffered XCHANs (xchan.zero.a2 or xchan.zero.b2). It reads from the XCHAN twice, discarding the first value and returning the second. It's not really needed and only included for completeness.

Parameters:

STUFF data This is what is read from the XCHAN.
CHAN STUFF from.xchan The channel from the XCHAN.

xchan.occ:862Constant PATTERN.C

VAL INT PATTERN.C

The reader may use the first read (from the current work arounds for zero buffered XCHANs: xchan.zero.a2 or xchan.zero.b2) as an ALT guard and commit to the second read as the first part of the response to the guard:

:
    -- Pattern 'C'
    ALT                               -- or PRI ALT
      ...  other guarded processes
      STUFF data:
      from.xchan ? data               -- dummy (ignore data)
        SEQ
          from.xchan ? data           -- response must commit to read
          ...  process the data
      ...  other guarded processes

However, the reader must beware that the writer will make only best efforts to supply the data for the second read and that this is not guaranteed to be immediate.

xchan.occ:885Process xchan.a

PROC xchan.a (VAL INT max, CHAN BOOL ready!, CHAN STUFF in?, out!)

This is a XCHAN process with application-defined capacity.

If the capacity is set to zero, the implementation uses the simple behaviour of the logic documented in xchan.zero.a.

If the capacity is zero, reading from this XCHAN must follow the read-discard-read-keep pattern (xchan.zero.sync.read or PATTERN.C). If the capacity is non-zero, an occam primitive read (?) must be used.

For writing, xchan.async.write, xchan.blocking.write or PATTERN.A should be used, regardless of buffering capacity.

Parameters:

VAL INT max The maximum capacity of this XCHAN (max >= 0).
CHAN BOOL ready This is signalled (with TRUE) when, and only when, data on the channel can be taken. This signal must be taken before data may be sent.
CHAN STUFF in Data input
CHAN STUFF out Data output

xchan.occ:919Process xchan.b

PROC xchan.b (VAL INT max, CHAN BOOL ready!, CHAN STUFF in?, out!)

This is a XCHAN process with application-defined capacity.

If the capacity is set to zero, the implementation uses the better behaviour of the logic documented in xchan.zero.b.

If the capacity is zero, reading from this XCHAN must follow the read-discard-read-keep pattern (xchan.zero.sync.read or PATTERN.C). If the capacity is non-zero, an occam primitive read (?) must be used.

For writing, xchan.async.write.b, xchan.blocking.write.b or PATTERN.B should be used, regardless of buffering capacity. However, if the capacity is non-zero, xchan.async.write, xchan.blocking.write or PATTERN.A are marginally more efficient.

Parameters:

VAL INT max The maximum capacity of this XCHAN (max >= 0).
CHAN BOOL ready This is signalled (with TRUE) when, and only when, data on the channel can be taken. This signal must be taken before data may be sent.
CHAN STUFF in Data input
CHAN STUFF out Data output