diff --git a/.github/workflows/blink_parity_mautic.yaml b/.github/workflows/blink_parity_mautic.yaml index ecb6d490c6..9442706a57 100644 --- a/.github/workflows/blink_parity_mautic.yaml +++ b/.github/workflows/blink_parity_mautic.yaml @@ -72,7 +72,10 @@ jobs: common: true, standaloneLine: true, cleanCode: true, - ); + ) + // single process: parallel workers overflow the NDJSON buffer on + // large mautic files and abort the PHP run (blink ignores this) + ->withoutParallel(); PHP cp /tmp/mautic/ecs.php /tmp/mautic/ecs-copy.php @@ -92,7 +95,7 @@ jobs: - name: Compare the two trees run: | # ratchet gate: fail once the differing share crosses this; lower it as parity improves - MAX_DIFF_PERCENT=1.7 + MAX_DIFF_PERCENT=1.6 total=0 differ=0 differing_files="" diff --git a/blink/.gitignore b/blink/.gitignore new file mode 100644 index 0000000000..17bdc726d6 --- /dev/null +++ b/blink/.gitignore @@ -0,0 +1,2 @@ +/blink +/check-sources diff --git a/blink/cmd/check-sources/main.go b/blink/cmd/check-sources/main.go index f4cb6a2f2c..2bdf4c1f6a 100644 --- a/blink/cmd/check-sources/main.go +++ b/blink/cmd/check-sources/main.go @@ -1,6 +1,7 @@ // Command check-sources verifies that every registered fixer links to a // reachable PHP-CS-Fixer source file on GitHub. It is run in CI so a renamed or -// removed upstream rule fails the build. +// removed upstream rule fails the build. GitHub rate-limits bursts of HEAD +// requests with 503/429, so each URL is retried with backoff before failing. package main import ( @@ -12,34 +13,58 @@ import ( "blink/internal/fixer/rules" ) -func main() { - client := &http.Client{Timeout: 20 * time.Second} - failed := false - - for _, f := range rules.All() { - url := f.SourceURL() +const maxAttempts = 4 +// checkURL returns the final status code (or an error) for url, retrying on a +// 5xx or 429 that GitHub returns when throttling a burst of requests. +func checkURL(client *http.Client, url string) (int, error) { + var lastStatus int + var lastErr error + for attempt := 1; attempt <= maxAttempts; attempt++ { req, err := http.NewRequest(http.MethodHead, url, nil) if err != nil { - fmt.Printf("FAIL %s: %v\n", f.Name(), err) - failed = true - continue + return 0, err } - + req.Header.Set("User-Agent", "blink-check-sources") resp, err := client.Do(req) if err != nil { - fmt.Printf("FAIL %s: %v\n", f.Name(), err) - failed = true - continue + lastErr = err + } else { + _ = resp.Body.Close() + lastStatus, lastErr = resp.StatusCode, nil + if resp.StatusCode == http.StatusOK { + return resp.StatusCode, nil + } + // a 4xx other than 429 is a genuine missing/renamed source, not throttling + if resp.StatusCode != http.StatusTooManyRequests && resp.StatusCode < 500 { + return resp.StatusCode, nil + } } - _ = resp.Body.Close() + if attempt < maxAttempts { + time.Sleep(time.Duration(attempt) * 2 * time.Second) + } + } + return lastStatus, lastErr +} - if resp.StatusCode != http.StatusOK { - fmt.Printf("FAIL %s: %s -> %d\n", f.Name(), url, resp.StatusCode) +func main() { + client := &http.Client{Timeout: 20 * time.Second} + failed := false + + for _, f := range rules.All() { + url := f.SourceURL() + status, err := checkURL(client, url) + switch { + case err != nil: + fmt.Printf("FAIL %s: %v\n", f.Name(), err) + failed = true + case status != http.StatusOK: + fmt.Printf("FAIL %s: %s -> %d\n", f.Name(), url, status) failed = true - continue + default: + fmt.Printf("OK %s\n", url) } - fmt.Printf("OK %s\n", url) + time.Sleep(150 * time.Millisecond) // throttle so GitHub does not rate-limit the burst } if failed {