-
Notifications
You must be signed in to change notification settings - Fork 535
Expand file tree
/
Copy pathdynamic_config.go
More file actions
160 lines (142 loc) · 4.5 KB
/
Copy pathdynamic_config.go
File metadata and controls
160 lines (142 loc) · 4.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016 Datadog, Inc.
package tracer
import (
"github.com/DataDog/dd-trace-go/v2/internal/locking"
"github.com/DataDog/dd-trace-go/v2/internal/telemetry"
)
// dynamicConfig is a thread-safe generic data structure to represent configuration fields.
// It's designed to satisfy the dynamic configuration semantics (i.e reset, update, apply configuration changes).
// This structure will be extended to track the origin of configuration values as well (e.g remote_config, env_var).
type dynamicConfig[T any] struct {
mu locking.RWMutex
// holds the current configuration value
current T // +checklocks:mu
// holds the startup configuration value
startup T // +checklocks:mu
// holds the name of the configuration, has to be compatible with telemetry.Configuration.Name
cfgName string // +checklocks:mu
// holds the origin of the current configuration value (currently only supports remote_config, empty otherwise)
cfgOrigin telemetry.Origin // +checklocks:mu
// executes any config-specific operations to propagate the update properly, returns whether the update was applied
apply func(T) bool // +checklocks:mu
// compares two configuration values, this is used to avoid unnecessary config and telemetry updates
equal func(x, y T) bool // +checklocks:mu
}
func newDynamicConfig[T any](name string, val T, apply func(T) bool, equal func(x, y T) bool) dynamicConfig[T] {
return dynamicConfig[T]{
cfgName: name,
current: val,
startup: val,
cfgOrigin: telemetry.OriginDefault,
apply: apply,
equal: equal,
}
}
// get returns the current configuration value
func (dc *dynamicConfig[T]) get() T {
dc.mu.RLock()
defer dc.mu.RUnlock()
return dc.current
}
func (dc *dynamicConfig[T]) set(f func(current T) T) {
dc.mu.Lock()
defer dc.mu.Unlock()
dc.current = f(dc.current)
}
// update applies a new configuration value
func (dc *dynamicConfig[T]) update(val T, origin telemetry.Origin) bool {
dc.mu.Lock()
defer dc.mu.Unlock()
if dc.equal(dc.current, val) {
return false
}
dc.current = val
dc.cfgOrigin = origin
return dc.apply(val)
}
// reset re-applies the startup configuration value
func (dc *dynamicConfig[T]) reset() bool {
dc.mu.Lock()
defer dc.mu.Unlock()
if dc.equal(dc.current, dc.startup) {
return false
}
dc.current = dc.startup
// TODO: set the origin to the startup value's origin
dc.cfgOrigin = telemetry.OriginDefault
return dc.apply(dc.startup)
}
// handleRC processes a new configuration value from remote config
// Returns whether the configuration value has been updated or not
func (dc *dynamicConfig[T]) handleRC(val *T) bool {
if val != nil {
return dc.update(*val, telemetry.OriginRemoteConfig)
}
return dc.reset()
}
// toTelemetry returns the current configuration value as telemetry.Configuration
func (dc *dynamicConfig[T]) toTelemetry() telemetry.Configuration {
dc.mu.RLock()
defer dc.mu.RUnlock()
return telemetry.Configuration{
Name: dc.cfgName,
Value: dc.current,
Origin: dc.cfgOrigin,
}
}
// setOrigin safely sets the configuration origin
func (dc *dynamicConfig[T]) setOrigin(origin telemetry.Origin) {
dc.mu.Lock()
defer dc.mu.Unlock()
dc.cfgOrigin = origin
}
// Origin safely reads the configuration origin
func (dc *dynamicConfig[T]) Origin() telemetry.Origin {
dc.mu.RLock()
defer dc.mu.RUnlock()
return dc.cfgOrigin
}
// getCurrentAndOrigin atomically reads both current value and origin
// This prevents TOCTOU bugs when both values are needed
func (dc *dynamicConfig[T]) getCurrentAndOrigin() (T, telemetry.Origin) {
dc.mu.RLock()
defer dc.mu.RUnlock()
return dc.current, dc.cfgOrigin
}
// setStartup updates the startup value (used during initialization)
func (dc *dynamicConfig[T]) setStartup(val T) {
dc.mu.Lock()
defer dc.mu.Unlock()
dc.startup = val
}
func equal[T comparable](x, y T) bool {
return x == y
}
// equalSlice compares two slices of comparable values
// The comparison takes into account the order of the elements
func equalSlice[T comparable](x, y []T) bool {
if len(x) != len(y) {
return false
}
for i, v := range x {
if v != y[i] {
return false
}
}
return true
}
// equalMap compares two maps of comparable keys and values
func equalMap[T comparable](x, y map[T]any) bool {
if len(x) != len(y) {
return false
}
for k, v := range x {
if yv, ok := y[k]; !ok || yv != v {
return false
}
}
return true
}