// LoadYamlFile is a convenience function to create a YamlLoader and call LoadFile.
var lr YamlLoader
lr.LoadFile(contents)
return &lr
}
// Err returns a joined error of lr.Errors.
Frontier kind: Code frontier
unlabeled · c_e77d8b42196b
49 tests · 1905 LOC · 70 files · introduces 0 tests · 225 LOC · 5 files
The orange circle is the focus. Violet and green circles are every ancestor and descendant, broader and narrower, at any distance; blue squares and pink diamonds are the introduced files and exact introduced tests of every visible concept, not only the focus's. Arrows point from broader to narrower concepts and bridge only concepts omitted from this view. Undirected links show source or test introduction. Concept and file size follows LOC; exact test nodes use test-count units.
Introduced files, introduced tests, and structurally relevant concept specialization
In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the native relationship evidence on this page.
Graph controls are ready.
Interactive rendering requires JavaScript and WebGL. Use the native relationship evidence on this page while the interactive map is unavailable.
Every exact file and test below is linked only from the concept that introduces it.
go.temporal.io/server/common/dynamicconfig/TestFileBasedClientSuite/TestGetIntValue_FilterByTQ_NamespaceOnlygo.temporal.io/server/common/dynamicconfig/TestFileBasedClientSuite/TestGetIntValue_FilteredByActivityTaskQueueInfogo.temporal.io/server/common/dynamicconfig/TestFileBasedClientSuite/TestGetIntValue_FilteredByNoTaskTypeQueueInfogo.temporal.io/server/common/dynamicconfig/TestFileBasedClientSuite/TestGetIntValue_FilteredByTaskQueueNameOnlygo.temporal.io/server/common/dynamicconfig/TestFileBasedClientSuite/TestGetIntValue_FilteredByWorkflowTaskQueueInfogo.temporal.io/server/common/circuitbreaker/TestTSCBWithDynamicSettingsgo.temporal.io/server/common/dynamicconfig/TestDeepCopy_OtherReferenceTypes_Nilgo.temporal.io/server/service/matching/configs/TestQuotasSuite/TestAPIPrioritiesOrderedgo.temporal.io/server/service/matching/configs/TestQuotasSuite/TestAPIToPriorityMappingEvery collected test enters the hierarchy at exactly one concept.
No tests are introduced at this concept. Its intent tests are introduced by other concepts.
Every collected source range enters the hierarchy at exactly one concept.
5 files ranked by introduced lines: 225 introduced LOC across 68 ranges. Expand a file to inspect source; the > gutter marks introduced lines.
// LoadYamlFile is a convenience function to create a YamlLoader and call LoadFile.
var lr YamlLoader
lr.LoadFile(contents)
return &lr
}
// Err returns a joined error of lr.Errors.
// LoadFile parses and processes the given file contents into lr.Map (initialized if nil).
var yamlValues map[string]yamlConstrainedValue
if err := yaml.Unmarshal(contents, &yamlValues); err != nil {
lr.errorf("decode error: %w", err)
return
}
lr.Map = make(ConfigValueMap, len(yamlValues))
}
lr.add(MakeKey(key), yamlCV)
}
}
}
precedence := PrecedenceUnknown
setting := queryRegistry(key)
if setting == nil {
lr.warnf("unregistered key %q", key)
} else {
precedence = setting.Precedence()
}
for i, cv := range yamlCV {
// yaml will unmarshal map into map[interface{}]interface{} instead of map[string]interface{}
// manually convert key type to string for all values here
val, err := convertKeyTypeToString(cv.Value)
if err != nil {
lr.error(err)
continue
// try validating if known setting
if valErr := setting.Validate(val); valErr != nil {
// TODO: raise this to error level
lr.warnf("validation failed: key %q value %v: %w", key, cv.Value, valErr)
}
}
cvs[i].Constraints = convertYamlConstraints(key, cv.Constraints, precedence, lr)
}
}
lr.Warnings = append(lr.Warnings, err)
}
lr.warn(fmt.Errorf(format, args...))
}
func (lr *YamlLoader) error(err error) {
}
switch v := v.(type) {
case map[any]any:
return convertKeyTypeToStringMap(v)
case []any:
return convertKeyTypeToStringSlice(v)
return v, nil
}
}
// nolint:revive // cognitive-complexity, it's just a big switch
func convertYamlConstraints(key Key, m map[string]any, precedence Precedence, lr *YamlLoader) Constraints {
yaml_loader.go
var cs Constraints
for k, v := range m {
validConstraint := true
switch strings.ToLower(k) {
case "namespace":
if v, ok := v.(string); ok {
cs.Namespace = v
} else {
lr.errorf("namespace constraint must be string")
}
validConstraint = precedence == PrecedenceNamespace || precedence == PrecedenceTaskQueue || precedence == PrecedenceDestination
yaml_loader.go
case "namespaceid":
if v, ok := v.(string); ok {
}
validConstraint = precedence == PrecedenceNamespaceID
if v, ok := v.(string); ok {
cs.TaskQueueName = v
} else {
lr.errorf("taskQueueName constraint must be string")
}
case "tasktype":
switch v := v.(type) {
case string:
i, err := enumspb.TaskQueueTypeFromString(v)
if err != nil {
lr.errorf("invalid value for taskType: %w", err)
lr.errorf("taskType constraint must be Workflow/Activity")
}
case int:
if v > int(enumspb.TASK_QUEUE_TYPE_UNSPECIFIED) {
lr.errorf("taskType constraint must be Workflow/Activity")
}
case "historytasktype":
switch v := v.(type) {
case string:
tt, err := enumsspb.TaskTypeFromString(v)
if err != nil {
lr.errorf("invalid value for historytasktype constraint: %w", err)
lr.errorf("historytasktype %s constraint is not supported", v)
}
case int:
cs.TaskType = enumsspb.TaskType(v)
default:
lr.errorf("historytasktype %T constraint is not supported", v)
}
case "shardid":
if v, ok := v.(int); ok {
}
validConstraint = precedence == PrecedenceShardID
if v, ok := v.(string); ok {
cs.Destination = v
} else {
lr.errorf("destination constraint must be string")
}
case "chasmtasktype":
if v, ok := v.(string); ok {
cs.ChasmTaskType = v
} else {
lr.errorf("chasmtasktype constraint must be string")
}
default:
lr.errorf("unknown constraint type %q", k)
// unregistered key above
// TODO: raise this to error level
lr.warnf("constraint %q isn't valid for dynamic config key %q", k, key)
}
}
}
}
func NewFileBasedClientWithMetrics(config *FileBasedClientConfig, logger log.Logger, doneCh <-chan any, metricsHandler metrics.Handler) (*FileBasedClient, error) {
file_based_client.go
if config == nil {
return nil, errors.New("configuration for dynamic config client is nil")
}
return NewFileBasedClientWithReader(reader, config, logger, doneCh, metricsHandler)
}
func NewFileBasedClientWithReader(reader FileReader, config *FileBasedClientConfig, logger log.Logger, doneCh <-chan any, metricsHandler metrics.Handler) (*FileBasedClient, error) {
file_based_client.go
if config == nil {
return nil, errors.New("configuration for dynamic config client is nil")
}
return nil, errors.New("file reader for dynamic config client is nil")
}
return nil, errors.New("logger for dynamic config client is nil")
}
metricsHandler = metrics.NoopMetricsHandler
logger.Warn("metrics handler is nil, using noop metrics handler")
}
logger: logger,
reader: reader,
config: config,
doneCh: doneCh,
NotifyingClientImpl: NewNotifyingClientImpl(),
}
client.metricsHandler.Store(&metricsHandler)
err := client.init()
if err != nil {
return nil, err
}
}
}
h := fc.metricsHandler.Load() // nolint:revive // unchecked-type-assertion
if h == nil {
// this should never happen, but we'll log a warning if it does
fc.logger.Warn("dynamic config is missing correct metrics handler, using noop metrics handler")
return metrics.NoopMetricsHandler
}
}
}
if err := fc.validateStaticConfig(fc.config); err != nil {
return fmt.Errorf("unable to validate dynamic config: %w", err)
}
return fmt.Errorf("unable to read dynamic config: %w", err)
}
ticker := time.NewTicker(fc.config.PollInterval)
for {
select {
case <-ticker.C:
err := fc.Update()
fc.logger.Error("Unable to update dynamic config.", tag.Error(err))
}
ticker.Stop()
return
}
}
}()
}
// This is public mainly for testing. The update loop will call this periodically, you don't
// have to call it explicitly.
modtime, updateErr := fc.reader.GetModTime()
retryOnErr := true
defer func() {
h := fc.getMetricsHandler()
// gauge value 1 refers to a failed update state, and should trigger alerts
if updateErr != nil {
metrics.DynamicConfigUpdateFailure.With(h).Record(1)
metrics.DynamicConfigUpdateFailure.With(h).Record(0)
}
if updateErr == nil || !retryOnErr {
fc.lastCheckedTime = modtime
}
}()
return fmt.Errorf("dynamic config file: %s: %w", fc.config.Filepath, updateErr)
}
return nil
}
if err != nil {
return fmt.Errorf("dynamic config file: %s: %w", fc.config.Filepath, err)
}
for _, e := range lr.Errors {
fc.logger.Error("dynamic config error", tag.Error(e))
}
fc.logger.Warn("dynamic config warning", tag.Error(w))
}
if len(lr.Errors) > 0 {
// we don't retry on parsing errors which will fail deterministically until the file is fixed
retryOnErr = false
}
oldValues, _ := prev.(ConfigValueMap) // nolint:revive // unchecked-type-assertion
changedMap := DiffAndLogConfigs(fc.logger, oldValues, lr.Map)
fc.logger.Info("Updated dynamic config")
fc.PublishUpdates(changedMap)
return nil
}
func (fc *FileBasedClient) validateStaticConfig(config *FileBasedClientConfig) error {
file_based_client.go
if config == nil {
return errors.New("configuration for dynamic config client is nil")
}
return fmt.Errorf("dynamic config: %s: %w", config.Filepath, err)
}
return fmt.Errorf("poll interval should be at least %v", minPollInterval)
}
}
return os.ReadFile(r.path)
}
fi, err := os.Stat(r.path)
if err != nil {
return time.Time{}, err
}
}
// returned as a ConfigValueMap that can be merged with old to produce new, except with deleted
// keys mapped to nil. It also logs the differences to a logger.
func DiffAndLogConfigs(logger log.Logger, oldValues ConfigValueMap, newValues ConfigValueMap) ConfigValueMap {
client_diff.go
changedMap := make(map[Key][]ConstrainedValue)
for key, newValues := range newValues {
oldValues, ok := oldValues[key]
if !ok {
for _, newValue := range newValues {
// new key added
diffAndLogValue(logger, key, nil, &newValue)
}
changedMap[Key(key)] = newValues
} else {
// compare existing keys
// check for removed values
if _, ok := newValues[key]; !ok {
for _, oldValue := range oldValues {
}
func diffAndLogValue(logger log.Logger, key Key, oldValue *ConstrainedValue, newValue *ConstrainedValue) {
client_diff.go
logLine := &strings.Builder{}
logLine.Grow(128)
logLine.WriteString("dynamic config changed for the key: ")
logLine.WriteString(key.String())
logLine.WriteString(" oldValue: ")
appendConstrainedValue(logLine, oldValue)
logLine.WriteString(" newValue: ")
appendConstrainedValue(logLine, newValue)
logger.Info(logLine.String())
}
func appendConstrainedValue(logLine *strings.Builder, value *ConstrainedValue) {
client_diff.go
if value == nil {
logLine.WriteString("nil")
} else {
logLine.WriteString("{ constraints: {")
if value.Constraints.Namespace != "" {
fmt.Fprintf(logLine, "{Namespace:%s}", value.Constraints.Namespace)
}
if value.Constraints.NamespaceID != "" {
fmt.Fprintf(logLine, "{NamespaceID:%s}", value.Constraints.NamespaceID)
}
fmt.Fprintf(logLine, "{TaskQueueName:%s}", value.Constraints.TaskQueueName)
}
if value.Constraints.TaskQueueType != enumspb.TASK_QUEUE_TYPE_UNSPECIFIED {
fmt.Fprintf(logLine, "{TaskQueueType:%s}", value.Constraints.TaskQueueType)
}
if value.Constraints.ShardID != 0 {
fmt.Fprintf(logLine, "{ShardID:%d}", value.Constraints.ShardID)
}
fmt.Fprintf(logLine, "{HistoryTaskType:%s}", value.Constraints.TaskType)
}
if value.Constraints.Destination != "" {
fmt.Fprintf(logLine, "{Destination:%s}", value.Constraints.Destination)
}
fmt.Fprint(logLine, "} value: ", value.Value, " }")
}
}
// TaskTypeFromString parses a TaskType value from either the protojson
// canonical SCREAMING_CASE enum or the traditional temporal PascalCase enum to TaskType
if v, ok := TaskType_value[s]; ok {
return TaskType(v), nil
return TaskType(v), nil
}
return TaskType(0), fmt.Errorf("%s is not a valid TaskType", s)
}
func (s NamespaceTypedSetting[T]) Key() Key { return s.key }
func (s NamespaceTypedSetting[T]) Precedence() Precedence { return PrecedenceNamespace }
setting_gen.go
func (s NamespaceTypedSetting[T]) Validate(v any) error {
_, err := s.convert(v)
return err
}
func (s NamespaceTypedConstrainedDefaultSetting[T]) Key() Key { return s.key }