diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index ba13f09..2d72fc4 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -23,8 +23,28 @@ env: jobs: check: - runs-on: ubuntu-latest - timeout-minutes: 25 + runs-on: ${{ matrix.os }} + timeout-minutes: ${{ matrix.timeout }} + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + timeout: 25 + - os: macos-latest + timeout: 45 + - os: windows-latest + timeout: 75 + # Windows runs the checkers under busybox-w32 ash, same as test.yaml's Windows lane. + # That's the shell mise's `shell = "bash"` tasks run on; Git Bash's MSYS fork-emulation cygheap breaks + # down on long task chains. The path matches the version pin in `.mise/config.windows.toml`'s + # [tools."http:busybox"] entry. + defaults: + run: + shell: >- + ${{ startsWith(matrix.os, 'windows') && + 'C:\Users\runneradmin\AppData\Local\mise\installs\http-busybox\1.37.0\ash.exe -euo pipefail {0}' + || 'bash --noprofile --norc -euo pipefail {0}' }} steps: - name: Checkout uses: actions/checkout@v4 @@ -32,8 +52,13 @@ jobs: fetch-depth: 1 persist-credentials: false + - name: Free disk space on Windows + if: runner.os == 'Windows' + uses: ./.github/actions/free-disk-space-windows + - name: Install mise + tools uses: ./.github/actions/install-mise-tools + timeout-minutes: 25 with: github-token: ${{ github.token }} @@ -43,6 +68,15 @@ jobs: GITHUB_TOKEN: ${{ github.token }} CARGO_NET_RETRY: "5" + # MISE_JOBS=1 serializes mise's task scheduler so the parallel cargo checkers don't race on Windows. + # cargo-check / cargo-clippy-check / cargo-doc-check otherwise contend on cargo's + # `.cargo/registry/index` lock -- on Linux/macOS cargo waits gracefully, on Windows it fails fast + # (see test.yaml's matching step). mise rejects an empty MISE_JOBS, and GHA has no `if:` on + # individual env entries, so it's a separate conditional step. + - name: Serialize mise tasks on Windows + if: runner.os == 'Windows' + run: echo "MISE_JOBS=1" >> "$GITHUB_ENV" + - name: Run checkers run: | mise run check diff --git a/.mise/config.dart.toml b/.mise/config.dart.toml index 52e977e..9cd35e8 100644 --- a/.mise/config.dart.toml +++ b/.mise/config.dart.toml @@ -98,11 +98,7 @@ shell = "bash -euo pipefail -c" [tasks."gen:dart-ws"] depends = ["gen:ws-spec"] description = "Emit the Dart sealed-class WS client via dart-typegen (consumes generated/specs/ws.kdl from gen:ws-spec)" -# Skip the regen on Windows; the committed file stays canonical. -# That committed file is regenerated on Linux/macOS. dart-typegen exits 0 on Windows but writes an empty .dart -# file, which would clobber the committed source and fail gen-specs-check. run = """ -[ "${OS:-}" = "Windows_NT" ] && { echo "gen:dart-ws: skipped on Windows (dart-typegen empty output)"; exit 0; } mkdir -p generated/dart-ws/lib dart-typegen generate -i generated/specs/ws.kdl -o generated/dart-ws/lib/ws_messages.dart dart format generated/dart-ws/lib/ws_messages.dart @@ -113,10 +109,10 @@ shell = "bash -euo pipefail -c" depends = ["gen:ws-spec"] description = "Emit the Dart REST client via swagger_parser + build_runner (consumes generated/specs/rest.yaml)" dir = "generated/dart-rest" -# Regenerate the Dart REST client, handling a Windows skip plus two swagger_parser output fixups. -# Skipped on Windows like gen:dart-ws -- build_runner/dart-format output drifts there and gen-specs-check is -# Windows-skipped anyway, so the Linux/macOS-regenerated files stay canonical. `rm -rf lib` first so a renamed -# output (e.g. client_postfix change) can't leave orphaned files behind. +# Regenerate the Dart REST client, applying two swagger_parser output fixups. +# `rm -rf lib` first so a renamed output (e.g. client_postfix change) can't leave orphaned files behind. The +# goawk rewrites pipe through `tr -d '\r'` because goawk emits CRLF on Windows and dart format preserves +# whatever line endings it finds. # # Fixup 1 (storage.dart): swagger_parser renders the application/octet-stream storage endpoints unusably for the # browser -- a `dart:io` `File` PUT body and a `void` GET response. goawk rewrites it before build_runner so putFile @@ -127,7 +123,6 @@ dir = "generated/dart-rest" # root client's sub-client imports as `clients/_.dart` -- a stray trailing underscore that export.dart does # not have. goawk strips it. Idempotent: a no-op if upstream fixes the inconsistency. run = """ -[ "${OS:-}" = "Windows_NT" ] && { echo "gen:dart-rest: skipped on Windows"; exit 0; } dart pub get coreutils rm -rf lib dart run swagger_parser @@ -148,9 +143,10 @@ END { printf("gen:dart-rest storage fixup miss: io=%d get=%d body=%d\\n", io, get, body) > "/dev/stderr" exit 1 } -}' lib/clients/storage.dart > "$s/storage.dart" +}' lib/clients/storage.dart | coreutils tr -d '\\r' > "$s/storage.dart" coreutils mv "$s/storage.dart" lib/clients/storage.dart -goawk '/^import / && /clients/ { gsub(/_\\.dart/, ".dart") } { print }' lib/rest_client.dart > "$s/rest_client.dart" +goawk '/^import / && /clients/ { gsub(/_\\.dart/, ".dart") } { print }' lib/rest_client.dart | + coreutils tr -d '\\r' > "$s/rest_client.dart" coreutils mv "$s/rest_client.dart" lib/rest_client.dart dart run build_runner build dart format lib diff --git a/.mise/config.toml b/.mise/config.toml index b5b82f7..830ffc3 100644 --- a/.mise/config.toml +++ b/.mise/config.toml @@ -1040,15 +1040,7 @@ description = "Fail if any checked-in artifact under generated/ is stale" # All et-int-gen outputs land under generated/. # The runner's wit/deps/et-ws-messages is a symlink into that tree, so a single diff # against generated/ covers everything. -# -# Skipped on Windows: the regen pipeline produces non-deterministic drift there (nightly rustfmt formats -# long signatures and doc-comment indentation differently across nightlies; pipx-driven python codegen -# emits subtly different output). Linux/macOS runs are canonical. -run = """ -[ "${OS:-}" = "Windows_NT" ] && { echo "gen-specs-check: skipped on Windows (non-deterministic)"; exit 0; } -git diff --exit-code -- generated -""" -shell = "bash -euo pipefail -c" +run = "git diff --exit-code -- generated" # `cargo-watch` is unmaintained; `watchexec` is its successor. # watchexec is what `cargo-watch` always called under the hood. Restart on .rs/.toml changes so a diff --git a/CLAUDE.md b/CLAUDE.md index a461a6b..03b014d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -430,22 +430,6 @@ merged). If this signature recurs, stop rerunning and fix the root cause: raise runner-registration timeout in the pyo3-runner module tests, or warm the torch import before the registration clock starts. -### Known intermittent CI failure: wasi-runner vector OTLP store-and-forward timeout - -`et-ws-wasi-runner`'s `vector_otlp_relay::vector_relays_buffered_otlp_after_backend_comes_online` intermittently -fails on the Windows `override` job (test.yaml) with the literal failure line - - mock never received the relayed `relay-probe` span -- store-and-forward failed - -from `services/ws-wasi-runner/tests/vector_otlp_relay.rs`. The test pushes an OTLP span into a Vector source whose -sink points at a dead collector (Vector buffers it), brings the mock collector online, then polls ~30s -(`Fixed::from_millis(250).take(120)`) for the buffered span to be relayed. The captured Vector stderr shows no sink -errors -- the buffer just wasn't flushed inside the window, so Vector's post-reconnect sink retry/backoff on a cold -runner is the suspect. Observed once on commit `a6d7cd5552586d95dc67e09d5800c347f368a998` at -https://github.com/edge-toolkit/core/actions/runs/28690968262/job/85092063432 and passed on the next run with no -code change. If this signature recurs, stop rerunning and fix the root cause: widen the poll window, or configure -the Vector sink's retry/backoff so a freshly-online collector is hit promptly rather than after a long backoff. - ## Workarounds When you can't (or shouldn't) fix the root cause right now -- a libc race in an upstream dep, a flaky platform driver, diff --git a/config/vector-otlp-relay.yaml b/config/vector-otlp-relay.yaml index 1765ce6..f03ca68 100644 --- a/config/vector-otlp-relay.yaml +++ b/config/vector-otlp-relay.yaml @@ -35,7 +35,13 @@ sinks: # `otlp` is protobuf; Vector's json OTLP encoding is not a valid request body (vectordotdev/vector#23971). encoding: codec: otlp - # Cap the retry backoff so the buffered event is redelivered within seconds of the collector returning. + # Retry promptly but keep retrying long enough to survive an outage. + # retry_initial_backoff_secs makes the first retry quick, so a buffered event is redelivered within a + # second or two of the collector returning. retry_max_duration_secs is NOT a backoff-interval cap -- + # it is the total per-request retry window, after which Vector DROPS the event ("retries exhausted"). + # At 5s the buffered span was dropped before a cold CI runner could bind the collector, which timed out + # the ws-wasi-runner vector_otlp_relay test on Windows; 300s keeps store-and-forward durable across a + # real outage while staying finite. request: retry_initial_backoff_secs: 1 - retry_max_duration_secs: 5 + retry_max_duration_secs: 300