Skip to content

Fix flaky Windows CI tests and a LogThrottleRetryer event-drop bug - #2229

Open
sky333999 wants to merge 3 commits into
mainfrom
fix/windows-ci-flaky-tests
Open

Fix flaky Windows CI tests and a LogThrottleRetryer event-drop bug#2229
sky333999 wants to merge 3 commits into
mainfrom
fix/windows-ci-flaky-tests

Conversation

@sky333999

@sky333999 sky333999 commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Windows CI (make test on windows-latest) had an ambient ~7–20% flake rate. Root cause for nearly all of it: Windows' ~15.6 ms timer-tick resolution, under the CPU contention of ~50 package test binaries running in parallel, makes time.Sleep + one-shot-assertion patterns race their wall-clock deadlines. This PR fixes the flaky tests and one real (minor) production bug surfaced during the investigation.

  1. LogThrottleRetryer production fix (+ tail.go debug logging)
  2. The flaky-test fixes (9 test files)
  3. Adversarial-review follow-up to the production fix

Source change

internal/retryer/logthrottle.go — LogThrottleRetryer silently dropped throttle-detected log events.

throttleChan had a buffer of 1 with a non-blocking send, so when the watcher goroutine was preempted, consecutive throttle events were dropped (~6/200 lost under contention); Stop() also returned before the watcher drained, so the final aggregated count could be under-reported.

Impact is diagnostic only — the "AWS API call has been throttled N times" summary could undercount during a throttling storm. No retry/availability impact (the SDK still retries). Note the one-slot buffer was leftover from an earlier blocking implementation; the drop behavior was introduced incidentally by the later non-blocking deadlock fix (#2190). This change restores the original "log every event" intent without reintroducing the deadlock.

Fix:

  • throttleChan buffer 1 → 128 (holds a burst with headroom, bounded memory; the non-blocking send still guarantees the AWS SDK path never blocks).
  • Drain throttleChan on shutdown before the watcher returns.
  • Make Stop() synchronous (waits for the watcher to drain and exit), guarded by sync.Once so a double call can't panic on close(done).

plugins/inputs/logfile/tail/tail.go — added Debugf logging at OpenFileCount transitions to aid tailer-lifecycle triage. Debug-level only; no behavior change.

Test fixes

Test(s) Fix
TimedDeleterWithIDCheck_DeleteWithDelay_{NoUpdate,WithUpdate,InvalidType} Eventually/Never instead of a 10 ms delay + 20 ms sleep
TestConvertOtelMetrics_{Dimensions,NoDimensions,Histogram} timestamp-recency budget 1 s → 1 min (real drift is hours, not seconds)
TestTailerSrc drop racy OpenFileCount==before+1 check (process-global atomic mutated by sibling tailers)
TestMiddleware wait on channels per middleware phase instead of a fixed 4 s sleep
TestBackoffRetries cache elapsed once; widen upper-bound leniency 200 ms → 500 ms
TestPublish poll len(svc.Calls) instead of a fixed-interval sleep + hard assert
TestLogsFileRemove widen post-deletion grace 1 s → 10 s
TestAdmitAndRollup feed 10 distinct keys so "exactly 2 admitted" is deterministic (random keys could trigger legitimate top-K rotation)
TestExistingAttributesNotOverwritten poll for async EC2 tag/metadata/volume fetches instead of a 1 s sleep
TestNewFileManagerSink drain sleep 1 ms → 200 ms (1 ms is below the Windows tick)
TestReadGaps poll for the async state-file flush instead of a fixed 200 ms read
TestLogThrottleRetryerLogging + sibling drop now-vestigial time.Sleep() after Stop() (Stop is synchronous)

Validation

Each fix was validated with an A/B repro harness (30-iteration make test loops on windows-latest, cache-busted, baseline vs. fixed legs). Final run: fixed leg 30/30 clean across all 16 targeted tests, 0 unrelated failures; the baseline leg kept reproducing the flakes. All fixes also pass local stress runs (Linux + GOOS=windows vet/build; 1000× for the deterministic ones; 20× for the retryer package after the review changes).

Under load the watcher goroutine can be preempted while ShouldRetry pushes
throttle events onto throttleChan. With the original buffer of 1 and a
non-blocking send, consecutive events were silently dropped (observed ~6/200
lost under CI contention), and Stop() returned before the watcher drained,
so callers could miss the final events.

- Enlarge throttleChan buffer 1 -> 1024 (non-blocking send still guarantees the
  AWS SDK error path never blocks).
- Drain throttleChan on shutdown before the watcher returns (Go's randomized
  select could otherwise strand queued events on the <-done path).
- Make Stop() synchronous via a 'stopped' channel so callers observe all events.

Also add Debugf logging at tail.OpenFileCount transitions (TailFile/CloseFile/
Reopen) to aid future tailer-lifecycle triage; debug-level only, no behavior
change.
Windows' ~15.6ms timer tick under 'make test' CPU contention makes fixed
time.Sleep + one-shot assertion patterns race. Replace them with
assert.Eventually/require.Eventually polling, widen over-tight wall-clock
budgets, use channel synchronization, and make one assertion deterministic:

- TimedDeleterWithIDCheck_DeleteWithDelay_{NoUpdate,WithUpdate,InvalidType}:
  Eventually/Never instead of racing a 10ms delay + 20ms sleep.
- TestConvertOtelMetrics_{Dimensions,NoDimensions,Histogram} (checkDatum):
  timestamp-recency budget 1s -> 1min (real drift is hours, not seconds).
- TestTailerSrc: drop the racy OpenFileCount==before+1 check (process-global
  atomic mutated by sibling tailers); leak detection via the existing Eventually.
- TestMiddleware: wait on channels for each middleware phase instead of a fixed 4s sleep.
- TestBackoffRetries: cache elapsed once and widen upper-bound leniency 200ms -> 500ms.
- TestPublish: poll len(svc.Calls) instead of sleeping a fixed interval + hard assert.
- TestLogsFileRemove: widen post-deletion grace 1s -> 10s.
- TestAdmitAndRollup: feed 10 distinct keys so exactly-2-admitted is deterministic
  (random keys could trigger legitimate top-K rotation and admit a 3rd).
- TestExistingAttributesNotOverwritten: poll for the async EC2 tag/metadata/volume
  fetches instead of a fixed 1s sleep.
- TestNewFileManagerSink: drain sleep 1ms -> 200ms (1ms is below the Windows tick).
- TestReadGaps: poll for the async state-file flush instead of a fixed 200ms read.
Follow-up to the buffer/drain/sync-Stop fix, from an adversarial review:
- Cap throttleChan buffer at 128 (was 1024): holds a full burst with headroom
  while bounding memory; 1024 was an unjustified magic number.
- Guard Stop() with sync.Once so a double call can't panic on close(done).
  Current callers Stop() once, but the type had no protection for future callers.
- Drop the now-vestigial time.Sleep() calls after Stop() in the tests; Stop()
  is synchronous (drains + waits for the watcher), so the sleeps are dead weight.
@sky333999
sky333999 requested a review from a team as a code owner August 3, 2026 19:27
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Binary Size Report

linux/amd64

Binary PR vs main (4753aac) vs v1.300070.0
amazon-cloudwatch-agent 193.2 MB +0 B ${\color{green}▼}$ 38.1 MB (-16.5%)
amazon-cloudwatch-agent-config-wizard 2.0 MB +0 B ${\color{red}▲}$ +86.0 KB (+4.5%)
config-downloader 2.0 MB +0 B ${\color{red}▲}$ +86.0 KB (+4.5%)
config-translator 2.0 MB +0 B ${\color{red}▲}$ +86.0 KB (+4.5%)
start-amazon-cloudwatch-agent 2.5 MB +0 B ${\color{red}▲}$ +81.9 KB (+3.4%)
workload-discovery 3.0 MB +0 B ${\color{red}▲}$ +94.2 KB (+3.2%)
Total 204.8 MB +0 B ${\color{green}▼}$ 37.7 MB (-15.5%)
linux/amd64 amazon-cloudwatch-agent (last 9 main commits + this PR)

 193 ┤                                        
     ┤        ███ ███ ███ ███ ███ ███ ███ ███ 
     ┤        ███ ███ ███ ███ ███ ███ ███ ███ 
     ┤        ███ ███ ███ ███ ███ ███ ███ ███ 
     ┤    ▄▄▄ ███ ███ ███ ███ ███ ███ ███ ███ 
     ┤    ███ ███ ███ ███ ███ ███ ███ ███ ███ 
     ┤    ███ ███ ███ ███ ███ ███ ███ ███ ███ 
     ┤    ███ ███ ███ ███ ███ ███ ███ ███ ███ 
 193 ┤▄▄▄ ███ ███ ███ ███ ███ ███ ███ ███ ███ 
 MB  └────────────────────────────────────────
      7b0a665                             PR
linux/arm64
Binary PR vs main (4753aac) vs v1.300070.0
amazon-cloudwatch-agent 177.0 MB ${\color{red}▲}$ +65.5 KB (+0.0%) ${\color{green}▼}$ 34.5 MB (-16.3%)
amazon-cloudwatch-agent-config-wizard 2.0 MB +0 B ${\color{green}▼}$ 22 B (-0.0%)
config-downloader 2.0 MB +0 B ${\color{green}▼}$ 22 B (-0.0%)
config-translator 2.0 MB +0 B ${\color{green}▼}$ 22 B (-0.0%)
start-amazon-cloudwatch-agent 2.4 MB +0 B ${\color{red}▲}$ +65.5 KB (+2.8%)
workload-discovery 2.9 MB +0 B ${\color{red}▲}$ +65.5 KB (+2.3%)
Total 188.3 MB ${\color{red}▲}$ +65.5 KB (+0.0%) ${\color{green}▼}$ 34.3 MB (-15.4%)
windows/amd64
Binary PR vs main (4753aac) vs v1.300070.0
amazon-cloudwatch-agent.exe 196.0 MB ${\color{red}▲}$ +9.2 KB (+0.0%) ${\color{green}▼}$ 38.2 MB (-16.3%)
amazon-cloudwatch-agent-config-wizard.exe 2.1 MB +0 B ${\color{red}▲}$ +130.6 KB (+6.5%)
config-downloader.exe 2.1 MB +0 B ${\color{red}▲}$ +130.0 KB (+6.5%)
config-translator.exe 2.1 MB +0 B ${\color{red}▲}$ +130.6 KB (+6.5%)
start-amazon-cloudwatch-agent.exe 2.3 MB +0 B ${\color{red}▲}$ +112.1 KB (+5.2%)
workload-discovery.exe 2.9 MB +0 B ${\color{red}▲}$ +97.3 KB (+3.5%)
Total 207.7 MB ${\color{red}▲}$ +9.2 KB (+0.0%) ${\color{green}▼}$ 37.6 MB (-15.3%)
Investigating size changes

Use go-size-analyzer to compare binaries:

GOEXPERIMENT=jsonv2 go install github.com/Zxilly/go-size-analyzer/cmd/gsa@latest
gsa diff --old <baseline-binary> --new <new-binary>

@github-actions

Copy link
Copy Markdown
Contributor

This PR was marked stale due to lack of activity.

@github-actions github-actions Bot added the Stale label Aug 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant