Skip to content

[Go SDK] InMemoryBackend stores caller references without deep copy (data race) #432

@santoshkumarradha

Description

@santoshkumarradha

Summary

InMemoryBackend.Set stores the caller's map/slice reference directly, and Get returns it without copying, creating a data race between the caller and concurrent readers or writers.

Context

memory.go:389 does b.data[ck][key] = value — the caller's map[string]any pointer is stored directly in the backend. Get at line 402 returns that same pointer after releasing RLock. Any caller that reads a value, then modifies the returned map, races with another goroutine that is concurrently reading or writing the same key. SetVector has the same pattern with []float64 and metadata maps. The backend's RWMutex protects the map structure but does nothing to protect the values stored inside it.

Scope

In Scope

  • Deep-copy map[string]any values on Set (before storing) and on Get (before returning).
  • Deep-copy []float64 slices and associated metadata maps in SetVector / GetVector.
  • A JSON marshal/unmarshal round-trip is an acceptable deep-copy strategy if a dedicated recursive clone is not already available in the codebase.

Out of Scope

  • Changing the MemoryBackend interface signature (context propagation is tracked separately in G2).
  • Optimising for zero-copy hot paths — correctness first.
  • Fixing ControlPlaneMemoryBackend — that is tracked separately.

Files

  • sdk/go/agent/memory.go:389 — deep-copy value in Set before storing
  • sdk/go/agent/memory.go:402 — deep-copy value in Get before returning
  • sdk/go/agent/memory.go:435 — deep-copy slice and metadata in SetVector / GetVector
  • sdk/go/agent/memory_test.go — race test: store a map, mutate the original, verify stored value is unchanged; store then mutate returned value, verify stored is unchanged; run with -race

Acceptance Criteria

  • Mutating the map passed to Set after the call does not affect the stored value
  • Mutating the map returned by Get does not affect the stored value
  • Same guarantees hold for SetVector / GetVector slices and metadata
  • Tests pass with -race flag (go test -race ./sdk/go/...)
  • Linting passes (make lint)

Notes for Contributors

Severity: HIGH

Run go test -race ./sdk/go/agent/... before and after the fix to verify the race detector catches the existing bug and clears after the fix. A JSON-based deep copy (json.Marshal + json.Unmarshal) is safe and simple; a reflect-based recursive clone is faster but more code to maintain — choose based on the hot-path requirements of the memory backend.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingsdk:goGo SDK related

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions