sync tested.

This commit is contained in:
2026-06-08 14:21:36 +02:00
parent 78f4583b6d
commit f0c4f5a4ce
6 changed files with 331 additions and 427 deletions
+130 -111
View File
@@ -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)
]