# Troubleshooting

> Known failure modes: missing io_method=sync, stack overflows, wrong share/tz path, initdb option gaps, root execution refused, missing psql client, and unported extension loads.

- Repository: malisper/pgrust
- GitHub: https://github.com/malisper/pgrust
- Human docs: https://grok-wiki.com/public/docs/malisper-pgrust-7e0c8e82bc21
- Complete Markdown: https://grok-wiki.com/public/docs/malisper-pgrust-7e0c8e82bc21/llms-full.txt

## Source Files

- `README.md`
- `scripts/run-recovery-tap`
- `scripts/run-regression`
- `crates/backend/main/main_main/src/lib.rs`
- `crates/backend/storage/buffer/bufmgr/src/buf_aio.rs`
- `.cargo/config.toml`
- `docker/entrypoint.sh`

---

---
title: "Troubleshooting"
description: "Known failure modes: missing io_method=sync, stack overflows, wrong share/tz path, initdb option gaps, root execution refused, missing psql client, and unported extension loads."
---

pgrust's single `postgres` binary fails in a small set of high-frequency ways: GUC defaults that do not match operational expectations (`io_method`, `max_stack_depth`), process stack limits, baked-in share/timezone paths, a reduced `postgres --initdb` option surface, a hard root check, an external `psql` client dependency, and extension loads that never reach a real `dlopen` C ABI.

## Symptom index

| Symptom | Likely cause | Fix |
| --- | --- | --- |
| Postmaster panic / abort around AIO or `io_method = io_uring` | Default or selected async IO method | Start with `-c io_method=sync` |
| `stack depth limit exceeded`, seam-frame overflow, refuse to boot | OS stack / `RUST_MIN_STACK` / `max_stack_depth` too small | Raise `ulimit -s`, `RUST_MIN_STACK`, and `max_stack_depth` |
| Missing timezone / bootstrap templates / wrong share tree | Wrong `PGRUST_PGSHAREDIR` at build time, or missing `-L` at initdb | Bake share path at build; pass `-L` to `--initdb` |
| `unrecognized initdb option "…"` | Full C `initdb` flag not ported | Use only supported argv; space-separated form only |
| `"root" execution of the PostgreSQL server is not permitted` | Running as uid 0 | Run as unprivileged user (Docker steps down automatically) |
| `error: no psql client found` | Clone-only regression runner | Install PG 18 client or set `PGRUST_PSQL` |
| `could not load library "…": loading C-ABI extension libraries is not supported` | Unported / third-party `.so` extension | Use ported builtins only; see [Use contrib extensions](/contrib-extensions) |

## Missing `io_method=sync`

The GUC `io_method` is `PGC_POSTMASTER` with boot value `worker` (`DEFAULT_IO_METHOD = IOMETHOD_WORKER`), matching Postgres 18. Operational docs, Docker, and regression harnesses still **force** synchronous IO:

```bash
-c io_method=sync
```

| Surface | Behavior |
| --- | --- |
| README / local start | Explicit `-c io_method=sync` |
| `docker/entrypoint.sh` | Injects `PGRUST_SERVER_OPTS` including `io_method=sync` for temp and final servers |
| `scripts/run-regression` | Postmaster argv includes `-c io_method=sync` |
| `scripts/run-recovery-tap` | Writes `io_method = sync` into `$TEMP_CONFIG` for every TAP node |

Selecting `io_method = io_uring` panics at method resolution with a message that points back to `io_method = sync`. Prefer the documented sync setting for any local or harness boot unless you have verified another method end-to-end for your workload.

<Warning>
Starting without `-c io_method=sync` diverges from every first-party runner in this repository. Prefer the same flags the README and Docker entrypoint use.
</Warning>

### Verification

```bash
# In a connected session (GUC is postmaster-level; show only reflects boot setting)
psql -h /tmp -p 5432 -U postgres -d postgres -c "SHOW io_method;"
```

Expect `sync` when you started with the required override.

## Stack overflows

Rust frames, seam indirection, and panic-based `ereport(ERROR)` produce deeper stacks than C Postgres. Three independent knobs matter:

| Control | Typical value | Role |
| --- | --- | --- |
| `ulimit -s` | `65520` | Process stack size (kB-class units as set by the shell) |
| `RUST_MIN_STACK` | `33554432` (32 MiB) | Main/thread stack for Rust runtime |
| `max_stack_depth` GUC | boot `2048` kB (raised vs C's `100`); harness often `60000` | Postgres stack-depth check |

pgrust intentionally diverges from C on the `max_stack_depth` boot value (`2048` kB vs `100` kB) so catalog-heavy clients (for example `\d`) do not fail on a stock instance. Harnesses and Docker still raise it further (`60000` or `7000kB` in recovery TAP).

### Required start pattern

```bash
ulimit -s 65520
export RUST_MIN_STACK=33554432

target/release/postgres \
  -D /tmp/pgrust-data \
  -F \
  -c listen_addresses= \
  -k /tmp \
  -p 5432 \
  -c io_method=sync \
  -c max_stack_depth=60000
```

Docker applies the same stack limits and GUC pair automatically via `docker/entrypoint.sh`.

### Symptoms

- SQL error: `stack depth limit exceeded`
- Failures on deep catalog queries or `\d` when `max_stack_depth` is left near C defaults
- Hard process stack overflow if only GUCs are raised but `ulimit -s` / `RUST_MIN_STACK` stay low

## Wrong share / timezone path

Install directories are compile-time `option_env!` values in `boot_paths` (`PGRUST_PGSHAREDIR` and related `PGRUST_*` path vars). Defaults without env are `/usr/local/pgsql/share` (and siblings). Wrong bake-in is a recurring footgun: the postmaster then resolves `timezone/` and `timezonesets/` against a missing or unrelated tree.

| Knob | When | Effect |
| --- | --- | --- |
| `PGRUST_PGSHAREDIR` at **build** | `cargo build` | Baked into binary; used for share/tz derivation at runtime |
| `.cargo/config.toml` `[env]` | Default build | Sets `PGRUST_PGSHAREDIR = "/tmp/pgrust_share"` when unset (override with an explicit env value) |
| `-L <sharedir>` on **`--initdb`** | Cluster create | Share templates for bootstrap (`postgres.bki`, `system_*.sql`, samples, extension SQL) |
| Runtime path relative to executable | Postmaster | When binary is outside a normal prefix layout, relative derivation may miss the tree |

### Documented build + init

```bash
PGRUST_PGSHAREDIR="$PWD/vendor/postgres-18.3/share" \
  cargo build --release --locked --bin postgres

target/release/postgres --initdb \
  -D /tmp/pgrust-data \
  -L "$PWD/vendor/postgres-18.3/share" \
  --no-locale \
  --encoding UTF8 \
  -U postgres
```

Docker passes `-L "$PG_SHAREDIR"` (default `/usr/share/postgresql/18` in the image) because the binary lives under `/usr/local/bin`, outside the PGDG bindir layout.

### Vendored tree contents

`vendor/postgres-18.3/share` holds bootstrap and runtime data: `postgres.bki`, system SQL, conf samples, `timezone/`, `timezonesets/`, `tsearch_data/`, `extension/`. Initdb without a correct `-L` (or a binary baked to a tree you never populated) fails early in template setup or later when timezone abbreviation files are read.

## Initdb option gaps

`postgres --initdb` / `postgres initdb` is a pgrust driver inside the same binary, not full C `initdb`. Unsupported flags fail with:

```text
unrecognized initdb option "…"
```

### Supported argv (subset)

| Flag | Notes |
| --- | --- |
| `-D` / `--pgdata` | Required |
| `-U` / `--username` | Bootstrap superuser |
| `-L` | Share directory override |
| `-E` / `--encoding` | Only `SQLASCII`, `UTF8`/`UNICODE`, `LATIN1` |
| `--lc-collate`, `--lc-ctype`, `--locale`, `--no-locale` | Locale flags; `--locale` is accepted but only `C` is honored in practice |
| `--wal-segsize` | Segment size in MB |

### Gaps and parsing rules

- **Space-separated only**: `--opt value`, not `--opt=value` (Docker documents this for `POSTGRES_INITDB_ARGS`).
- **No** `--pwfile`: Docker applies the superuser password later with `ALTER ROLE` against a temp server.
- **No** `-A` / `--auth`, `--data-checksums`, and other C-only switches — they error as unrecognized.
- **No** `POSTGRES_INITDB_WALDIR` → pgrust `--waldir` mapping unless the entrypoint translates it; prefer supported flags.
- Non-empty existing `PGDATA` is refused: `directory "…" exists but is not empty`.

Success ends with:

```text
Success. You can now start the database server using pgrust/postgres -D <pgdata>
```

## Root execution refused

`check_root` in `main_main` refuses euid `0` before dispatch (including `initdb`):

```text
"root" execution of the PostgreSQL server is not permitted.
The server must be started under an unprivileged user ID to prevent
possible system security compromise.  …
```

Also refused: real uid ≠ effective uid (`{progname}: real and effective user IDs must match`).

### Exceptions (root allowed)

| First argv | Root check |
| --- | --- |
| `--help` / `-?`, `--version` / `-V` | Exit before check |
| `--describe-config` | Check skipped |
| `-C` as first switch (with following var) | Check skipped (pg_ctl-style) |

Docker may start as root to fix ownership, then steps down to `postgres` before running the server — matching the official image contract.

## Missing `psql` client

pgrust ships the **server** binary only. There is no pgrust `psql`. Clients and harnesses need a PostgreSQL 18 `psql` (and Docker images bundle one).

### Clone-only regression runner

`scripts/run-regression` resolves the client in order:

1. `PGRUST_PSQL`
2. `command -v psql`
3. `/tmp/pgrust_pginstall/bin/psql`

If none work:

```text
error: no psql client found.
…
  macOS:  brew install libpq && export PATH="$(brew --prefix libpq)/bin:$PATH"
  or:     export PGRUST_PSQL=/path/to/psql
```

### Install hints

| Platform | Package / path |
| --- | --- |
| macOS | `brew install libpq`; put `$(brew --prefix libpq)/bin` on `PATH` |
| Debian/Ubuntu | `postgresql-client-18` (see README) |
| Docker | Use in-container `psql` against the running service |

Recovery / isolation TAP scripts additionally require a full C install (`PG_INSTALL`) for tools beyond `psql` (`initdb`, `pg_ctl`, …) — a different failure class than clone-only regression.

## Unported extension loads

dfmgr resolves libraries by simple basename (`$libdir/foo.so` → `foo`) against an **in-process builtin registry** first. Ported modules register via `register_builtin_library`. Anything not registered falls through to the OS loader edge, which **does not** `dlopen` real C-ABI `.so` files in this backend.

### Errors you will see

| Message | Meaning |
| --- | --- |
| `could not load library "…": loading C-ABI extension libraries is not supported by this server` | Unported or third-party shared library path |
| `could not access file "…": %m` | Path/stat failure before load |
| `could not find function "…" in file "…"` | Builtin library registered but symbol missing |
| `incompatible library "…": missing magic block` | Magic-block path (OS open path); not the normal unported case |

### Ported as builtins (representative)

Registered contrib / language modules include: `citext`, `hstore`, `ltree`, `pg_trgm`, `pgcrypto`, `pg_stat_statements`, `uuid-ossp`, `pg_prewarm`, `injection_points`, `plpgsql`, `regress` (test helpers), snowball, and output plugin `test_decoding`. SQL/control files still come from the share tree (`CREATE EXTENSION` against vendored `extension/`).

### Not generally supported

- Arbitrary Postgres extensions built as `.so` against C Postgres
- PL/Python, PL/Perl, PL/Tcl (status: not generally compatible)
- Unported contrib modules not registered as builtins

`LOAD` of a ported name (for example `plpgsql`) runs the in-process `_PG_init` equivalent without touching the OS loader.

## Decision flow

```text
Boot / connect failure?
├─ root / setuid? ────────────────────► run as unprivileged user
├─ no -c io_method=sync? ─────────────► add io_method=sync
├─ stack errors / refuse to boot? ────► ulimit + RUST_MIN_STACK + max_stack_depth
├─ missing bki / timezone / templates? ► rebuild with PGRUST_PGSHAREDIR; initdb -L
├─ unrecognized initdb option? ───────► drop unsupported C flags; space-separated only
├─ regression: no psql? ──────────────► install client / PGRUST_PSQL
└─ CREATE EXTENSION / LOAD fails? ────► only ported builtins; no C-ABI .so
```

## Quick recovery checklist

<Steps>
  <Step title="Confirm binary and share bake-in">
    Rebuild with an explicit share path when the default `/tmp/pgrust_share` is empty:

    ```bash
    PGRUST_PGSHAREDIR="$PWD/vendor/postgres-18.3/share" \
      cargo build --release --locked --bin postgres
    ```
  </Step>
  <Step title="Recreate PGDATA with -L">
    ```bash
    target/release/postgres --initdb \
      -D /tmp/pgrust-data \
      -L "$PWD/vendor/postgres-18.3/share" \
      --no-locale --encoding UTF8 -U postgres
    ```
  </Step>
  <Step title="Start with required stack and IO settings">
    Use the `ulimit` / `RUST_MIN_STACK` / `io_method=sync` / `max_stack_depth` pattern above (or Docker, which injects them).
  </Step>
  <Step title="Connect and smoke-test">
    ```bash
    psql -h /tmp -p 5432 -U postgres -d postgres \
      -c "select version(), 1 + 1 as two"
    ```
  </Step>
</Steps>

## Related pages

<CardGroup>
  <Card title="Run the server" href="/run-server">
    Postmaster flags, mandatory IO/stack settings, and readiness checks.
  </Card>
  <Card title="Configuration reference" href="/configuration-reference">
    `io_method`, `max_stack_depth`, `RUST_MIN_STACK`, `ulimit`, and `PGRUST_*` path env vars.
  </Card>
  <Card title="Install paths and share directory" href="/install-paths">
    Build-time share wiring, runtime `-L`, and timezone tree layout.
  </Card>
  <Card title="initdb options reference" href="/initdb-reference">
    Supported initdb argv and unsupported flags that error.
  </Card>
  <Card title="Use contrib extensions" href="/contrib-extensions">
    Ported builtin modules versus unported extension loads.
  </Card>
  <Card title="Run regression tests" href="/regression-tests">
    `scripts/run-regression`, `PGRUST_PSQL`, and clone-only client requirements.
  </Card>
  <Card title="Docker" href="/docker">
    Entrypoint-injected server opts and first-boot init behavior.
  </Card>
  <Card title="Compatibility and status" href="/compatibility">
    Non-production status and extension/PL boundaries.
  </Card>
</CardGroup>
