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 a Drop would 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.rs proves that at the mock, through both pg_cancel_backend and a client-side cancel.

For the people who maintain it

PathWhat
src/The executor, the network stack, the rate limiter, the row's JSON, and the scan. Chapter 6.
BUCKThe library, built with pg_major = True: ffi.rs and plan.rs differ between PG17 and PG18 by the pgNN feature.
Cargo.tomlGenerated 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/ →