Skip to content

cacheRobotClient is not concurrency-safe #85

Description

@guettli

cacheRobotClient is not concurrency-safe

Description

cacheRobotClient keeps shared mutable state without synchronization, but the same client instance can be accessed from multiple goroutines.

The affected state lives in client.go:

  • l
  • m
  • lastUpdate
  • forcedRefreshServerNames

These fields are read and written from multiple methods without any mutex, for example:

There is also a realistic concurrent path in production:

  • request handling reads and mutates the cache via Robot lookup code in util.go
  • credential hot-reload can update the same client in the background via hotreload.go

This means cache invalidation, forced refresh bookkeeping, and normal reads can overlap.

Why this matters

This can lead to data races and inconsistent cache behavior, for example:

  • concurrent read/write of m
  • concurrent read/write of forcedRefreshServerNames
  • stale or partially refreshed cache state across l, m, and lastUpdate
  • cache invalidation in SetCredentials racing with ongoing lookups

Even if this does not always crash, it makes behavior timing-dependent and hard to reason about.

Expected behavior

cacheRobotClient should be safe for concurrent use by multiple goroutines.

Actual behavior

The client relies on unsynchronized shared mutable state.

Suggested fix

Protect the cache state with a sync.RWMutex or sync.Mutex and make cache updates atomic across:

  • l
  • m
  • lastUpdate
  • forcedRefreshServerNames

In particular, SetCredentials, ServerGetListForceRefresh, ServerGet, ServerGetList, and the forced-refresh bookkeeping methods should all participate in the same locking scheme.

Acceptance criteria

  • no unsynchronized access to mutable fields in cacheRobotClient
  • concurrent reads and invalidations are safe
  • add a focused concurrency test for cache access plus SetCredentials
  • go test -race ./... stays clean

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    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