Skip to content

fix(dylint): give each boundary-lint process a unique observation identity - #1382

Merged
zackees merged 1 commit into
mainfrom
fix/dylint-observation-identity
Aug 23, 2026
Merged

fix(dylint): give each boundary-lint process a unique observation identity#1382
zackees merged 1 commit into
mainfrom
fix/dylint-observation-identity

Conversation

@zackees

@zackees zackees commented Aug 23, 2026

Copy link
Copy Markdown
Member

Symptom

Dylint (windows) failed on #1379 — a change confined to fbuild-cli:

platform-boundary: actual Dylint observations disagree for pid=2636
  source=crates/fbuild-toolchain/src/toolchain/esp_qemu.rs:
  expected={('attr_cfg', 'windows'): 6} actual={('attr_cfg', 'windows'): 12}
platform-boundary: actual Dylint observations disagree for pid=2636
  source=crates/fbuild-toolchain/src/toolchain/esp_qemu_runtime.rs:
  expected={('attr_cfg', 'target_os'): 3} actual={('attr_cfg', 'target_os'): 6}

Three things pointed away from the PR: nothing in it touched
fbuild-toolchain, both counts were exactly double, and the Linux leg of the
same commit was green.

Cause

dylint --all-targets runs one driver process per crate-target, so a crate's
lib and lib-test targets compile the same sources twice. Each process stamped
its observation lines with std::process::id() and nothing else. Windows
recycles PIDs readily enough that the second process can be handed the first
one's number once it has exited — and compare_dylint_observations keys
findings by (process, source, kind, normalized), so the two runs merged into
one bucket and every count for the sources they share read double.

The same collision is possible on Linux; the PID space is large enough that it
effectively never happens, which is why the gate has looked stable.

Verification

Replaying a real observation file with every identity collapsed to one PID
reproduces the CI failure exactly — the same two files, the same 6 → 12 and
3 → 6:

$ awk -F'\t' 'BEGIN{OFS="\t"} {$1="2636"; print}' observed.tsv > collide.tsv
$ uv run --no-project python ci/enforce_platform_boundary.py --dylint-observed collide.tsv
platform-boundary: actual Dylint observations disagree for pid=2636 source=…/esp_qemu.rs: expected={('attr_cfg', 'windows'): 6} actual={('attr_cfg', 'windows'): 12}
platform-boundary: actual Dylint observations disagree for pid=2636 source=…/esp_qemu_runtime.rs: expected={('attr_cfg', 'target_os'): 3} actual={('attr_cfg', 'target_os'): 6}

With the fix in place the same sweep emits distinct identities and the checker
passes:

$ cut -f1 target/platform-boundary-dylint-observed.tsv | sort -u
13176-18ce7fb775e64928
17564-18ce7fb7cba933c0
25424-18ce7fb88715b32c
…
$ uv run --no-project python ci/enforce_platform_boundary.py --dylint-observed … --print-totals
rows=14; dylint_rows=14

Fix

The identity is <pid>-<nanos>, taken once per process via a LazyLock.
Reusing a PID requires the original holder to have exited first, so two
processes cannot share a start nanosecond.

The Python checker needs no change — it already treats the first field as an
opaque key. Deduplicating findings would have been the wrong fix: the counter
legitimately counts repeated (kind, normalized) pairs within one file.

Crate version bumped so the cached .so is rebuilt with the new emitter.

…ntity

`Dylint (windows)` failed on an fbuild-cli-only change with:

    platform-boundary: actual Dylint observations disagree for pid=2636
      source=crates/fbuild-toolchain/src/toolchain/esp_qemu.rs:
      expected={('attr_cfg', 'windows'): 6} actual={('attr_cfg', 'windows'): 12}

Nothing in that PR touched fbuild-toolchain, both counts were exactly
double, and the Linux leg of the same commit was green.

`cargo dylint --all-targets` runs one driver process per crate-target, so a
crate's lib and lib-test targets compile the same sources twice. Each
process stamped its observations with `std::process::id()` alone — and
Windows hands out a recycled PID readily enough that the second process can
be given the first one's number after it exits. The checker keys findings by
that PID, so two runs merged into one bucket and every count for the shared
sources read double. The same collision on Linux is rare enough that the
gate looked stable.

Replayed against a real observation file, collapsing every identity to a
single PID reproduces the CI failure exactly — same two files, same 6 -> 12
and 3 -> 6:

    awk -F'\t' 'BEGIN{OFS="\t"} {$1="2636"; print}' observed.tsv > collide.tsv
    uv run --no-project python ci/enforce_platform_boundary.py \
      --dylint-observed collide.tsv

The identity is now `<pid>-<nanos>`, taken once per process. Reusing a PID
requires the original holder to have exited first, so two processes cannot
share a start nanosecond. The checker needs no change: it already treats the
first field as an opaque key.

Crate version bumped so the cached `.so` is rebuilt with the new emitter.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 23, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@zackees, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 30 minutes

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

Wait for the limit to reset, then comment @coderabbitai review or push new commits to the PR.

An organization admin can change what happens after included review limits in Billing.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ae10eaa3-c93e-49d2-944a-d0e3b19cea51

📥 Commits

Reviewing files that changed from the base of the PR and between b6f7eb9 and 8476651.

⛔ Files ignored due to path filters (1)
  • dylints/enforce_platform_boundary/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (2)
  • dylints/enforce_platform_boundary/Cargo.toml
  • dylints/enforce_platform_boundary/src/lib.rs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@zackees
zackees merged commit cf381ef into main Aug 23, 2026
18 checks passed
@zackees
zackees deleted the fix/dylint-observation-identity branch August 23, 2026 18:12
@fastled-project-sync fastled-project-sync Bot moved this to Triage in FastLED Tracker Aug 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Triage

Development

Successfully merging this pull request may close these issues.

1 participant