Files

53 lines
1.9 KiB
Go

package inventory
import (
"errors"
"io"
"net"
"github.com/jackc/pgx/v5/pgconn"
)
// Unreachable says whether an error means the store could not be asked right now, rather than
// that it answered no.
//
// The difference decides whether something worth keeping is kept or lost. The store is recreated
// when the foundation is adopted (ADR 0078), and for those seconds every write fails; a caller
// that treats that like a refusal throws away what it was writing — a node's report of the apply
// that caused the restart was lost exactly so, and the mesh never heard from the node again
// (novox/hq issue 082).
//
// Unreachable is the connection failing and the server saying "not now". The connection failing
// is a network error, a connection that ended mid-conversation (a store killed rather than
// stopped gives a bare unexpected EOF), a timeout, or anything pgx marks safe to retry. The server
// saying "not now" is a connection exception (class 08) or it shutting down or starting up (57P01,
// 57P02, 57P03). Everything else is an answer, and asking again gets the same one: a wrong
// password, a database that does not exist, a statement cancelled, a constraint. Those are
// checked first, even inside a failed connection, because a connection that failed on a wrong
// password would otherwise read as a network fault and be asked again for ever.
func Unreachable(err error) bool {
if err == nil {
return false
}
var pg *pgconn.PgError
if errors.As(err, &pg) {
switch pg.Code {
case "57P01", "57P02", "57P03":
return true
}
return len(pg.Code) == 5 && pg.Code[:2] == "08"
}
if errors.Is(err, io.ErrUnexpectedEOF) || errors.Is(err, io.EOF) {
return true
}
if pgconn.Timeout(err) || pgconn.SafeToRetry(err) {
return true
}
var network net.Error
if errors.As(err, &network) {
return true
}
var connect *pgconn.ConnectError
return errors.As(err, &connect)
}