Skip to content

connect: NewStorage context deadline causes TCS connection to fail after etcd timeout #94

@oleg-jukovec

Description

@oleg-jukovec

Problem

The NewStorage function in connect/connect.go uses a single ctx context.Context parameter for both etcd and TCS connection attempts. When the caller passes a context with a deadline (e.g., context.WithTimeout(ctx, 5*time.Second)), the deadline applies to the entire NewStorage call, not to each individual connection attempt.

This causes a problem when connecting to TCS after etcd fails:

  1. NewEtcdStorage(ctx, cfg) is called first
  2. Inside createEtcdClient, it creates statusCtx with context.WithTimeout(ctx, cfg.dialTimeout())
  3. If etcd is unavailable, the full dialTimeout is consumed
  4. By the time NewTCSStorage(ctx, cfg) is called, the parent ctx deadline has already expired
  5. TCS connection fails immediately with context deadline exceeded, even though TCS is available

Example

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

cfg := connect.Config{
    Endpoints:   []string{"localhost:4401"},
    Username:    "client",
    Password:    "secret",
    DialTimeout: 5 * time.Second,
}

// This fails with TCS even though TCS is available,
// because etcd attempt consumed the entire 5s deadline.
stg, cleanup, err := connect.NewStorage(ctx, cfg)

Current Behavior

  • With ctx having a 5s deadline and DialTimeout: 5s:
    • etcd attempt takes ~5s (timeout)
    • TCS attempt fails immediately (parent ctx expired)
  • Result: Cannot connect to TCS when etcd is unavailable

Expected Behavior

Each connection attempt (etcd and TCS) should have its own independent timeout, so that if etcd fails, TCS still has a full DialTimeout window to connect.

Workaround

Callers can currently work around this by passing context.Background() (without a deadline) and relying on DialTimeout alone. However, this loses the ability to cancel the operation externally (e.g., on application shutdown).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions