Skip to content

Add async single-flight proxy resolution #41

Add async single-flight proxy resolution

Add async single-flight proxy resolution #41

Workflow file for this run

name: CI
on:
push:
branches: [main]
pull_request:
env:
CARGO_TERM_COLOR: always
jobs:
test:
name: ${{ matrix.name }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
# Each target builds/tests EVERY PAC backend it supports in one
# build (the test suite parametrizes over all compiled-in backends):
# native QuickJS + Wasmtime + wasm2c on the 64-bit targets, and
# native + wasm2c on armv7 (Wasmtime/Cranelift has no 32-bit ARM
# backend — wasm2c is what makes the sandbox reachable there). The
# shipped proxytester artifact is a separate single-backend build:
# Wasmtime everywhere, wasm2c on armv7 (`artifact_features`).
# Single-backend combinations (WinHTTP-only, native-only, ...) are
# additionally covered by the `pac-backend-variants` job below.
#
# The native engine needs a C toolchain for the QuickJS sources
# (MSVC on Windows, `cross` images on ARM Linux); wasm2c needs the
# pinned `wasm2c` binary (installed below; the cross containers get
# it via Cross.toml pre-build hooks).
# Windows (WinHTTP still handles normal resolution).
- name: windows x64
os: windows-latest
target: x86_64-pc-windows-msvc
features: '--no-default-features --features "tokio pac-engine pac-engine-wasmtime pac-engine-wasm2c"'
artifact_features: '--no-default-features --features "tokio pac-engine-wasmtime"'
run_tests: true
- name: windows arm64
os: windows-11-arm
target: aarch64-pc-windows-msvc
features: '--no-default-features --features "tokio pac-engine pac-engine-wasmtime pac-engine-wasm2c"'
artifact_features: '--no-default-features --features "tokio pac-engine-wasmtime"'
run_tests: true
# macOS.
- name: macos arm64
os: macos-latest
target: aarch64-apple-darwin
features: '--no-default-features --features "tokio pac-engine pac-engine-wasmtime pac-engine-wasm2c"'
artifact_features: '--no-default-features --features "tokio pac-engine-wasmtime"'
run_tests: true
- name: macos x64
os: macos-15-intel
target: x86_64-apple-darwin
features: '--no-default-features --features "tokio pac-engine pac-engine-wasmtime pac-engine-wasm2c"'
artifact_features: '--no-default-features --features "tokio pac-engine-wasmtime"'
run_tests: true
# Linux x86_64.
- name: linux x86_64
os: ubuntu-latest
target: x86_64-unknown-linux-gnu
features: '--no-default-features --features "tokio pac-engine pac-engine-wasmtime pac-engine-wasm2c"'
artifact_features: '--no-default-features --features "tokio pac-engine-wasmtime"'
run_tests: true
# Linux aarch64 via cross (tests run under qemu).
- name: linux aarch64
os: ubuntu-latest
target: aarch64-unknown-linux-gnu
features: '--no-default-features --features "tokio pac-engine pac-engine-wasmtime pac-engine-wasm2c"'
artifact_features: '--no-default-features --features "tokio pac-engine-wasmtime"'
use_cross: true
run_tests: true
# Linux armv7: no Wasmtime (no Cranelift backend for 32-bit ARM);
# native + wasm2c, and the shipped artifact uses the wasm2c sandbox.
- name: linux armv7
os: ubuntu-latest
target: armv7-unknown-linux-gnueabihf
features: '--no-default-features --features "tokio pac-engine pac-engine-wasm2c"'
artifact_features: '--no-default-features --features "tokio pac-engine-wasm2c"'
use_cross: true
run_tests: true
steps:
- uses: actions/checkout@v4
- name: Install Rust
run: |
rustup toolchain install stable --profile minimal --no-self-update
rustup default stable
rustup target add ${{ matrix.target }}
shell: bash
- uses: Swatinem/rust-cache@v2
with:
key: ${{ matrix.target }}
# The pinned wasm2c for the host build. No macOS x64 release asset
# exists; Homebrew currently ships exactly 1.0.41 there (build.rs
# verifies the version either way and fails loudly on drift). On Windows
# the binary is passed by explicit native path (not PATH lookup) —
# windows-11-arm runs the x64 binary through Windows' x64 emulation.
- name: Install wasm2c (WABT 1.0.41)
if: ${{ !matrix.use_cross }}
run: |
set -e
case "$RUNNER_OS-$(uname -m)" in
Linux-x86_64) asset="linux-x64" ;;
macOS-arm64) asset="macos-arm64" ;;
macOS-x86_64) asset="" ;;
Windows-*) asset="windows-x64" ;;
*) echo "unexpected runner $RUNNER_OS-$(uname -m)"; exit 1 ;;
esac
if [ -z "$asset" ]; then
brew install wabt
else
mkdir -p "$HOME/wabt"
curl -sSL "https://github.com/WebAssembly/wabt/releases/download/1.0.41/wabt-1.0.41-$asset.tar.gz" \
| tar xz --strip-components=1 -C "$HOME/wabt"
bin="$HOME/wabt/bin/wasm2c"
if [ "$RUNNER_OS" = "Windows" ]; then bin="$(cygpath -w "$bin.exe")"; fi
echo "OS_PROXY_RESOLVER_WASM2C=$bin" >> "$GITHUB_ENV"
"$HOME/wabt/bin/wasm2c" --version
fi
shell: bash
# The WABT release binaries need a newer glibc than the cross container
# images ship, so cross builds cannot run wasm2c themselves. Its output
# is target-independent C: generate it here on the host and point the
# containerized build at it (Cross.toml passes the variable through; the
# path is manifest-relative so it maps into the container).
- name: Pre-generate wasm2c C for the container (host wasm2c)
if: matrix.use_cross
run: |
set -e
mkdir -p "$HOME/wabt" pac-wasm-guest/generated
curl -sSL "https://github.com/WebAssembly/wabt/releases/download/1.0.41/wabt-1.0.41-linux-x64.tar.gz" \
| tar xz --strip-components=1 -C "$HOME/wabt"
"$HOME/wabt/bin/wasm2c" --version
"$HOME/wabt/bin/wasm2c" pac-wasm-guest/pac_guest.wasm --module-name pac_guest \
-o pac-wasm-guest/generated/pac_guest.c
echo "OS_PROXY_RESOLVER_PAC_GUEST_C_DIR=pac-wasm-guest/generated" >> "$GITHUB_ENV"
shell: bash
- name: Install cross
if: matrix.use_cross
run: cargo install cross --locked
- name: Build
run: ${{ matrix.use_cross && 'cross' || 'cargo' }} build --target ${{ matrix.target }} ${{ matrix.features }} --examples
shell: bash
- name: Test
if: matrix.run_tests
run: ${{ matrix.use_cross && 'cross' || 'cargo' }} test --target ${{ matrix.target }} ${{ matrix.features }}
shell: bash
- name: Build proxytester (release)
run: ${{ matrix.use_cross && 'cross' || 'cargo' }} build --release --target ${{ matrix.target }} ${{ matrix.artifact_features }} --example proxytester
shell: bash
- name: Stage proxytester artifact
run: |
ext=""
case "${{ matrix.target }}" in
*windows*) ext=".exe" ;;
esac
stage="dist/proxytester-${{ matrix.target }}"
mkdir -p "$stage"
# The PAC engine (Wasmtime + AOT guest; the wasm2c sandbox on armv7)
# is statically linked into the binary, so proxytester is
# self-contained (WinHTTP handles normal resolution on Windows).
cp "target/${{ matrix.target }}/release/examples/proxytester${ext}" "$stage/"
shell: bash
- name: Upload proxytester artifact
uses: actions/upload-artifact@v4
with:
name: proxytester-${{ matrix.target }}
path: dist/proxytester-${{ matrix.target }}/
if-no-files-found: error
os-config-tests:
name: os config round-trip (${{ matrix.name }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- name: windows
os: windows-latest
- name: macos
os: macos-latest
- name: linux
os: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
run: |
rustup toolchain install stable --profile minimal --no-self-update
rustup default stable
shell: bash
- uses: Swatinem/rust-cache@v2
with:
key: os-config-${{ matrix.os }}
# Linux needs a session bus + dconf backend + the GNOME proxy schema so
# `gsettings` has somewhere to read/write; the runner image is headless.
- name: Install GNOME proxy schema (Linux)
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
dbus dconf-gsettings-backend gsettings-desktop-schemas glib-networking
shell: bash
# These tests read the real OS proxy config after configuring it, so they
# only run here (gated behind OS_PROXY_RESOLVER_OS_TESTS). On Linux the
# whole thing runs under a private D-Bus session.
# These tests only exercise OS-config reading, not PAC, but the crate
# still needs a backend to compile on non-Windows. Use the default
# (Wasmtime) backend, which needs no C toolchain.
- name: Run OS round-trip tests
env:
OS_PROXY_RESOLVER_OS_TESTS: "1"
run: |
if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then
dbus-run-session -- cargo test --features tokio os_roundtrip -- --nocapture
else
cargo test --features tokio os_roundtrip -- --nocapture
fi
shell: bash
pac-bench:
name: PAC benchmark (${{ matrix.name }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
# Every engine available per OS is timed and cross-checked on the
# same PAC script and URLs:
# windows : WinHTTP + native QuickJS + Wasmtime + wasm2c
# macos/linux : native QuickJS + Wasmtime + wasm2c
# linux armv7 : native QuickJS + wasm2c (no Cranelift for 32-bit
# ARM), built with `cross` and run under qemu — the
# absolute numbers are qemu-skewed, the point is
# that the sandbox runs and the cross-check holds
# on a 32-bit target.
- name: windows
os: windows-latest
features: pac-engine pac-engine-wasmtime pac-engine-wasm2c
iterations: 3000
- name: macos
os: macos-latest
features: pac-engine pac-engine-wasmtime pac-engine-wasm2c
iterations: 3000
- name: linux
os: ubuntu-latest
features: pac-engine pac-engine-wasmtime pac-engine-wasm2c
iterations: 3000
- name: linux armv7 (qemu)
os: ubuntu-latest
target: armv7-unknown-linux-gnueabihf
use_cross: true
features: pac-engine pac-engine-wasm2c
iterations: 200
steps:
- uses: actions/checkout@v4
- name: Install Rust
run: |
rustup toolchain install stable --profile minimal --no-self-update
rustup default stable
if [ -n "${{ matrix.target }}" ]; then rustup target add ${{ matrix.target }}; fi
shell: bash
- uses: Swatinem/rust-cache@v2
with:
key: pac-bench-${{ matrix.name }}
# See the `test` job for the asset selection and cross rationale.
- name: Install wasm2c (WABT 1.0.41)
if: ${{ !matrix.use_cross }}
run: |
set -e
case "$RUNNER_OS-$(uname -m)" in
Linux-x86_64) asset="linux-x64" ;;
macOS-arm64) asset="macos-arm64" ;;
macOS-x86_64) asset="" ;;
Windows-*) asset="windows-x64" ;;
*) echo "unexpected runner $RUNNER_OS-$(uname -m)"; exit 1 ;;
esac
if [ -z "$asset" ]; then
brew install wabt
else
mkdir -p "$HOME/wabt"
curl -sSL "https://github.com/WebAssembly/wabt/releases/download/1.0.41/wabt-1.0.41-$asset.tar.gz" \
| tar xz --strip-components=1 -C "$HOME/wabt"
bin="$HOME/wabt/bin/wasm2c"
if [ "$RUNNER_OS" = "Windows" ]; then bin="$(cygpath -w "$bin.exe")"; fi
echo "OS_PROXY_RESOLVER_WASM2C=$bin" >> "$GITHUB_ENV"
"$HOME/wabt/bin/wasm2c" --version
fi
shell: bash
- name: Pre-generate wasm2c C for the container (host wasm2c)
if: matrix.use_cross
run: |
set -e
mkdir -p "$HOME/wabt" pac-wasm-guest/generated
curl -sSL "https://github.com/WebAssembly/wabt/releases/download/1.0.41/wabt-1.0.41-linux-x64.tar.gz" \
| tar xz --strip-components=1 -C "$HOME/wabt"
"$HOME/wabt/bin/wasm2c" --version
"$HOME/wabt/bin/wasm2c" pac-wasm-guest/pac_guest.wasm --module-name pac_guest \
-o pac-wasm-guest/generated/pac_guest.c
echo "OS_PROXY_RESOLVER_PAC_GUEST_C_DIR=pac-wasm-guest/generated" >> "$GITHUB_ENV"
shell: bash
- name: Install cross
if: matrix.use_cross
run: cargo install cross --locked
# Three release builds of pac_bench with growing feature sets, recording
# the binary size of each so the per-backend size delta is explicit.
# (`--no-default-features` because the default set enables Wasmtime.)
- name: Measure binary-size deltas
run: |
set -e
CARGO="${{ matrix.use_cross && 'cross' || 'cargo' }}"
TARGET_FLAG="${{ matrix.target && format('--target {0}', matrix.target) || '' }}"
BIN="target/${{ matrix.target && format('{0}/', matrix.target) || '' }}release/examples/pac_bench"
[ "$RUNNER_OS" = "Windows" ] && BIN="$BIN.exe"
size_of() { wc -c < "$BIN" | tr -d ' '; }
$CARGO build --release --example pac_bench $TARGET_FLAG --no-default-features --features "tokio pac-engine"
base=$(size_of)
echo "pac_bench (pac-engine only) : $base bytes"
if [[ "${{ matrix.features }}" == *pac-engine-wasmtime* ]]; then
$CARGO build --release --example pac_bench $TARGET_FLAG --no-default-features --features "tokio pac-engine pac-engine-wasmtime"
wt=$(size_of)
echo "pac_bench (+ pac-engine-wasmtime) : $wt bytes (delta $((wt - base)))"
fi
$CARGO build --release --example pac_bench $TARGET_FLAG --no-default-features --features "tokio ${{ matrix.features }}"
full=$(size_of)
echo "pac_bench (all backends for this OS) : $full bytes"
echo "wasm2c backend binary-size delta : $((full - ${wt:-$base})) bytes"
shell: bash
# Correctness first: the full test suite against every embedded engine
# (unit tests + PAC corpus + hostile-PAC timeout, each parametrized over
# all compiled-in backends).
- name: Test all embedded PAC backends
run: ${{ matrix.use_cross && 'cross' || 'cargo' }} test --release ${{ matrix.target && format('--target {0}', matrix.target) || '' }} --no-default-features --features "tokio ${{ matrix.features }}"
shell: bash
# pac_bench exits non-zero if the embedded backends — which run
# byte-identical engine sources — disagree on any URL.
- name: Run PAC benchmark
run: ${{ matrix.use_cross && 'cross' || 'cargo' }} run --release ${{ matrix.target && format('--target {0}', matrix.target) || '' }} --example pac_bench --no-default-features --features "tokio ${{ matrix.features }}" -- --iterations ${{ matrix.iterations }}
shell: bash
pac-backend-variants:
name: PAC backend variant (${{ matrix.name }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
# WinHTTP-only pure-Rust Windows build: no embedded engine at all.
# Valid on Windows because WinHTTP handles PAC.
- name: windows WinHTTP-only (pure Rust)
os: windows-latest
features: --no-default-features --features tokio
# Native-only build (no wasm sandbox) on a 64-bit host.
- name: linux native-only
os: ubuntu-latest
features: --no-default-features --features "tokio pac-engine"
# Wasmtime-only (the default feature set): no C toolchain use at all.
- name: linux wasmtime-only
os: ubuntu-latest
features: --features tokio
# wasm2c-only: the sandbox without Wasmtime, as an armv7-style build
# would use, but tested on a fast 64-bit host.
- name: linux wasm2c-only
os: ubuntu-latest
features: --no-default-features --features "tokio pac-engine-wasm2c"
needs_wasm2c: true
steps:
- uses: actions/checkout@v4
- name: Install Rust
run: |
rustup toolchain install stable --profile minimal --no-self-update
rustup default stable
shell: bash
- uses: Swatinem/rust-cache@v2
with:
key: variant-${{ matrix.name }}
- name: Install wasm2c (WABT 1.0.41)
if: matrix.needs_wasm2c
run: |
mkdir -p "$HOME/wabt"
curl -sSL "https://github.com/WebAssembly/wabt/releases/download/1.0.41/wabt-1.0.41-linux-x64.tar.gz" \
| tar xz --strip-components=1 -C "$HOME/wabt"
echo "$HOME/wabt/bin" >> "$GITHUB_PATH"
shell: bash
- name: Build and test
run: cargo test ${{ matrix.features }}
shell: bash
no-backend-is-a-compile-error:
name: no PAC backend is a compile error (non-Windows)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
run: |
rustup toolchain install stable --profile minimal --no-self-update
rustup default stable
shell: bash
- uses: Swatinem/rust-cache@v2
with:
key: no-backend
# Off Windows a backend is required; building with neither must fail.
- name: Assert `--no-default-features` fails to compile
run: |
if cargo build --no-default-features 2>/dev/null; then
echo "::error::expected a compile error with no PAC backend selected, but the build succeeded"
exit 1
fi
echo "OK: building with no PAC backend fails as expected"
shell: bash
electron-pac-bench:
name: Electron (Chromium) PAC baseline (${{ matrix.name }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- name: windows
os: windows-latest
- name: macos
os: macos-latest
- name: linux
os: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
# No lockfile is committed (Electron pulls a large platform binary), so
# install rather than `npm ci`.
- name: Install Electron
run: npm install --no-audit --no-fund
working-directory: bench/electron
shell: bash
# Baseline for the Rust pac_bench numbers above, on every OS pac_bench
# runs on: Chromium's own V8 PAC resolver, which is what Electron uses
# by default (the OS resolver is only used with
# --use-system-proxy-resolver). Same built-in PAC and URLs.
# resolveProxy is an async IPC to the network service and is throughput-
# serialized: --concurrency 32 shows it barely lifts throughput (~1.2x),
# which is the evidence that the calls/s ceiling is async-IPC cost (plus
# timer granularity, ~15.6ms on Windows), not PAC evaluation. The Linux
# runner is headless, so Electron needs a virtual display (xvfb) and its
# sandbox disabled (no setuid helper on the runner image).
- name: Run Electron PAC baseline
run: |
if [ "$RUNNER_OS" = "Linux" ]; then
export ELECTRON_DISABLE_SANDBOX=1
xvfb-run --auto-servernum npm run bench -- --iterations 3000 --concurrency 32
else
npm run bench -- --iterations 3000 --concurrency 32
fi
working-directory: bench/electron
shell: bash
lint:
name: rustfmt + clippy + docs
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
run: |
rustup toolchain install stable --profile minimal --component rustfmt --component clippy --no-self-update
rustup default stable
- uses: Swatinem/rust-cache@v2
# `--all-features` includes pac-engine-wasm2c, whose build script runs
# the pinned wasm2c.
- name: Install wasm2c (WABT 1.0.41)
run: |
mkdir -p "$HOME/wabt"
curl -sSL "https://github.com/WebAssembly/wabt/releases/download/1.0.41/wabt-1.0.41-macos-arm64.tar.gz" \
| tar xz --strip-components=1 -C "$HOME/wabt"
echo "$HOME/wabt/bin" >> "$GITHUB_PATH"
- run: cargo fmt --check
- run: cargo clippy --all-features --all-targets -- -D warnings
- run: cargo doc --no-deps --all-features
env:
RUSTDOCFLAGS: -D warnings
licenses:
name: licenses (deny + notices)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
run: |
rustup toolchain install stable --profile minimal --no-self-update
rustup default stable
- uses: Swatinem/rust-cache@v2
- name: Install cargo-deny and cargo-about
uses: taiki-e/install-action@v2
with:
tool: cargo-deny,cargo-about
# Fail if any dependency carries a license outside deny.toml's allow-list.
- name: Check dependency licenses
run: cargo deny check licenses bans sources
# Fail if ThirdPartyNotices.txt is stale relative to the current graph.
# The comparison ignores blank-line and whitespace differences (-B -w):
# some crates ship multiple copies of the same license text differing only
# in whitespace (e.g. miniz_oxide's LICENSE vs LICENSE-MIT.md), and
# cargo-about picks whichever the filesystem lists first, which varies by
# OS. We only care that the license *content* matches, not its formatting.
- name: Verify ThirdPartyNotices.txt is up to date
run: |
cargo about generate --all-features about.hbs > ThirdPartyNotices.generated.txt
if ! diff -u -B -w ThirdPartyNotices.txt ThirdPartyNotices.generated.txt; then
echo "::error::ThirdPartyNotices.txt is out of date. Run: cargo about generate --all-features about.hbs > ThirdPartyNotices.txt"
exit 1
fi