146 lines
5.4 KiB
Racket
146 lines
5.4 KiB
Racket
#lang scribble/manual
|
|
|
|
@(require (for-label racket/base
|
|
racket/contract
|
|
racket/serialize
|
|
"../main.rkt"))
|
|
|
|
@title{port-channel}
|
|
@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]]
|
|
|
|
@defmodule[port-channel]
|
|
|
|
The @racketmodname[port-channel] module wraps a Racket port as an asynchronous,
|
|
channel-like object. It is intended as a small building block for stream-based
|
|
communication protocols, for example between a parent Racket process and a
|
|
subprocess connected through standard input and standard output.
|
|
|
|
A port-channel transports serialized Racket values. The implementation uses
|
|
@racketmodname[racket/serialize]: values are passed through @racket[serialize],
|
|
written to the port, read back as Racket data and reconstructed with
|
|
@racket[deserialize]. Ordinary data values work directly. Custom structures must
|
|
be defined with @racket[serializable-struct]. Ports, threads, custodians and
|
|
ordinary procedures are not serializable values.
|
|
|
|
@section{Model}
|
|
|
|
A port-channel has one of two directions:
|
|
|
|
@itemlist[
|
|
@item{@racket['input]: a reader thread reads complete serialized values from an
|
|
input port and puts the deserialized values on an internal asynchronous
|
|
channel. The port-channel is itself a synchronizable event, so
|
|
@racket[(sync pc)] returns the next complete value.}
|
|
@item{@racket['output]: @racket[port-channel-put] queues values on an internal
|
|
asynchronous channel. A writer thread serializes and writes queued values
|
|
to the output port.}]
|
|
|
|
Buffered I/O alone is not enough to provide this behaviour. Synchronizing on a
|
|
raw input port only says that some input is available; it does not guarantee that
|
|
a complete serialized value is ready. The reader thread converts the byte stream
|
|
into complete channel messages.
|
|
|
|
@section{Reference}
|
|
|
|
@defproc[(make-port-channel [port port?]
|
|
[#:direction direction (or/c 'auto 'input 'output) 'auto]
|
|
[#:source source any/c 'port]
|
|
[#:close? close? any/c #t])
|
|
port-channel?]{
|
|
Creates a port-channel for @racket[port]. With @racket['auto], the direction is
|
|
inferred from the port. An input-only port becomes an input port-channel. An
|
|
output-only port becomes an output port-channel. If the port is both an input and
|
|
an output port, @racket[#:direction] must be supplied explicitly.
|
|
|
|
The @racket[source] value is stored in reported @racket[port-channel-error]
|
|
values. When @racket[close?] is true, @racket[close-port-channel] closes the
|
|
underlying port.}
|
|
|
|
@defproc[(port-channel? [v any/c]) boolean?]{
|
|
Returns true when @racket[v] is a port-channel.}
|
|
|
|
@defproc[(port-channel-direction [pc port-channel?]) (or/c 'input 'output)]{
|
|
Returns the direction of @racket[pc].}
|
|
|
|
@defproc[(port-channel-put [pc port-channel?] [v any/c]) void?]{
|
|
Queues @racket[v] for writing to an output port-channel. The call only enqueues
|
|
the value; serialization and port I/O are performed by the writer thread.
|
|
|
|
The value must be serializable by @racket[serialize]. If serialization or writing
|
|
fails, a @racket[port-channel-error] is published on the error channel.}
|
|
|
|
@defproc[(port-channel-get [pc port-channel?]) any/c]{
|
|
Returns the next value from an input port-channel. This is equivalent to
|
|
@racket[(sync pc)]. When the underlying input stream reaches end-of-file,
|
|
@racket[eof] is published as the final value.}
|
|
|
|
@defproc[(port-channel-try-get [pc port-channel?]) any/c]{
|
|
Attempts to get a value from an input port-channel without blocking. Returns
|
|
@racket[#f] when no value is currently available.}
|
|
|
|
@defproc[(port-channel-evt [pc port-channel?]) evt?]{
|
|
Returns @racket[pc] as a synchronizable event. This is mainly a convenience for
|
|
code that wants an explicit event-producing function.}
|
|
|
|
@defproc[(close-port-channel [pc port-channel?]) void?]{
|
|
Closes the port-channel. For an input port-channel, this closes the input port.
|
|
For an output port-channel, a close marker is queued, so values already queued
|
|
before the close marker are written first.}
|
|
|
|
@defproc[(port-channel-wait [pc port-channel?]) void?]{
|
|
Waits until the reader or writer thread of @racket[pc] has terminated.}
|
|
|
|
@section{Errors}
|
|
|
|
@defstruct*[port-channel-error ([source any/c]
|
|
[message string?])]{
|
|
Represents an error reported by the reader or writer thread.}
|
|
|
|
@defproc[(port-channel-error-get [pc port-channel?]) port-channel-error?]{
|
|
Blocks until the next error is available.}
|
|
|
|
@defproc[(port-channel-error-try-get [pc port-channel?]) any/c]{
|
|
Attempts to get the next error without blocking. Returns @racket[#f] when no
|
|
error is currently available.}
|
|
|
|
@defproc[(port-channel-error-evt [pc port-channel?]) evt?]{
|
|
Returns the error channel as a synchronizable event.}
|
|
|
|
@section{Example}
|
|
|
|
@racketblock[
|
|
(require port-channel)
|
|
|
|
(define-values (in out) (make-pipe))
|
|
|
|
(define reader (make-port-channel in))
|
|
(define writer (make-port-channel out))
|
|
|
|
(port-channel-put writer '(hello 1 2 3))
|
|
(sync reader)
|
|
]
|
|
|
|
The result is:
|
|
|
|
@racketblock[
|
|
'(hello 1 2 3)
|
|
]
|
|
|
|
@section{Serializable structures}
|
|
|
|
@racketblock[
|
|
(require racket/serialize
|
|
port-channel)
|
|
|
|
(serializable-struct message (id payload) #:transparent)
|
|
|
|
(define-values (in out) (make-pipe))
|
|
(define reader (make-port-channel in))
|
|
(define writer (make-port-channel out))
|
|
|
|
(port-channel-put writer (message 1 '(a b c)))
|
|
(sync reader)
|
|
]
|
|
|
|
The received value is a reconstructed @racket[message] structure.
|