;;; SPDX-License-Identifier: LGPL-3.0-or-later ;;; SPDX-FileCopyrightText: Copyright © 2010-2021 Tony Garnock-Jones #lang syndicate (provide (all-defined-out)) (require "../functional-queue.rkt") (define-logger syndicate/protocol/credit) ;; (credit* Any (U Number 'reset)) ;; (credit Any ... (U Number 'reset)) ;; ;; Send this message to issue `amount` units of credit (in the context ;; of credit-based flow control) to the given `context`. ;; ;; A `context` may identify any essentially asynchronous stream where ;; either the possibility of overwhelming a consumer exists, or the ;; need for occasionally changing the settings of a producer in an ;; atomic way exists. For example, reading HTTP headers proceeds ;; line-by-line until the body is reached, at which point it proceeds ;; byte-by-byte. ;; ;; The `amount` may either be a number or `'reset`, which should zero ;; out (discard) any available credit. In particular, it may be ;; `+inf.0`, effectively turning credit-based flow control off for the ;; named context. ;; ;; See also https://eighty-twenty.org/2011/05/15/origins-of-ack-and-flow-control. ;; (message-struct credit* (context amount)) (define-match-expander credit (syntax-rules () [(_ context ... amount) (credit* (list context ...) amount)]) (syntax-rules () [(_ context ... amount) (credit* (list context ...) amount)])) (define (issue-credit! #:amount [amount 1] . context) (send! (credit* context amount))) (define (issue-unbounded-credit! . context) (send! (credit* context +inf.0))) (define (make-flow-controlled-sender . context) (make-flow-controlled-sender* context)) (define (make-flow-controlled-sender* context) (field [q (make-queue)] [item-credit 0]) (when (log-level? syndicate/protocol/credit-logger 'debug) (begin/dataflow (log-syndicate/protocol/credit-debug "context ~a, queue length ~a, credit ~a" context (queue-length (q)) (item-credit)))) (begin/dataflow (when (and (positive? (item-credit)) (not (queue-empty? (q)))) (define-values (item new-q) (dequeue (q))) (send! item) (q new-q) (item-credit (- (item-credit) 1)))) (on (message (credit* context $amount)) (item-credit (if (eq? amount 'reset) 0 (+ (item-credit) amount)))) (lambda (item) (q (enqueue (q) item)))) ;; It's quite possible that credit-based flow control is not the right ;; approach for Syndicate. Using assertions that describe the content ;; of a stream more relationally ought to allow "replay" of ;; information in different contexts; though the trade-off is not only ;; reduced performance, but a need to garbage-collect ;; no-longer-interesting portions of the stream; that is, ;; *acknowledgements*. In a reliable-delivery context, it would appear ;; that at least one of acks or flow-control is required! (?!?)