Skip to content

fix: tracking callback invocation - #135

Open
vazarkevych wants to merge 6 commits into
growthbook:mainfrom
vazarkevych:fix-tracking-callback-invocation
Open

fix: tracking callback invocation#135
vazarkevych wants to merge 6 commits into
growthbook:mainfrom
vazarkevych:fix-tracking-callback-invocation

Conversation

@vazarkevych

@vazarkevych vazarkevych commented Apr 3, 2025

Copy link
Copy Markdown
Collaborator

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 the
tracker but always returned false. Because the call site fires the callback
when !isExperimentTracked(...), onTrack re-fired on every evaluation for the
same (hashAttribute, hashValue, experimentKey, variationId) combination.

Fix

  • isExperimentTracked(...) now returns whether the assignment was already
    tracked (delegates to the existing alreadyTracked(...) helper), so onTrack
    fires exactly once per unique assignment.
  • De-duplication is handled solely by the bounded ExperimentTracker
    (Guava LRU cache, maximumSize = 30).

Volodymyr Nazarkevych 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
Comment thread lib/src/main/java/growthbook/sdk/java/multiusermode/configurations/Options.java Outdated
…cker to GlobalContext to make it singleton, remove ExperimentHelper from Options class
@vazarkevych
vazarkevych requested a review from madhuchavva May 9, 2025 11:48
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
vazarkevych force-pushed the fix-tracking-callback-invocation branch from 3068c39 to 35b68ad Compare July 2, 2026 14:25
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.
@vazarkevych vazarkevych changed the title Fix tracking callback invocation fix: tracking callback invocation Jul 2, 2026
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.

2 participants