#gaato/http/rt

    The ports through which the connection engines of gaato/http and the client of gaato/mttp reach a runtime. Each is a trait; this package implements none of them, so code written against it imports no event loop and builds on every target.

    PortWhat it provides
    ClockWall-clock time and delays
    TimerA time limit for a piece of work (within)
    GateA counting, FIFO admission gate
    ConnAn owned, full-duplex byte connection
    DialerOpening connections and upgrading them to TLS
    SignalA sticky one-shot notification
    CoordinationStructured concurrency: a primary task and its monitor

    default_random returns a sample in [0, 1) for jitter and boundaries; it is not for secrets.

    The implementations live elsewhere:

    • gaato/http-async implements them on moonbitlang/core/async: sockets, TLS, timers and task groups.
    • gaato/http/sim implements them in memory on the deterministic driver, for tests on every target, and holds the contract checkers that both implementations pass.

    The rules each side may rely on (sticky EOF, idempotent close, who owns a connection after a TLS upgrade, how supervision ends, how cancellation travels) are in the repository's CONTRACT.md (https://github.com/gaato/mttp/blob/main/CONTRACT.md).

    #Example

    Code takes the ports as trait objects, so a test can hand it a double:

    ///|
    /// Waits `attempts` times, doubling the delay each time, and returns the
    /// time spent.
    async fn backoff(clock : &@rt.Clock, attempts : Int32) -> Int64 {
    let start = clock.now_unix_ms()
    for i = 0, delay = 100; i < attempts; i = i + 1, delay = delay * 2 {
    clock.sleep(delay)
    }
    clock.now_unix_ms() - start
    }

    ///|
    /// A clock whose sleeps move its time forward at once.
    struct Instant {
    mut now : Int64
    }

    ///|
    impl @rt.Clock for Instant with fn sleep(self, ms) {
    if ms > 0 {
    self.now += ms.to_int64()
    }
    }

    ///|
    impl @rt.Clock for Instant with fn now_unix_ms(self) {
    self.now
    }

    ///|
    test "back off on a test clock" {
    let clock = Instant::{ now: 0, }
    assert_eq(@driver.run_sync(() => backoff(clock, 3)), 700L)
    }

    Clock

    pub(open) trait Clock {
    async fn sleep(Self, Int) -> Unit noraise
    fn now_unix_ms(Self) -> Int64
    }

    A source of wall-clock time and of delays.

    Conn

    pub(open) trait Conn {
    async fn read(Self, max_len~ : Int) -> Bytes? raise
    TransportError

    async fn write(Self, Bytes) -> Unit raise
    TransportError

    fn close(Self) -> Unit
    }

    An owned, full-duplex byte connection.

    One read and one write may run at the same time. rt.Conn has no write-half shutdown, because the runtime offers none for sockets or TLS.

    Coordination

    pub(open) trait Coordination {
    fn signal(Self) -> &Signal
    async fn supervise(Self, primary~ : async () -> Unit raise
    TransportError
    , monitor~ : async () -> Unit raise
    TransportError
    ) -> Unit raise
    TransportError

    }

    The structured concurrency a connection engine needs.

    Dialer

    pub(open) trait Dialer {
    async fn connect(Self, host~ : String, port~ : Int) -> &Conn raise
    TransportError

    async fn start_tls(Self, &Conn, host~ : String) -> &Conn raise
    TransportError

    }

    Opens connections and upgrades them to TLS, without naming a runtime or TLS type.

    Gate

    pub(open) trait Gate {
    async fn acquire(Self) -> Unit noraise
    fn release(Self) -> Unit
    }

    A counting admission gate: a caller holds a slot across an exchange.

    Signal

    pub(open) trait Signal {
    async fn wait(Self) -> Unit noraise
    fn notify(Self) -> Unit
    }

    A sticky one-shot notification.

    Timer

    pub(open) trait Timer {
    async fn within(Self, Int, async () -> Unit noraise) -> Bool noraise
    }

    A time limit for a piece of work. Trait methods cannot be generic, so the work returns nothing: a caller that wants a result writes it into a cell the closure captures.

    default_random

    fn default_random() -> Double

    A sample in [0, 1) for retry jitter and multipart boundaries: host randomness when the target has it, otherwise a deterministic xorshift sequence. Neither is suitable for secrets, tokens or cryptographic nonces. Code that must be reproducible takes a random source as a parameter with this function as its default.