Files
pulse/internal
rcourtman ff984d19e2 Stop the Windows polling contract depending on host speed (#2804)
`TestGuestWindowsMeminfoPollingContract` failed intermittently on the
loaded `pulse-dev` box. Nothing in the polling logic was at fault: the
fixture owned three wall-clock windows a starved machine overruns, and
each failure cascades into the later assertions. This removes them. The
change is test-only (two files).

## What raced

1. **A one second HTTP budget.** The real `proxmox` clients were built
with `Timeout: time.Second` against a loopback server. The guest-agent
guard defers a poll whose lock check fails (`lock-unverified`) and
fences the guest for a minute after a command times out or its body is
cut short (`agent-timeout`, `agent-response-incomplete`, then
`agent-cooldown`). One late reply therefore broke the read counts and
History the later assertions expect, or deferred every poll after it.
The fixture now sets only hang guards, including the monitor's
guest-agent timeouts it used to leave at zero (they silently fell back
to 10s defaults and logged a warning per call).
2. **A one second History window.** `recordGuestMetrics` was passed
`time.Now().Add(-time.Second)` *after* the build, `UpdateVMs` and
`updateResourceStore`. A guest whose `LastSeen` precedes that cycle
start is skipped (`guestObservedInCycle`), so a slow tail silently
dropped the sample and History lost a point (poll 1 in three of the four
cases I saw, poll 3 in the other). The poll loop takes `cycleStart`
before building (`monitor_pve_guest_poll.go:50`); the fixture now does
the same.
3. **Two timers in the shared History fixture**
(`guest_history_observation_test.go`). `WaitForMaintenance(5 *
time.Second)` now waits on the maintenance barrier itself
(`WaitForMaintenance(0)`), and stored points are read back over the last
hour instead of the last minute (the range still selects the raw tier).
I checked the other users of the helper: none writes backdated points or
relies on older points being excluded, and the whole
`internal/monitoring` package passes.

One lock check in `client/cluster` is answered 1.2s late, so the one
second budget cannot come back unnoticed. That adds 1.2s to the test.

## Evidence

Measured on `pulse-dev` (8 cores, load average 50-100, memory and I/O
pressure high), each run a separate process, against the unmodified
test:

| run | variant | failures |
| --- | --- | --- |
| 40 runs, median load 80 | unmodified | 7 |
| 40 runs, same window | deadline + cycle-start fix (without the fixture
timers) | 0 |
| 25 interleaved runs | unmodified | 3 |
| 25 interleaved runs | first draft of this fix, deadline only | 3 |

The unmodified failures had the signatures from the report:
`prev-lock-unverified` with `fs=2` and `History = [30 0]` or `[30 40]`,
`normal OS observation absent`, `agent-timeout` followed by
`agent-cooldown`, and a History missing its first point. The first draft
passing only partly is what exposed windows 2 and 3.

Negative controls, deterministic at any load:

- Restoring only the one second `Timeout`: fails every time (40 of 40,
and again on the final code) with `poll 2 ... prev-lock-unverified ...
fs=2`, `Windows disk History = [30 0], want [30 40 0]`, then the
backup-lock and resume assertions.
- Restoring the `now-1s` window with a 1.2s tail before recording:
History is empty for every metric. The same tail with `cycleStart`
passes.
- A point written 90 seconds earlier is invisible to the old one minute
query window and returned by the new one (scratch test, not committed).

Checked on the final code (on `4471dcaaba`, before the comment-only
edits that followed): the whole `internal/monitoring` package passes. I
did **not** get a heavy-load window for the final build: the box's load
fell to about 1 shortly after, so the final code was verified by the
controls and the package run, not by a repeat of the 40-run comparison.

## What still can fail

Two limits remain that the fixture cannot set: the proxmox transport
allows ten seconds to dial and to receive response headers
(`pkg/tlsutil/fingerprint.go`, `dnscache.go`), and a cluster client
probes a new endpoint within three seconds (`cluster_client.go`). Both
are production policy. Emulating a host stall by suspending the test
process for 12 seconds, with the late reply still armed in all four
subtests, made the fixed test fail 7 of 30 times, all at that lock
check; a 3 second suspension passed 30 of 30 for both builds, since the
test spends most of its time sleeping. That is also why the late reply
is armed in one subtest, not four: the four share a client budget and
each armed one widens the window a stall can land in.

## Found, not changed here

The same `recordGuestMetrics(..., time.Now().Add(-time.Second))` window
is in eight call sites across five sibling tests:
`guest_history_observation_test.go` (4),
`guest_filesystem_completeness_test.go`,
`guest_filesystem_identity_test.go`,
`guest_agent_optional_read_integration_test.go`,
`guest_disk_expiry_test.go`.
`monitor_pve_guest_cpu_authority_test.go:29` has its own
`WaitForMaintenance(5 * time.Second)`. They share the latent race but
are outside this contract test.
2026-10-10 14:13:31 +01:00
..