Skip to content

release: 3.7.0 — Rust 1.95, tokio 1.52, edition 2024#229

Open
vadv wants to merge 13 commits intomasterfrom
feature/3.7.0-rust-tokio-edition
Open

release: 3.7.0 — Rust 1.95, tokio 1.52, edition 2024#229
vadv wants to merge 13 commits intomasterfrom
feature/3.7.0-rust-tokio-edition

Conversation

@vadv
Copy link
Copy Markdown
Collaborator

@vadv vadv commented Apr 30, 2026

Summary

  • Bumps Rust toolchain from 1.87.0 to 1.95.0, tokio from 1.49 to 1.52, migrates the project to edition 2024.
  • Fixes all clippy lints surfaced under the new toolchain (auto-fix for the bulk + 11 manual rewrites).
  • Includes a pre-existing fix: coordinator_benchmarks failed to compile on master because notify_idle_returned was pub(crate) and benches/* sees pg_doorman as an external crate. Now #[doc(hidden)] pub.
  • No user-facing behavior change. Operators upgrading from 3.6.x do not need any config or deployment changes.

Commits

The five commits are intentionally kept separate so bisect can isolate the source of any regression:

  1. fix(bench): expose notify_idle_returned for coordinator_benchmarks
  2. chore(deps): bump Rust toolchain + tokio
  3. chore: migrate to edition 2024
  4. chore(clippy): fix lints surfaced by 1.95 + 2024
  5. release: 3.7.0 (version + changelog)

Local verification

  • cargo fmt — clean
  • cargo clippy --all-targets -- --deny warnings — clean
  • cargo build --release — OK (~1m13s)
  • cargo test --test bdd — 35/36 pass; the single failure is go: go.mod requires go >= 1.24.0 (running go 1.23.7), which is pre-existing on master and unrelated to this PR
  • make test-bdd — blocked locally because the ghcr.io/ozontech/pg_doorman/test-runner docker image still ships rustc 1.87. Needs an image rebuild against rustc 1.95 so contributors can run the suite locally.
  • Ubicloud benchmarks — left to CI to compare vs. master on real hardware.

Notes

  • TLS patches in patches/ (rust-native-tls, tokio-native-tls, openssl-src) compiled cleanly against the new tokio with no changes; no resync was needed.
  • After merge: rebuild and publish the test-runner image so make test-bdd works for contributors.

dmitrivasilyev added 13 commits April 30, 2026 12:36
The bench measures the cost of notify_idle_returned, but the helper
was pub(crate) — and Cargo compiles benches as a separate consumer
crate, so the bench failed to build with E0624 ("private method")
even on the current master. Mark it #[doc(hidden)] pub: still
excluded from the documented public surface of the lib crate, but
reachable from the bench. The helper itself is unchanged.
Foundation for the 3.7.0 upgrade — get the toolchain and async
runtime to current stable so the next steps (edition 2024 migration,
clippy fixes) don't have to deal with year-old tooling.

Rust pinned to 1.95.0 (was 1.87.0); tokio to 1.52.1 (was 1.49.0
in the lockfile). The lockfile picks up matching minor bumps in
libc, mio, socket2, and tokio-macros that the new tokio requires.
Step 2 of the 3.7.0 upgrade. Bumps the project to Rust edition 2024
and adapts source to the new language defaults.

Two production paths required real code changes:
- std::env::remove_var becomes unsafe in 2024 (it can race with
  other threads reading the environment); both call sites in
  run_server are now wrapped in unsafe blocks.
- unsafe fn bodies are no longer implicit unsafe blocks; each
  unsafe fn in the daemon module now opens an explicit unsafe
  block over its body.

The remaining source edits are pattern-binding cleanups that
2024's match ergonomics make redundant (if let Some(ref x)
collapses to if let Some(x)).

The bulk of the diff is rustfmt under edition 2024 — formatter
defaults shifted (import grouping, trailing-comment alignment).

Note: cargo clippy --deny warnings does not pass yet on this
commit. Edition 2024 enables collapsible_if-style diagnostics on
the new if-let-chains, and rustc 1.95 surfaces several
manual_checked_ops, collapsible_match, and similar lints — all
addressed in the next commit (C3).
Step 3 of the 3.7.0 upgrade. cargo clippy --fix handled the bulk
auto-rewritably (collapsible_if into 2024 if-let-chains, plus
collapsible_match, derivable_impls, useless_conversion, and
unnecessary_unwrap across admin, app, auth, pool, client, server,
util, and several test helpers).

Eleven lints required manual rewrites:
- if-x-is-some-then-x-unwrap → if-let-Some patterns in scram and
  server_backend.
- result.is_err && unwrap_err → let-Err binding in entrypoint and
  migration (uses 2024 let-chains).
- sort_by(reverse-cmp) → sort_by_key(Reverse) in log_level, pool
  inner, and a bench helper.
- Manual `if x > 0 { y / x } else { default }` → checked_div(x)
  .unwrap_or(default) in pool inner, stats address, and stats pool.
  Same observable behavior; the default already matched the
  original else-branch in each place.

cargo fmt and cargo clippy --all-targets --deny warnings now
both exit clean on the branch.
Wraps up the 3.7.0 toolchain refresh: bumps the project version
and adds the changelog entry covering the four preceding commits.

No user-visible behavior changes since 3.6.4 — this release packages
the rust/tokio/edition upgrade landed in the preceding commits.
Operators upgrading from 3.6.x do not need any config or deployment
changes.
Why: the test-runner docker image was pinned to rustc 1.87.0,
which made make test-bdd (and the corresponding CI job) fail at
the MSRV check after Cargo.toml moved to rust-version 1.95.0 in
the preceding commits.

The image tag in bdd-tests.yml is derived from a hash of
tests/nix/flake.nix + flake.lock, so changing the pin triggers
a fresh image build on the next workflow run; subsequent BDD
jobs pick up the new rustc automatically.
The previous test-runner image bump pinned rust-bin.stable.\"1.95.0\"
in flake.nix, but the existing flake.lock snapshot of rust-overlay
(2025-12-27) didn't have that attribute yet, so nix build failed
in CI with: error: attribute '\"1.95.0\"' missing.

Refresh nixpkgs, flake-parts, and rust-overlay locks so the new
toolchain is available in the next CI image build.
The 3.7.0 toolchain bump pinned rust-toolchain.toml and Cargo.toml
to 1.95.0, but several other places still hard-coded 1.87.0 and
broke their CI jobs:

- copr-publish, launchpad-publish — RUST_VERSION env.
- build-packages — wget URL for rust-1.87.0 in the debian and rpm
  matrix, plus the rust:1.87.0-alpine container for the static
  musl build.
- pkg/rpm/pg-doorman.spec — %global rust_version.
- Dockerfile — base image rust:1.87.0-slim-bookworm.
- benches/setup-and-run-bench.sh — rustup --default-toolchain.
- documentation/{en,ru}/src/tutorials/contributing.md — the
  "Rust 1.87.0" line under the prerequisites list.

Also fixes the nix test-runner image against the 2026-04-28
nixpkgs snapshot, which removed two attributes used by the
flake:
- nodePackages was deprecated; npm now ships bundled with
  nodejs_22, so the standalone nodePackages.npm entry is dropped.
- go_1_24 was dropped; switch to go_1_25 (still satisfies
  tests/go/go.mod's go 1.24.0 minimum).
Now that MSRV is 1.95.0, replace a few verbose stdlib idioms with
their newer equivalents where it improves readability:

- Duration::from_secs(60) → Duration::from_mins(1) for the JWT
  TTL in server authentication (was from_secs(120) → from_mins(2))
  and the default server idle-check timeout — minutes are the
  natural unit for these knobs.
- Manual is_char_boundary backwards walk in patroni::client::
  truncate_str collapses to str::floor_char_boundary, dropping
  the explicit loop.
- Three patroni_proxy port-test backend mocks switched to
  from_mins(1) for the keep-open sleep, consistent with the
  config-default change.

Other Duration::from_secs sites kept as-is on purpose: their
surrounding comments compare values in seconds (e.g. "60s vs
loop_wait of 10s"), and converting to minutes would split the
comment from the code unit.
Edition 2024 requires extern blocks to be marked unsafe. The
TLS migration FFI block in client::migration was guarded by the
tls-migration feature, so the cargo check during the edition 2024
migration commit (without the feature) didn't surface it; CI's
TLS Migration BDD job rebuilds with the feature on and failed
with: error: extern blocks must be unsafe.

Mark the block as unsafe extern "C". The function bodies inside
were already wrapped in their own unsafe { ... } blocks at the
call sites, so no other change is needed.
Five production sites called unwrap on values that depend on
external input or earlier guards. Each becomes a typed error
or a let-Some pattern, so a malformed PG packet or a config
mismatch surfaces as a regular Error instead of a panic.

- ParameterStatus parsing in server::protocol_io and
  server::server_backend now propagates the parse error from
  read_string with ? — the function signature for
  handle_parameter_status grew a Result so the caller can act
  on it; a malformed packet from the backend cleanly tears
  down that connection instead of aborting the worker.
- Static-MD5 verification in auth::mod (passthrough_md5_verify)
  binds the stripped hash via let-Some on the MD5_PASSWORD_PREFIX
  prefix — the previous unwrap relied on the caller having
  already done starts_with, which made the call site fragile to
  refactors.
- The auth_query MD5 path is rewritten as let-Some chains so the
  prefix strip can never panic, even if the cache returns an
  unexpected entry.
- server::authentication's clear-password branch collapses the
  is_none guard plus two unwrap+clone pairs into a single
  let-Some pattern, and the JWT key strip is also let-Some'd —
  no behavior change beyond the panic being a typed error.
Five message builders allocated a String only to copy its bytes
into a BytesMut/Vec and discard it. Each now writes directly to
the destination buffer:

- md5_hash_second_pass: write! into a presized Vec<u8> instead of
  format!(...).chars().map(as u8).collect — also drops the UTF-8
  decode/re-encode round-trip on what is already ASCII-only hex.
- error_message and wrong_password: put_slice the literal/borrowed
  bytes plus a put_u8(0) for the NUL, instead of format!("{x}\\0")
  → as_bytes.
- row_description: same pattern for the per-column name field.

These run on every error reply, every md5 auth handshake, and
once per row in administrative SELECTs. Behavior is byte-for-byte
identical (verified by visual diff of the bytes pushed).
Public message helpers row_description and data_row_nullable used
to take &Vec<T>; the slice form &[T] is the idiomatic Rust API for
read-only access and lets callers pass array literals directly,
without an intermediate Vec allocation.

All call sites in the admin module simplified accordingly:
&vec![("name", DataType::Text)] → &[("name", DataType::Text)],
which clippy::useless_vec now also enforces.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant