fix: tracking callback invocation - #135
Open
vazarkevych wants to merge 6 commits into
Open
Conversation
added 2 commits
April 3, 2025 16:45
…eriment and experiment result for tracking callback invocation
# Conflicts: # lib/src/main/java/growthbook/sdk/java/evaluators/FeatureEvaluator.java
madhuchavva
requested changes
Apr 21, 2025
…cker to GlobalContext to make it singleton, remove ExperimentHelper from Options class
Resolve conflicts by taking main's implementation everywhere, since main's growthbook#216 remote-eval refactor superseded most of this branch's work: - remote-eval tracking dedup now lives in ExperimentEvaluator.fireRemoteEvaluationTracks() - tracking-key logic is in private trackingKey()/alreadyTracked() helpers - ExperimentTracker stays per-evaluator (already long-lived per GrowthBook instance) Preserve only the still-relevant fix: local isExperimentTracked() in ExperimentEvaluator always returned false, so the tracking callback re-fired on every evaluation. It now delegates to alreadyTracked() so repeated (hashAttribute, hashValue, experimentKey, variationId) combinations are de-duplicated, matching remote-eval behavior. NOTE: this changes tracking-callback firing behavior for local evaluation and should be confirmed with a maintainer before release.
Add regression tests proving TrackingCallback.onTrack fires exactly once per unique (hashAttribute, hashValue, experimentKey, variationId) assignment across repeated run() calls, and once per distinct experiment. These fail against the previous isExperimentTracked() that always returned false.
vazarkevych
force-pushed
the
fix-tracking-callback-invocation
branch
from
July 2, 2026 14:25
3068c39 to
35b68ad
Compare
Experiment de-duplication is handled solely by the bounded ExperimentTracker (Guava LRU cache, max 30), as requested in review. ExperimentHelper used an unbounded HashSet and was only instantiated as a dead field in GBContext (its isTracked() was never called). Removing the class and the field fully drops the unbounded-tracker remnant.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes duplicate invocation of the tracking callback (
TrackingCallback.onTrack)during local experiment evaluation. Previously the callback fired on every
evaluation of the same assignment instead of exactly once.
Problem
ExperimentEvaluator.isExperimentTracked(...)recorded each experiment in thetracker but always returned
false. Because the call site fires the callbackwhen
!isExperimentTracked(...),onTrackre-fired on every evaluation for thesame
(hashAttribute, hashValue, experimentKey, variationId)combination.Fix
isExperimentTracked(...)now returns whether the assignment was alreadytracked (delegates to the existing
alreadyTracked(...)helper), soonTrackfires exactly once per unique assignment.
ExperimentTracker(Guava LRU cache,
maximumSize = 30).