Read a refused line's request before answering it, so a busy line is read as busy
The engine answered busy and closed before the client's request had landed; the client's write then broke and it never read the refusal waiting for it. Under the whole suite's load this failed the busy test (novox/hq issue 434).
This commit is contained in:
@@ -189,15 +189,20 @@ func TestOneAccountsLinesBeyondItsBoundAreAnsweredBusy(t *testing.T) {
|
||||
t.Cleanup(func() { close(release) })
|
||||
go func() { _, _ = cli.Ask(context.Background(), socket, []string{"push"}) }()
|
||||
<-started
|
||||
done := make(chan cli.Answer, 1)
|
||||
type asked struct {
|
||||
a cli.Answer
|
||||
err error
|
||||
}
|
||||
done := make(chan asked, 1)
|
||||
go func() {
|
||||
a, _ := cli.Ask(context.Background(), socket, []string{"status"})
|
||||
done <- a
|
||||
a, err := cli.Ask(context.Background(), socket, []string{"status"})
|
||||
done <- asked{a, err}
|
||||
}()
|
||||
select {
|
||||
case a := <-done:
|
||||
if !strings.Contains(a.Refused, "busy") {
|
||||
t.Fatalf("a second line from the same account reads %+v", a)
|
||||
case got := <-done:
|
||||
// An error is a busy line the client never read (novox/hq issue 434), not an answer.
|
||||
if got.err != nil || !strings.Contains(got.a.Refused, "busy") {
|
||||
t.Fatalf("a second line from the same account reads %+v (%v)", got.a, got.err)
|
||||
}
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("a second line from the same account waited instead of being answered busy")
|
||||
@@ -207,6 +212,50 @@ func TestOneAccountsLinesBeyondItsBoundAreAnsweredBusy(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// **A refusal waits for the request it answers** (novox/hq issue 434): the client writes its line first and reads
|
||||
// the answer after. An engine that answered busy and closed before that write landed broke the client's write, and
|
||||
// the refusal was never read — under the whole suite's load the busy line read as an error. Here the client
|
||||
// connects and holds its line back: no answer may come before the line, and the line, once written, is answered
|
||||
// busy. Ordered by the client's own write, so it holds however slow the machine is.
|
||||
func TestARefusalWaitsForTheRequestItAnswers(t *testing.T) {
|
||||
was, wasWithin := perAccount, refusedWithin
|
||||
perAccount, refusedWithin = 1, time.Minute
|
||||
t.Cleanup(func() { perAccount, refusedWithin = was, wasWithin })
|
||||
release := make(chan struct{})
|
||||
started := make(chan struct{}, 2)
|
||||
socket := serving(t, func(context.Context, Asked) cli.Answer {
|
||||
started <- struct{}{}
|
||||
<-release
|
||||
return cli.Answer{}
|
||||
})
|
||||
t.Cleanup(func() { close(release) })
|
||||
go func() { _, _ = cli.Ask(context.Background(), socket, []string{"push"}) }()
|
||||
<-started
|
||||
conn, err := net.Dial("unix", socket)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
// Nothing is written yet: an engine that answers now answers before the request. The wait is far inside
|
||||
// refusedWithin, so an engine that waits is still waiting when it ends.
|
||||
_ = conn.SetReadDeadline(time.Now().Add(200 * time.Millisecond))
|
||||
var b [1]byte
|
||||
if n, err := conn.Read(b[:]); n > 0 || !errors.Is(err, os.ErrDeadlineExceeded) {
|
||||
t.Fatalf("the engine answered before the request was written (%d bytes, %v)", n, err)
|
||||
}
|
||||
_ = conn.SetDeadline(time.Now().Add(5 * time.Second))
|
||||
if err := cli.WriteRequest(conn, cli.Request{Line: []string{"status"}}); err != nil {
|
||||
t.Fatalf("the request could not be written: %v", err)
|
||||
}
|
||||
a, err := cli.ReadAnswer(conn)
|
||||
if err != nil || !strings.Contains(a.Refused, "busy") {
|
||||
t.Fatalf("a line written after the engine chose busy reads %+v (%v)", a, err)
|
||||
}
|
||||
if len(started) != 0 {
|
||||
t.Fatal("the busy line was asked")
|
||||
}
|
||||
}
|
||||
|
||||
// **A process in another namespace is not this machine's account** (review of ADR 0272): a container running with
|
||||
// the operator's user id shares the id and not the machine; refused, and nothing is asked.
|
||||
func TestAPeerInAnotherNamespaceIsRefused(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user