Files
go-omni/transport/native/disconnect_test.go

105 lines
3.0 KiB
Go

package native
import (
"bufio"
"context"
"errors"
"io"
"net"
"net/http"
"strings"
"testing"
"time"
)
type observedReadConn struct {
net.Conn
reading chan struct{}
}
func (c *observedReadConn) Read(data []byte) (int, error) {
select {
case c.reading <- struct{}{}:
default:
}
return c.Conn.Read(data)
}
func TestDisconnectWatchStopsBeforeReaderReuse(t *testing.T) {
server, client := net.Pipe()
defer server.Close()
defer client.Close()
conn := &observedReadConn{Conn: server, reading: make(chan struct{}, 1)}
reader := bufio.NewReader(conn)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
watch := newClientDisconnectWatch(reader, conn, cancel)
select {
case <-conn.reading:
case <-time.After(time.Second):
t.Fatal("disconnect watcher did not start reading")
}
watch.stop()
if ctx.Err() != nil {
t.Fatalf("interrupting the watcher canceled a healthy request: %v", ctx.Err())
}
if err := server.SetReadDeadline(time.Now().Add(time.Second)); err != nil {
t.Fatal(err)
}
writeDone := make(chan error, 1)
go func() {
_, err := io.WriteString(client, "GET /next HTTP/1.1\r\n")
writeDone <- err
}()
line, err := reader.ReadString('\n')
if err != nil || line != "GET /next HTTP/1.1\r\n" {
t.Fatalf("reader retained watcher data or deadline: line=%q err=%v", line, err)
}
if err := <-writeDone; err != nil {
t.Fatal(err)
}
}
func TestDisconnectWatchPreservesPipelineAndWriteFailureCancels(t *testing.T) {
server, client := net.Pipe()
defer server.Close()
defer client.Close()
pipeline := "GET /next HTTP/1.1\r\nHost: test\r\n\r\n"
reader := bufio.NewReader(strings.NewReader(pipeline))
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
watch := newClientDisconnectWatch(reader, server, cancel)
select {
case <-watch.done:
case <-time.After(time.Second):
t.Fatal("watcher should stop when pipelined data is available")
}
watch.stop()
if ctx.Err() != nil {
t.Fatal("pipeline data canceled the current request")
}
remaining, err := io.ReadAll(reader)
if err != nil || string(remaining) != pipeline {
t.Fatalf("pipeline bytes changed: data=%q err=%v", remaining, err)
}
// With pending pipeline data, EOF detection is deferred. A failed stream
// write must still cancel downstream work even when the handler ignores it.
client.Close()
writer := newResponseWriter(http.MethodGet)
writer.configure(&http.Request{ProtoMajor: 1, ProtoMinor: 1}, server, false, time.Second, cancel)
writer.Flush()
if !errors.Is(ctx.Err(), context.Canceled) {
t.Fatal("failed Flush did not cancel request context")
}
if _, err := writer.Write([]byte("more")); err == nil {
t.Fatal("failed Flush was not returned by subsequent Write")
}
if err := writer.writeTo(server, false, time.Second); err == nil {
t.Fatal("failed Flush was not returned on response completion")
}
writer.reset("")
if writer.cancel != nil || writer.writeErr != nil || writer.conn != nil {
t.Fatal("pooled response retained the previous request")
}
}