Scope watchpr's terminal 404 to the PR lookup
Only a 404 from GetPR means the PR is gone. A 404 from any other call can be a proxy or ingress blip, so it now warns and counts against the consecutive-failure cap instead of killing the watch on first sight.
This commit is contained in:
+21
-6
@@ -1,10 +1,21 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// errPRGone marks a 404 from the PR lookup itself. A 404 from any other endpoint
|
||||
// can be a proxy or ingress blip and is left to the ordinary failure cap.
|
||||
var errPRGone = errors.New("PR no longer visible")
|
||||
|
||||
// IsPRGone reports whether err is a 404 from the PR lookup, meaning the PR is no
|
||||
// longer visible rather than one endpoint being briefly unreachable.
|
||||
func IsPRGone(err error) bool {
|
||||
return errors.Is(err, errPRGone)
|
||||
}
|
||||
|
||||
// PRState is a point-in-time snapshot of the PR attributes watchpr tracks.
|
||||
type PRState struct {
|
||||
Ref PRRef `json:"ref"`
|
||||
@@ -23,6 +34,9 @@ type PRState struct {
|
||||
func FetchState(c *GiteaClient, ref PRRef, agentLogin string) (PRState, error) {
|
||||
pr, err := c.GetPR(ref.RepoPath(), ref.Number)
|
||||
if err != nil {
|
||||
if IsNotFound(err) {
|
||||
return PRState{}, fmt.Errorf("%w: %w", errPRGone, err)
|
||||
}
|
||||
return PRState{}, err
|
||||
}
|
||||
// A 404 here means the head commit is gone (branch deleted after a squash/
|
||||
@@ -82,8 +96,8 @@ func terminalState(st PRState) (bool, string) {
|
||||
}
|
||||
|
||||
// MaxPollFailures is how many consecutive failed polls of the same PR are
|
||||
// tolerated before Watch gives up. At watchpr's default 60s interval that rides
|
||||
// out a 20-minute outage.
|
||||
// tolerated before Watch gives up. The abort fires on the 20th failed tick, so
|
||||
// at watchpr's default 60s interval a watch rides out ~19 minutes of failure.
|
||||
const MaxPollFailures = 20
|
||||
|
||||
// Watch establishes a baseline for each ref, then polls on every tick until a
|
||||
@@ -91,9 +105,10 @@ const MaxPollFailures = 20
|
||||
// already terminal (merged/closed) at baseline is reported immediately rather
|
||||
// than polled forever. Transient poll errors are handed to onError and the loop
|
||||
// continues, but never blindly: a baseline fetch error, an authentication
|
||||
// failure surviving a token re-mint, a 404 on a tracked PR (the repo is gone,
|
||||
// renamed, or no longer visible), and MaxPollFailures consecutive failures of
|
||||
// one PR all abort, because a watcher that sees nothing must not look healthy.
|
||||
// failure surviving a token re-mint, a 404 from the PR lookup itself (the repo
|
||||
// is gone, renamed, or no longer visible), and MaxPollFailures consecutive
|
||||
// failures of one PR all abort, because a watcher that sees nothing must not
|
||||
// look healthy.
|
||||
// onBaseline, if set, fires once after all baselines are captured and before the
|
||||
// first tick.
|
||||
func Watch(f StateFetcher, refs []PRRef, agentLogin string, ticks <-chan time.Time, onBaseline func(), onError func(PRRef, error)) (WatchResult, error) {
|
||||
@@ -117,7 +132,7 @@ func Watch(f StateFetcher, refs []PRRef, agentLogin string, ticks <-chan time.Ti
|
||||
key := ref.String()
|
||||
cur, err := f.FetchState(ref, agentLogin)
|
||||
if err != nil {
|
||||
if IsAuthError(err) || IsNotFound(err) {
|
||||
if IsAuthError(err) || IsPRGone(err) {
|
||||
return WatchResult{}, fmt.Errorf("polling %s: %w", key, err)
|
||||
}
|
||||
fails[key]++
|
||||
|
||||
Reference in New Issue
Block a user