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/) →