1# Chapter 6: postjevsql-pg/src, an async runtime made of Postgres 2 3Rust's `async` is a promise with no engine attached: a future does nothing 4until something polls it, and something has to sleep until there is a 5reason to poll again. That something is an *executor*, and usually you 6borrow one (tokio, in most of the Rust world). Chapter 5 said why this crate 7cannot. So `executor.rs` is a small executor of its own, about 650 lines, 8whose one place to sleep is a Postgres `WaitEventSet`. 9 10```mermaid 11sequenceDiagram 12 participant S as The scan 13 participant E as executor.rs 14 participant W as WaitEventSet (built for this wait) 15 participant P as Postgres 16 S->>E: block_on: wait for the oldest row's answer 17 E->>W: the latch, postmaster death, every registered socket, the earliest timer 18 W-->>E: woken: a socket is readable, a timer is due, or the latch is set 19 E->>P: ResetLatch, CHECK_FOR_INTERRUPTS 20 alt Ctrl-C or statement_timeout 21 P-->>E: ERROR, unwinding: every future in flight is dropped, streams reset 22 else nothing to cancel 23 E->>E: poll the woken tasks 24 E-->>S: the answer, when it is there 25 end 26``` 27 28The set is built afresh for each wait and freed after it, because Postgres 29has no way to remove one socket from a set (no `RemoveWaitEvent` in any 30supported major), and sockets come and go with reconnects and DNS. With the 31usual one HTTP/2 socket this costs a few syscalls against requests of 70 to 32500 ms, which is nothing. 33 34On top of the executor sits an ordinary-looking HTTP stack, every layer 35chosen because it needs no runtime of its own: non-blocking `std` sockets 36(`net.rs`), rustls through futures-rustls for TLS, hyper for HTTP/2, and 37hickory for DNS (`dns.rs`), all driven by the executor. There is one HTTP/2 38connection per backend, reused across queries, and every row a scan judges 39is a stream on it. 40 41> **Aside.** Why bother resolving names on the executor, when libc has 42> `getaddrinfo`? Because `getaddrinfo` blocks, and while it blocks nothing 43> sees a cancel: glibc waits 5 seconds per attempt, per nameserver. DNS is 44> the one step where plain libc would quietly make Ctrl-C not work. The price 45> is that hickory reads only `/etc/resolv.conf` and `/etc/hosts`, so names 46> that need nsswitch (LDAP, mDNS) do not resolve. 47 48> **Try it.** `nix develop -c buck2 test //tests:keepalive` shows the 49> connection's pings at the mock: sent while a scan waits, never between 50> statements, and an unanswered one closing the connection so the request 51> goes again on a new one. 52 53## For the people who maintain it 54 55| Path | What | 56| --- | --- | 57| [lib.rs](lib.rs) | The crate's safe surface: what it exports, and `Error`. | 58| [executor.rs](executor.rs) | The single-threaded executor: `block_on`, `spawn_local`, task `Scope`s that a scan's end or a reset callback drops, socket `register`/`deregister`, timers (`sleep`, `sleep_until`), and `random_unit` from Postgres's PRNG. | 59| [net.rs](net.rs) | Non-blocking TCP and UDP for the executor, `HyperIo` (futures-io to hyper), and `PeerSettings`, which reads the server's `MAX_CONCURRENT_STREAMS` off its frames since hyper keeps it private. | 60| [https.rs](https.rs) | jev-client's two ports: `HttpsTransport` (one reused HTTP/2 connection over rustls; it sends and classifies failures, and never retries) and `Backend` (time, sleep and jitter on the executor). `Endpoint` parses `jev.endpoint`, `KeepAlive` the ping settings, `stream_limit` the server's stream cap. | 61| [dns.rs](dns.rs) | hickory's `RuntimeProvider` on the executor, against `jev.dns_servers` or `/etc/resolv.conf`. | 62| [ratelimit.rs](ratelimit.rs) | The cluster's rate limit: postjevsql-core's GCRA words in a named DSM segment every backend attaches to, the admission wait on the executor, and the feedback from 429s and answers. | 63| [guc.rs](guc.rs) | `define_checked_string` and `StringCheck`: a string setting validated when it is set, with the hook's `unsafe` kept here. | 64| [row.rs](row.rs) | `RowJson`: a row as JSON that is the same whoever asks, with dates and times in RFC 3339 and every other value under fixed output settings. | 65| [owner.rs](owner.rs) | `as_owner_of`: run as a relation's owner, as a SECURITY DEFINER function would, which is how a granted role reaches `jev_cache`. | 66| [stats.rs](stats.rs) | The backend's counters behind `jev_stats()`, and `Telemetry`, jev-client's observer, which logs each retry, redial, connection and answer at DEBUG1. | 67| [scan/](scan/) | The batch scan. Chapter 7. | 68 69← Previous: [Chapter 5, postjevsql-pg/](../) · Up: [postjevsql-pg](../) · Next: [Chapter 7, scan/](scan/) →