Skip to content

Conversation

aepfli
Copy link
Member

@aepfli aepfli commented Jul 29, 2025

trying to add context value enrichment approach for in-process mode.

Long time no golang, so this might be a little bit of sub optimal approach open for feedback/insights

@aepfli aepfli force-pushed the feat/contextValues branch from fa7a42d to 30a0194 Compare July 30, 2025 06:34
@aepfli
Copy link
Member Author

aepfli commented Sep 1, 2025

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a context enrichment mechanism for the in-process provider, which is a valuable feature. The implementation is mostly on the right track, but there are a few important issues to address. There is a race condition in the in_process service when accessing contextValues, which needs to be fixed with a mutex. The ContextValues function should also return a copy of the map to prevent mutation of the service's internal state. Additionally, there are a couple of issues in the tests, including a race condition due to mutation of shared state in a parallel test, and an incomplete map comparison. Addressing these points will make the implementation more robust and reliable.

Comment on lines +307 to +311
WithContextEnricher(func(m map[string]any) *of.EvaluationContext {
m["test2"] = "test2"
context := of.NewTargetlessEvaluationContext(m)
return &context
}),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The ContextEnricher function in this test case mutates its input map m. Since the tests are marked with t.Parallel(), and the enrichment map is shared across test cases, this will lead to a race condition and unpredictable test failures. The enricher function should not modify its input; it should work on a copy if modifications are needed.

For example:

WithContextEnricher(func(m map[string]any) *of.EvaluationContext {
    newMap := make(map[string]any, len(m)+1)
    for k, v := range m {
        newMap[k] = v
    }
    newMap["test2"] = "test2"
    context := of.NewTargetlessEvaluationContext(newMap)
    return &context
})

Comment on lines +300 to +302
func (i *InProcess) ContextValues() map[string]any {
return i.contextValues
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The contextValues field is accessed concurrently without synchronization, leading to a race condition. It's written in the sync goroutine (lines 115-117) and read by evaluation goroutines via this ContextValues method. Furthermore, this method returns a direct reference to the internal map, which allows callers to mutate the service's internal state.

To resolve this, you should introduce a sync.RWMutex to protect accesses to contextValues and return a copy of the map from this function. Remember to also protect the write operation on lines 115-117.

Example implementation:

func (i *InProcess) ContextValues() map[string]any {
    i.mtx.RLock()
    defer i.mtx.RUnlock()

    if i.contextValues == nil {
        return nil
    }

    // Return a copy to prevent mutation of internal state
    contextValuesCopy := make(map[string]any, len(i.contextValues))
    for k, v := range i.contextValues {
        contextValuesCopy[k] = v
    }
    return contextValuesCopy
}

Comment on lines +416 to +420
for k, v := range test.expectContextEnrichment {
if enriched[k] != v {
t.Errorf("incorrect context_enrichment attribute, expected %v, got %v", v, enriched[k])
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This loop only checks that the keys from test.expectContextEnrichment exist in enriched with the correct values. It doesn't check if enriched has extra keys, or if some keys are missing. For a more robust check, you should compare the maps for equality, for example using reflect.DeepEqual. This will also make the test assertion more concise.

if !reflect.DeepEqual(test.expectContextEnrichment, enriched) {
	t.Errorf("incorrect context_enrichment attribute, expected %v, got %v", test.expectContextEnrichment, enriched)
}

@aepfli
Copy link
Member Author

aepfli commented Sep 17, 2025

/gemini summary

Copy link
Contributor

Summary of Changes

This pull request enhances the in-process flagd provider by introducing a flexible context enrichment mechanism. It allows users to define custom functions to modify or extend the evaluation context dynamically, ensuring that flag evaluations can incorporate additional, derived, or external data. This is achieved through a new "ContextEnricher" configuration option and its integration via a "SyncContextHook", providing a powerful way to customize flag evaluation behavior.

Highlights

  • Context Enrichment for In-Process Provider: Introduced a new "ContextEnricher" type and "WithContextEnricher" option to allow custom logic for modifying or adding to the evaluation context for the in-process flagd provider.
  • IService Interface Update: The "IService" interface now includes a "ContextValues()" method, enabling services to expose their current context values.
  • InProcess Service Context Storage: The "InProcess" service now stores "contextValues" derived from the "SyncContext" received during flag synchronization.
  • SyncContextHook Integration: A new "SyncContextHook" is used within the "Provider" to apply the configured "ContextEnricher" before flag evaluations.
  • Test Coverage: Added new test cases to "provider_test.go" to validate the functionality of the "WithContextEnricher" option and ensure proper context enrichment.
Changelog
  • providers/flagd/internal/mock/service_mock.go
    • Added "ContextValues()" method to "MockIService" for testing purposes.
  • providers/flagd/pkg/configuration.go
    • Introduced "ContextEnricher" field to "ProviderConfiguration" and a "WithContextEnricher" option to set it, along with a default implementation.
  • providers/flagd/pkg/iservice.go
    • Extended "IService" interface with "ContextValues()" method.
  • providers/flagd/pkg/provider.go
    • Modified "Provider" to include a slice of "hooks" and integrated "NewSyncContextHook" to apply context enrichment.
  • providers/flagd/pkg/provider_test.go
    • Added new test cases to verify the "ContextEnricher" functionality, including scenarios for adding and modifying context values.
  • providers/flagd/pkg/service/in_process/service.go
    • Implemented "contextValues" storage and retrieval within the "InProcess" service, capturing "SyncContext" data.
  • providers/flagd/pkg/service/in_process/service_grpc_test.go
    • Updated gRPC tests to include "SyncContext" in mock responses and assert its presence.
  • providers/flagd/pkg/service/rpc/service.go
    • Added a placeholder "ContextValues()" method returning "nil" for the RPC service.
  • providers/flagd/pkg/sync_context_hook.go
    • New file defining the "ContextEnricher" type and "SyncContextHook" for applying context modifications.
Activity
  • aepfli initiated a Gemini review and later requested a summary.
  • Early discussions between aepfli and toddbaert revolved around the complexity and configurability of the context enrichment approach, leading to the adoption of a configurable option.
  • gemini-code-assist[bot] provided critical feedback, identifying high-priority race conditions in test cases and the InProcess service's ContextValues implementation, suggesting fixes involving defensive copying and mutexes.
  • gemini-code-assist[bot] also recommended using reflect.DeepEqual for more robust map comparison in tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants