#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.