Preserve maintenance timeout behavior and prepare SILO 20261026

Signed-off-by: Feng Ruohang <rh@vonng.com>
This commit is contained in:
Feng Ruohang
2026-10-26 22:43:51 +08:00
parent a13efd5760
commit dcc2f20ba8
34 changed files with 634 additions and 969 deletions
+2 -4
View File
@@ -344,8 +344,7 @@ func (c *Subroute) newMuxClient(ctx context.Context) (*muxClient, error) {
// Request allows to do a single remote request.
// 'req' will not be used after the call and caller can reuse.
// If no deadline is set on ctx, a 1-minute deadline will be added unless
// WithoutDefaultTimeout was requested.
// If no deadline is set on ctx, a 1-minute deadline will be added.
func (c *Connection) Request(ctx context.Context, h HandlerID, req []byte) ([]byte, error) {
if !h.valid() {
return nil, ErrUnknownHandler
@@ -371,8 +370,7 @@ func (c *Connection) Request(ctx context.Context, h HandlerID, req []byte) ([]by
// Request allows to do a single remote request.
// 'req' will not be used after the call and caller can reuse.
// If no deadline is set on ctx, a 1-minute deadline will be added unless
// WithoutDefaultTimeout was requested.
// If no deadline is set on ctx, a 1-minute deadline will be added.
func (c *Subroute) Request(ctx context.Context, h HandlerID, req []byte) ([]byte, error) {
if !h.valid() {
return nil, ErrUnknownHandler
+1 -2
View File
@@ -550,8 +550,7 @@ type Requester interface {
// Call the remote with the request and return the response.
// The response should be returned with PutResponse when no error.
// If no deadline is set, a 1-minute deadline is added unless
// WithoutDefaultTimeout was requested.
// If no deadline is set, a 1-minute deadline is added.
func (h *SingleHandler[Req, Resp]) Call(ctx context.Context, c Requester, req Req) (resp Resp, err error) {
if c == nil {
if h.ignoreNilConn {
+2 -14
View File
@@ -59,16 +59,6 @@ type Response struct {
Err error
}
type withoutDefaultTimeoutKey struct{}
// WithoutDefaultTimeout disables the implicit single-request timeout.
// Explicit context cancellation and connection loss still end the request.
// Use this for admitted mutations that must return their actual result before
// the caller releases its lock.
func WithoutDefaultTimeout(ctx context.Context) context.Context {
return context.WithValue(ctx, withoutDefaultTimeoutKey{}, true)
}
func newMuxClient(ctx context.Context, muxID uint64, parent *Connection) *muxClient {
ctx, cancelFn := context.WithCancelCause(ctx)
return &muxClient{
@@ -111,10 +101,8 @@ func (m *muxClient) roundtrip(h HandlerID, req []byte) ([]byte, error) {
m.respMu.Unlock()
ctx := m.ctx
// Admitted storage mutations must not time out while the handler can
// still commit. A zero wire deadline also keeps their late response.
noDefaultTimeout, _ := ctx.Value(withoutDefaultTimeoutKey{}).(bool)
if msg.DeadlineMS == 0 && !noDefaultTimeout {
// Add deadline if none.
if msg.DeadlineMS == 0 {
msg.DeadlineMS = uint32(defaultSingleRequestTimeout / time.Millisecond)
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, defaultSingleRequestTimeout)
+29 -53
View File
@@ -25,67 +25,43 @@ import (
"time"
)
func TestSingleMutationWaitsBeyondDefaultTimeout(t *testing.T) {
for _, admitted := range []bool{false, true} {
t.Run(map[bool]string{false: "ordinary", true: "admitted"}[admitted], func(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
ctx := t.Context()
if admitted {
ctx = WithoutDefaultTimeout(ctx)
}
conn := &Connection{outQueue: make(chan []byte, 1)}
client := newMuxClient(ctx, 1, conn)
defer client.cancelFn(context.Canceled)
done := make(chan Response, 1)
go func() {
msg, err := client.roundtrip(handlerTest, nil)
done <- Response{Msg: msg, Err: err}
}()
wire := <-conn.outQueue
var request message
if _, err := request.UnmarshalMsg(wire); err != nil {
t.Fatal(err)
}
PutByteBuffer(wire)
if admitted && request.DeadlineMS != 0 {
t.Errorf("admitted mutation tells the server to discard a late result after %dms", request.DeadlineMS)
}
time.Sleep(defaultSingleRequestTimeout + time.Second)
synctest.Wait()
if !admitted {
select {
case got := <-done:
if !errors.Is(got.Err, context.DeadlineExceeded) {
t.Fatalf("ordinary timeout: %v", got.Err)
}
default:
t.Fatal("ordinary request lost its default timeout")
}
return
}
select {
case got := <-done:
t.Fatalf("mutation returned before its commit completed: %v", got.Err)
default:
}
client.response(0, Response{Msg: []byte("committed")})
got := <-done
if got.Err != nil || string(got.Msg) != "committed" {
t.Fatalf("lost late commit result: %q, %v", got.Msg, got.Err)
}
})
})
}
func TestSingleRequestDefaultTimeout(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
conn := &Connection{outQueue: make(chan []byte, 1)}
client := newMuxClient(t.Context(), 1, conn)
defer client.cancelFn(context.Canceled)
done := make(chan error, 1)
go func() { _, err := client.roundtrip(handlerTest, nil); done <- err }()
wire := <-conn.outQueue
var request message
if _, err := request.UnmarshalMsg(wire); err != nil {
t.Fatal(err)
}
PutByteBuffer(wire)
if request.DeadlineMS != uint32(defaultSingleRequestTimeout/time.Millisecond) {
t.Fatalf("wire deadline: %d", request.DeadlineMS)
}
time.Sleep(defaultSingleRequestTimeout + time.Second)
synctest.Wait()
select {
case err := <-done:
if !errors.Is(err, context.DeadlineExceeded) {
t.Fatal(err)
}
default:
t.Fatal("request outlived the default timeout")
}
})
}
func TestWithoutDefaultTimeoutStillEndsOnDisconnectOrCancellation(t *testing.T) {
func TestSingleRequestCancellationAndDisconnect(t *testing.T) {
for _, disconnect := range []bool{false, true} {
t.Run(map[bool]string{false: "explicit-cancel", true: "disconnect"}[disconnect], func(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
ctx, cancel := context.WithCancel(t.Context())
defer cancel()
conn := &Connection{outQueue: make(chan []byte, 1)}
client := newMuxClient(WithoutDefaultTimeout(ctx), 1, conn)
client := newMuxClient(ctx, 1, conn)
defer client.cancelFn(context.Canceled)
done := make(chan error, 1)
go func() {