sync tested.
This commit is contained in:
+130
-111
@@ -4,7 +4,6 @@
|
||||
racket/contract/base
|
||||
racket/async-channel
|
||||
racket/place
|
||||
racket/serialize
|
||||
port-channel
|
||||
uni-channel))
|
||||
|
||||
@@ -13,155 +12,175 @@
|
||||
|
||||
@defmodule[uni-channel]
|
||||
|
||||
The @racketmodname[uni-channel] module provides a small uniform facade over
|
||||
three channel-like transports: @racketmodname[racket/async-channel]
|
||||
asynchronous channels, @racketmodname[racket/place] place channels, and
|
||||
@racketmodname[port-channel] port channels.
|
||||
The @racketmodname[uni-channel] module provides one small wrapper around three
|
||||
Racket channel-like transports: @racket[async-channel?], @racket[place-channel?]
|
||||
and @racket[port-channel?].
|
||||
|
||||
The purpose is not to hide transport semantics completely. A place channel still
|
||||
uses place-channel message constraints and a port channel still serializes values
|
||||
with @racketmodname[racket/serialize]. The purpose is to make code that only
|
||||
needs send, receive, event synchronization and close operations independent from
|
||||
the concrete channel implementation.
|
||||
The constructor is deliberately simple:
|
||||
|
||||
@racketblock[
|
||||
(make-uni-channel channel)
|
||||
]
|
||||
|
||||
The wrapper detects the concrete channel kind and exposes the same small set of
|
||||
operations for sending, receiving, event synchronization and closing. The wrapper
|
||||
does not change the transport semantics of the underlying channel. A
|
||||
@racket[place-channel?] still accepts only place messages, and a
|
||||
@racket[port-channel?] still transports serialized values as defined by
|
||||
@racketmodname[port-channel].
|
||||
|
||||
@section{Model}
|
||||
|
||||
A uni-channel has a kind, a direction, a name and an underlying backend
|
||||
object. The kind is one of @racket['async], @racket['place] or @racket['port].
|
||||
The direction is one of @racket['input], @racket['output] or
|
||||
@racket['bidirectional]. Receiving is supported for input and bidirectional
|
||||
channels. Sending is supported for output and bidirectional channels.
|
||||
A uni-channel has a kind and a direction.
|
||||
|
||||
A uni-channel that supports receiving is itself a synchronizable event:
|
||||
The kind is one of:
|
||||
|
||||
@racketblock[(sync ch)]
|
||||
@itemlist[
|
||||
@item{@racket['async] for an @racket[async-channel?].}
|
||||
@item{@racket['place] for a @racket[place-channel?].}
|
||||
@item{@racket['port] for a @racket[port-channel?].}]
|
||||
|
||||
For output-only channels, synchronizing on the uni-channel uses
|
||||
@racket[never-evt].
|
||||
The direction is one of:
|
||||
|
||||
@section{Constructors}
|
||||
@itemlist[
|
||||
@item{@racket['bidirectional] for async-channel and place-channel wrappers.}
|
||||
@item{@racket['input] or @racket['output] for port-channel wrappers, copied from
|
||||
@racket[port-channel-direction].}]
|
||||
|
||||
@defproc[(make-uni-channel [backend any/c]
|
||||
[#:kind kind (or/c 'auto 'async 'place 'port) 'auto]
|
||||
[#:direction direction (or/c 'auto 'input 'output 'bidirectional) 'auto]
|
||||
[#:name name any/c #f])
|
||||
A uni-channel that supports receiving is itself a synchronizable event.
|
||||
Synchronizing the wrapper returns the value received from the underlying channel:
|
||||
|
||||
@racketblock[
|
||||
(sync ch)
|
||||
]
|
||||
|
||||
For output-only channels, synchronizing on the uni-channel uses @racket[never-evt].
|
||||
Such a channel has no receive side, so it can never become ready as a receive
|
||||
event.
|
||||
|
||||
@section{Constructor}
|
||||
|
||||
@defproc[(make-uni-channel [channel (or/c async-channel? place-channel? port-channel?)])
|
||||
uni-channel?]{
|
||||
Wraps an existing backend channel. With @racket['auto], the kind is inferred from
|
||||
@racket[async-channel?], @racket[place-channel?] or @racket[port-channel?].
|
||||
Wraps @racket[channel] as a uni-channel. The kind and direction are inferred from
|
||||
the supplied channel.
|
||||
|
||||
For a @racket[port-channel?] backend, the direction is read from
|
||||
@racket[port-channel-direction]. A single port-channel endpoint is therefore
|
||||
input-only or output-only. Use @racket[make-uni-port-channel] when one uni
|
||||
channel should combine an input port and an output port into a bidirectional
|
||||
endpoint.}
|
||||
|
||||
@defproc[(make-uni-async-channel [ch async-channel? (make-async-channel)]
|
||||
[#:direction direction (or/c 'auto 'input 'output 'bidirectional) 'bidirectional]
|
||||
[#:name name any/c 'async-channel])
|
||||
uni-channel?]{
|
||||
Wraps a single asynchronous channel. The same underlying queue is used for both
|
||||
sending and receiving when the direction is @racket['bidirectional].}
|
||||
|
||||
@defproc[(make-uni-async-channel-pair [#:name-a name-a any/c 'async-channel-a]
|
||||
[#:name-b name-b any/c 'async-channel-b])
|
||||
(values uni-channel? uni-channel?)]{
|
||||
Creates two bidirectional uni-channel endpoints backed by two asynchronous channels.
|
||||
A value sent on the first endpoint is received on the second endpoint, and the
|
||||
other way around.}
|
||||
|
||||
@defproc[(make-uni-place-channel [ch place-channel?]
|
||||
[#:direction direction (or/c 'auto 'input 'output 'bidirectional) 'bidirectional]
|
||||
[#:name name any/c 'place-channel])
|
||||
uni-channel?]{
|
||||
Wraps an existing place-channel endpoint. Values sent through this backend must
|
||||
satisfy Racket's place-message constraints.}
|
||||
|
||||
@defproc[(make-uni-place-channel-pair [#:name-a name-a any/c 'place-channel-a]
|
||||
[#:name-b name-b any/c 'place-channel-b])
|
||||
(values uni-channel? uni-channel?)]{
|
||||
Creates two bidirectional uni-channel endpoints using @racket[place-channel].}
|
||||
|
||||
@defproc[(make-uni-port-channel [#:input in (or/c #f input-port?) #f]
|
||||
[#:output out (or/c #f output-port?) #f]
|
||||
[#:source source any/c 'port-channel]
|
||||
[#:close? close? any/c #t]
|
||||
[#:name name any/c 'port-channel])
|
||||
uni-channel?]{
|
||||
Creates a uni-channel backed by one or two @racketmodname[port-channel]
|
||||
endpoints. At least one of @racket[#:input] or @racket[#:output] must be
|
||||
provided. With both, the result is bidirectional: receives come from the input
|
||||
port-channel and sends go to the output port-channel.}
|
||||
For @racket[async-channel?] and @racket[place-channel?], the result is
|
||||
@racket['bidirectional]. For @racket[port-channel?], the result has the same
|
||||
direction as the port-channel endpoint. To represent a full-duplex port-based
|
||||
connection, create one input @racket[port-channel?] and one output
|
||||
@racket[port-channel?], then wrap each endpoint separately with
|
||||
@racket[make-uni-channel].}
|
||||
|
||||
@section{Predicates and accessors}
|
||||
|
||||
@defproc[(uni-channel? [v any/c]) boolean?]{Returns true when @racket[v] is a uni-channel.}
|
||||
@defproc[(uni-channel-kind [ch uni-channel?]) (or/c 'async 'place 'port)]{Returns the backend kind.}
|
||||
@defproc[(uni-channel-direction [ch uni-channel?]) (or/c 'input 'output 'bidirectional)]{Returns the channel direction.}
|
||||
@defproc[(uni-channel-name [ch uni-channel?]) any/c]{Returns the diagnostic name supplied when the uni-channel was created.}
|
||||
@defproc[(uni-channel-impl [ch uni-channel?]) any/c]{Returns the underlying backend object.}
|
||||
@defproc[(uni-channel? [v any/c]) boolean?]{
|
||||
Returns true when @racket[v] is a uni-channel.}
|
||||
|
||||
@defproc[(uni-channel-kind [ch uni-channel?]) (or/c 'async 'place 'port)]{
|
||||
Returns the detected backend kind.}
|
||||
|
||||
@defproc[(uni-channel-direction [ch uni-channel?])
|
||||
(or/c 'input 'output 'bidirectional)]{
|
||||
Returns the direction of @racket[ch].}
|
||||
|
||||
@section{Sending and receiving}
|
||||
|
||||
@defproc[(uni-channel-put [ch uni-channel?] [v any/c]) void?]{
|
||||
Sends @racket[v] on @racket[ch]. The channel must be output-capable. For
|
||||
@racket['port] channels, the value must be serializable by @racket[serialize].
|
||||
For @racket['place] channels, the value must be allowed as a place message.}
|
||||
Sends @racket[v] on @racket[ch]. The channel must support output. For
|
||||
@racket['port] channels, the value must be serializable by
|
||||
@racketmodname[racket/serialize]. For @racket['place] channels, the value must be
|
||||
allowed as a place message.}
|
||||
|
||||
@defproc[(uni-channel-send [ch uni-channel?] [v any/c]) void?]{Alias for @racket[uni-channel-put].}
|
||||
@defproc[(uni-channel-get [ch uni-channel?]) any/c]{Receives the next value from @racket[ch].}
|
||||
@defproc[(uni-channel-recv [ch uni-channel?]) any/c]{Alias for @racket[uni-channel-get].}
|
||||
@defproc[(uni-channel-try-get [ch uni-channel?]) any/c]{Attempts to receive a value without blocking. Returns @racket[#f] when no value is currently available.}
|
||||
@defproc[(uni-channel-get-evt [ch uni-channel?]) evt?]{Returns a synchronizable receive event.}
|
||||
@defproc[(uni-channel-put-evt [ch uni-channel?] [v any/c]) evt?]{Returns an event that sends @racket[v] when selected.}
|
||||
@defproc[(uni-channel-send [ch uni-channel?] [v any/c]) void?]{
|
||||
Alias for @racket[uni-channel-put].}
|
||||
|
||||
@defproc[(uni-channel-get [ch uni-channel?]) any/c]{
|
||||
Receives the next value from @racket[ch]. The channel must support input.}
|
||||
|
||||
@defproc[(uni-channel-recv [ch uni-channel?]) any/c]{
|
||||
Alias for @racket[uni-channel-get].}
|
||||
|
||||
@defproc[(uni-channel-try-get [ch uni-channel?]) any/c]{
|
||||
Attempts to receive a value without blocking. Returns @racket[#f] when no value
|
||||
is currently available. As with the underlying Racket operations, a queued
|
||||
@racket[#f] value cannot be distinguished from no value by this function.}
|
||||
|
||||
@defproc[(uni-channel-get-evt [ch uni-channel?]) evt?]{
|
||||
Returns a synchronizable receive event. Synchronizing this event returns the
|
||||
value received from the underlying channel. For output-only channels this returns
|
||||
@racket[never-evt], because no value can be received from that endpoint.}
|
||||
|
||||
@defproc[(uni-channel-put-evt [ch uni-channel?] [v any/c]) evt?]{
|
||||
Returns an event that sends @racket[v] when selected. The channel must support
|
||||
output.}
|
||||
|
||||
@section{Closing}
|
||||
|
||||
@defproc[(uni-channel-close [ch uni-channel?]) void?]{
|
||||
Closes @racket[ch]. For async-channel and place-channel backends this marks the
|
||||
uni-channel wrapper as closed; Racket does not provide a native close operation for
|
||||
those channel types. For port-channel backends this delegates to
|
||||
@racket[close-port-channel]. For a bidirectional port channel only the output
|
||||
side is closed, so already queued values can still be received from the input
|
||||
side until end-of-file.}
|
||||
Closes the uni-channel wrapper. For async-channel and place-channel wrappers this
|
||||
marks only the wrapper as closed, because those Racket channel types do not have a
|
||||
native close operation. For port-channel wrappers this delegates to
|
||||
@racket[close-port-channel]. Calling @racket[uni-channel-close] more than once is
|
||||
safe.}
|
||||
|
||||
@defproc[(uni-channel-closed? [ch uni-channel?]) boolean?]{Returns true when @racket[uni-channel-close] has been called on the wrapper.}
|
||||
@defproc[(uni-channel-closed? [ch uni-channel?]) boolean?]{
|
||||
Returns true when @racket[uni-channel-close] has been called on @racket[ch].}
|
||||
|
||||
@section{Port-channel errors}
|
||||
@section{Transport errors}
|
||||
|
||||
@defproc[(uni-channel-error-get [ch uni-channel?]) any/c]{Blocks until a backend error is available. This is meaningful for @racket['port] channels and raises an exception for backends that do not have an error channel.}
|
||||
@defproc[(uni-channel-error-try-get [ch uni-channel?]) any/c]{Attempts to get a backend error without blocking. Returns @racket[#f] when no error is available.}
|
||||
@defproc[(uni-channel-error-evt [ch uni-channel?]) evt?]{Returns a synchronizable backend-error event. For non-port backends this is @racket[never-evt].}
|
||||
@defproc[(uni-channel-error-get [ch uni-channel?]) any/c]{
|
||||
Blocks until a transport error is available. Some wrapped transports can
|
||||
report asynchronous transport errors, for example I/O or serialization errors.
|
||||
Wrappers that do not have an error stream raise an exception.}
|
||||
|
||||
@defproc[(uni-channel-error-try-get [ch uni-channel?]) any/c]{
|
||||
Attempts to get a transport error without blocking. Returns @racket[#f] when no
|
||||
error is available. Wrappers that do not have an error stream also return
|
||||
@racket[#f].}
|
||||
|
||||
@defproc[(uni-channel-error-evt [ch uni-channel?]) evt?]{
|
||||
Returns a synchronizable transport-error event. Wrappers that do not have an
|
||||
error stream return @racket[never-evt].}
|
||||
|
||||
@section{Examples}
|
||||
|
||||
@subsection{Async-channel pair}
|
||||
@subsection{Async channel}
|
||||
|
||||
@racketblock[
|
||||
(require uni-channel)
|
||||
(require racket/async-channel
|
||||
uni-channel)
|
||||
|
||||
(define ch (make-uni-channel (make-async-channel)))
|
||||
|
||||
(uni-channel-send ch 'hello)
|
||||
(uni-channel-recv ch)
|
||||
]
|
||||
|
||||
@subsection{Place channel pair}
|
||||
|
||||
@racketblock[
|
||||
(require racket/place
|
||||
uni-channel)
|
||||
|
||||
(define-values (raw-a raw-b) (place-channel))
|
||||
(define a (make-uni-channel raw-a))
|
||||
(define b (make-uni-channel raw-b))
|
||||
|
||||
(define-values (a b) (make-uni-async-channel-pair))
|
||||
(uni-channel-send a '(hello from a))
|
||||
(uni-channel-recv b)
|
||||
]
|
||||
|
||||
@subsection{Place-channel pair}
|
||||
@subsection{Port-channel endpoints over a pipe}
|
||||
|
||||
@racketblock[
|
||||
(require uni-channel)
|
||||
|
||||
(define-values (a b) (make-uni-place-channel-pair))
|
||||
(uni-channel-send a '(hello from a))
|
||||
(sync b)
|
||||
]
|
||||
|
||||
@subsection{Port-channel over a pipe}
|
||||
|
||||
@racketblock[
|
||||
(require uni-channel)
|
||||
(require port-channel
|
||||
uni-channel)
|
||||
|
||||
(define-values (in out) (make-pipe))
|
||||
(define ch (make-uni-port-channel #:input in #:output out))
|
||||
(define reader (make-uni-channel (make-port-channel in)))
|
||||
(define writer (make-uni-channel (make-port-channel out)))
|
||||
|
||||
(uni-channel-send ch '(hello 1 2 3))
|
||||
(uni-channel-recv ch)
|
||||
(uni-channel-send writer '(hello 1 2 3))
|
||||
(uni-channel-recv reader)
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user