Chapter 5: postjevsql-pg, where the unsafe lives
Postgres is an old, careful C program with house rules, and an extension lives in its house. Some of the rules are unusual if you come from web servers:
- One process per connection, one thread per process. Your session has a backend process all to itself, and every Postgres function assumes it is being called from that process's one thread. Call one from anywhere else and you corrupt the backend.
- Errors do not return; they jump.
ereport(ERROR)longjmps straight back to the top of the query, past every stack frame in between. Rust frames that own something with aDropwould never run it. - Cancel is a flag and a latch. Ctrl-C does not kill anything: a signal
handler sets a latch, and the backend notices at its next
CHECK_FOR_INTERRUPTS(). Code that sleeps somewhere Postgres cannot see never notices, and the query cannot be cancelled. - Memory belongs to contexts, which are freed wholesale when a query ends, so nothing in them is ever dropped one by one.
This crate is where all of that is met. It is the one crate allowed unsafe,
and its job is to export safe types so that no other crate needs any: an
executor whose only way to wait is Postgres's own, an HTTP/2 client that
runs on it, and the plan node that judges rows (chapter 7).
flowchart TD ext["postjevsql (safe code only)"] --> scan["scan/: the plan node, behind the Judge trait"] ext --> https["https.rs: HttpsTransport, Backend"] scan --> exec["executor.rs: one thread, waits on a WaitEventSet"] https --> net["net.rs: non-blocking TCP, hyper's I/O"] https --> dns["dns.rs: hickory on the executor"] https --> rl["ratelimit.rs: the cluster's limit, in shared memory"] net --> exec dns --> exec rl --> exec
Aside: why not tokio? Everyone's first idea, and wrong twice here. Tokio's usual runtime runs futures on worker threads, where a Postgres call corrupts the backend. Its single-threaded runtime is safer, but it sleeps in its own
epoll, which never sees Postgres's latch, so a cancel would wait for the request to finish. Running tokio in 10 ms slices with interrupt checks between them is polling, and burns CPU to add latency. So the executor here waits on exactly what Postgres waits on, and a cancel lands in the same wait as the network does.
Try it. In
psql, start a statement that judges many rows and press Ctrl-C. It stops at once, mid-request, and the HTTP/2 streams it had open are reset at the server.tests/cancel.rsproves that at the mock, through bothpg_cancel_backendand a client-side cancel.
For the people who maintain it
| Path | What |
|---|---|
| src/ | The executor, the network stack, the rate limiter, the row's JSON, and the scan. Chapter 6. |
| BUCK | The library, built with pg_major = True: ffi.rs and plan.rs differ between PG17 and PG18 by the pgNN feature. |
| Cargo.toml | Generated by tools/cargo-gen (chapter 20). |
The traps each of these closes, and the evidence for each choice, are the
root CLAUDE.md's Implementation §1 and §2.
← Previous: Chapter 4, src/bin/ · Up: crates · Next: Chapter 6, postjevsql-pg/src/ →