Wait out a forge not served for a moment when registering a build (novox/hq issue 486)
A registration asked the forge while node-tools re-served after a tool-runner rollout, heard "nothing serves gitea.gitea_api", and failed a good build's plan. The bus's not-served answer is now link.ErrNothingServes (and a timeout link.ErrNoAnswer), and the registration asks again for up to 90 s on those alone, never on a refusal; past the bound it fails saying how long it waited.
This commit is contained in:
+17
-4
@@ -16,6 +16,19 @@ type Answer struct {
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// ErrNoAnswer is a tool asked that did not answer within the asker's wait: nothing refused, and asking again
|
||||
// may be answered. With ErrNothingServes, what an asker may wait out (novox/hq issue 486).
|
||||
var ErrNoAnswer = errors.New("no answer within the wait")
|
||||
|
||||
// saidAs is an error said in its own words that is still the error it is a case of, for errors.Is.
|
||||
type saidAs struct {
|
||||
text string
|
||||
is error
|
||||
}
|
||||
|
||||
func (e saidAs) Error() string { return e.text }
|
||||
func (e saidAs) Unwrap() error { return e.is }
|
||||
|
||||
// Ask calls one of a module's tools over the broker and waits for its answer.
|
||||
//
|
||||
// **The control plane is the way in** (novox/hq 04-ISSUES/049, ADR 0095). A module's broker
|
||||
@@ -37,16 +50,16 @@ func Ask(ctx context.Context, bus Bus, module, tool string, args json.RawMessage
|
||||
reply, err := bus.AskTool(ctx, module, tool, args, timeout)
|
||||
if err != nil {
|
||||
if errors.Is(err, nats.ErrNoResponders) {
|
||||
return Answer{}, fmt.Errorf(
|
||||
return Answer{}, saidAs{fmt.Sprintf(
|
||||
"nothing serves %s: no runtime has bound %q on the bus. The module is not "+
|
||||
"assigned, its runtime is not up, or it serves no such tool — `status` says "+
|
||||
"whether the machine carrying it has applied", module, key)
|
||||
"whether the machine carrying it has applied", module, key), ErrNothingServes}
|
||||
}
|
||||
if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, nats.ErrTimeout) {
|
||||
return Answer{}, fmt.Errorf(
|
||||
return Answer{}, saidAs{fmt.Sprintf(
|
||||
"%s did not answer within %s. Its runtime serves %q when it is up and has bound "+
|
||||
"the bus — `status` says whether the machine carrying it has applied",
|
||||
module, timeout, key)
|
||||
module, timeout, key), ErrNoAnswer}
|
||||
}
|
||||
return Answer{}, fmt.Errorf("cannot ask %s: %w", key, err)
|
||||
}
|
||||
|
||||
@@ -206,8 +206,9 @@ func (b OverNATS) AskTool(ctx context.Context, module, tool string, args []byte,
|
||||
if err != nil {
|
||||
if errors.Is(err, nats.ErrNoResponders) {
|
||||
// Said at once rather than after the whole wait: nothing is subscribed to that
|
||||
// subject, which is a different fact from a tool being slow.
|
||||
return nil, fmt.Errorf("nothing serves %s.%s", module, tool)
|
||||
// subject, which is a different fact from a tool being slow. Said as ErrNothingServes, so an
|
||||
// asker can tell a tool not served now from one that refused (novox/hq issue 486).
|
||||
return nil, saidAs{fmt.Sprintf("nothing serves %s.%s", module, tool), ErrNothingServes}
|
||||
}
|
||||
return nil, fmt.Errorf("asking %s.%s: %w", module, tool, err)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package link
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -115,6 +116,15 @@ func TestAskingAToolNobodyServesFailsAtOnce(t *testing.T) {
|
||||
if !strings.Contains(err.Error(), "nothing serves") {
|
||||
t.Errorf("the refusal does not say nobody is there: %v", err)
|
||||
}
|
||||
// And it is ErrNothingServes, through Ask too, so an asker can wait out a tool runner re-serving and
|
||||
// never a refusal (novox/hq issue 486).
|
||||
if !errors.Is(err, ErrNothingServes) {
|
||||
t.Errorf("nothing serving the tool is not ErrNothingServes: %v", err)
|
||||
}
|
||||
if _, err := Ask(context.Background(), bus, "nobody", "home", nil, 30*time.Second); !errors.Is(err, ErrNothingServes) ||
|
||||
!strings.Contains(err.Error(), "nothing serves nobody.home") {
|
||||
t.Errorf("Ask does not say ErrNothingServes in its words: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// And a served tool answers, with no reply queue to declare and no correlation to check.
|
||||
|
||||
Reference in New Issue
Block a user