mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-05-27 12:52:27 +02:00
doq: use OpenStreamSync and retry on StreamLimitReachedError
Replace conn.OpenStream (non-blocking) with conn.OpenStreamSync so that the resolver waits for the server's MAX_STREAMS credit replenishment frame instead of immediately failing when the stream limit is temporarily exhausted. Also retry on StreamLimitReachedError as defense-in-depth for servers that are slow or fail to send MAX_STREAMS updates.
This commit is contained in:
committed by
Cuong Manh Le
parent
a92d20cef8
commit
a767ebdaa5
@@ -100,8 +100,9 @@ func newDOQConnPool(_ context.Context, uc *UpstreamConfig, addrs []string) *doqC
|
|||||||
|
|
||||||
// Resolve performs a DNS query using a pooled QUIC connection.
|
// Resolve performs a DNS query using a pooled QUIC connection.
|
||||||
func (p *doqConnPool) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, error) {
|
func (p *doqConnPool) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, error) {
|
||||||
// Retry logic for transient errors: io.EOF (connection reset) and
|
// Retry logic for transient errors: io.EOF (connection reset),
|
||||||
// IdleTimeoutError (stale pooled connection timed out).
|
// IdleTimeoutError (stale pooled connection timed out), and
|
||||||
|
// StreamLimitReachedError (stream credit exhausted before server MAX_STREAMS arrived).
|
||||||
for range 5 {
|
for range 5 {
|
||||||
answer, err := p.doResolve(ctx, msg)
|
answer, err := p.doResolve(ctx, msg)
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
@@ -111,6 +112,10 @@ func (p *doqConnPool) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, erro
|
|||||||
if errors.As(err, &idleErr) {
|
if errors.As(err, &idleErr) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
var streamLimitErr quic.StreamLimitReachedError
|
||||||
|
if errors.As(err, &streamLimitErr) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, wrapCertificateVerificationError(err)
|
return nil, wrapCertificateVerificationError(err)
|
||||||
}
|
}
|
||||||
@@ -135,18 +140,25 @@ func (p *doqConnPool) doResolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, er
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open a new stream for this query
|
// Ensure the context has a deadline before calling OpenStreamSync, which
|
||||||
stream, err := conn.OpenStream()
|
// blocks until the server sends a MAX_STREAMS update. Without a deadline the
|
||||||
|
// call could block indefinitely when the server never sends the update.
|
||||||
|
deadline, ok := ctx.Deadline()
|
||||||
|
if !ok {
|
||||||
|
var cancel context.CancelFunc
|
||||||
|
ctx, cancel = context.WithTimeout(ctx, 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
deadline, _ = ctx.Deadline()
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenStreamSync blocks until the server's MAX_STREAMS credit arrives,
|
||||||
|
// avoiding the StreamLimitReachedError race that OpenStream (non-blocking)
|
||||||
|
// triggers when the credit replenishment frame is still in flight.
|
||||||
|
stream, err := conn.OpenStreamSync(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.putConn(conn, false)
|
p.putConn(conn, false)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set deadline
|
|
||||||
deadline, ok := ctx.Deadline()
|
|
||||||
if !ok {
|
|
||||||
deadline = time.Now().Add(5 * time.Second)
|
|
||||||
}
|
|
||||||
_ = stream.SetDeadline(deadline)
|
_ = stream.SetDeadline(deadline)
|
||||||
|
|
||||||
// Write message length (2 bytes) followed by message
|
// Write message length (2 bytes) followed by message
|
||||||
|
|||||||
Reference in New Issue
Block a user