Skip to content

Commit 7026fac

Browse files
committed
Retry config sync on concurrent changes
Snapshot the current config generation before building the merged sync view, normalize the merged role/workflow/provider definition maps off-lock, and only commit them if the generation is unchanged. Keep the retry logic scoped to MergeConfiguration, compare and commit definitions only, and detach the snapshot through JSON so stale retries do not alias nested state. Reloaded definitions now bump the generation counter, while broader nested-mutation cleanup remains tracked in #306.
1 parent c1e09eb commit 7026fac

5 files changed

Lines changed: 669 additions & 82 deletions

File tree

docs/development/index.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,12 @@ description: Developer documentation for Thand Agent
99
# Development
1010

1111
Documentation for developers contributing to or extending the Thand Agent.
12+
13+
## Config Mutation Invariant
14+
15+
Configuration definition maps should be treated as immutable snapshots.
16+
When config changes, prefer replacing whole entries or whole definition maps
17+
instead of mutating nested state in place. Some older code paths still perform
18+
mutation-prone updates; keep new code aligned with the invariant and track
19+
cleanup of legacy exceptions in follow-up issues rather than extending them.
20+
Current cleanup work is tracked in [#306](https://github.com/thand-io/agent/issues/306).

internal/config/config.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,7 @@ func (c *Config) ReloadConfig() error {
360360
logrus.Infoln("Loaded workflows from external source:", len(workflows))
361361
c.mu.Lock()
362362
c.Workflows.Definitions = workflows
363+
c.configGeneration++
363364
c.mu.Unlock()
364365
} else {
365366
logrus.Warningln("No workflows loaded from external source")
@@ -378,6 +379,7 @@ func (c *Config) ReloadConfig() error {
378379
logrus.Infoln("Loaded providers from external source:", len(providers))
379380
c.mu.Lock()
380381
c.Providers.Definitions = providers
382+
c.configGeneration++
381383
c.mu.Unlock()
382384
} else {
383385
logrus.Warningln("No providers loaded from external source")
@@ -394,6 +396,7 @@ func (c *Config) ReloadConfig() error {
394396
logrus.Infoln("Loaded roles from external source:", len(roles))
395397
c.mu.Lock()
396398
c.Roles.Definitions = roles
399+
c.configGeneration++
397400
c.mu.Unlock()
398401
} else {
399402
logrus.Warningln("No roles loaded from external source")

internal/config/model.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ type Config struct {
6464
logger thandLogger
6565
mu sync.RWMutex
6666

67+
// Incremented whenever synced config definitions actually change.
68+
// Definition maps should be treated as immutable snapshots: callers should
69+
// replace whole entries or whole maps rather than mutating nested state
70+
// in place. Legacy mutation-prone paths are being tracked in issue #306.
71+
configGeneration uint64
72+
6773
// Cached services client
6874
initializeServiceClientOnce sync.Once
6975
servicesClient models.ServicesClientImpl

0 commit comments

Comments
 (0)