All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.split.engine.experiments.SplitFetcherImp Maven / Gradle / Ivy

The newest version!
package io.split.engine.experiments;

import io.split.client.dtos.SplitChange;
import io.split.client.exceptions.UriTooLongException;
import io.split.client.interceptors.FlagSetsFilter;
import io.split.client.utils.FeatureFlagsToUpdate;
import io.split.client.utils.RuleBasedSegmentsToUpdate;
import io.split.storages.RuleBasedSegmentCacheProducer;
import io.split.storages.SplitCacheProducer;
import io.split.telemetry.domain.enums.LastSynchronizationRecordsEnum;
import io.split.telemetry.storage.TelemetryRuntimeProducer;
import io.split.engine.common.FetchOptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashSet;
import java.util.Set;

import static com.google.common.base.Preconditions.checkNotNull;
import static io.split.client.utils.FeatureFlagProcessor.processFeatureFlagChanges;
import static io.split.client.utils.RuleBasedSegmentProcessor.processRuleBasedSegmentChanges;
import static io.split.client.utils.Utils.checkExitConditions;

/**
 * An ExperimentFetcher that refreshes experiment definitions periodically.
 *
 * @author adil
 */
public class SplitFetcherImp implements SplitFetcher {

    private static final Logger _log = LoggerFactory.getLogger(SplitFetcherImp.class);

    private final SplitParser _parser;
    private final SplitChangeFetcher _splitChangeFetcher;
    private final SplitCacheProducer _splitCacheProducer;
    private final Object _lock = new Object();
    private final TelemetryRuntimeProducer _telemetryRuntimeProducer;
    private final FlagSetsFilter _flagSetsFilter;
    private final RuleBasedSegmentCacheProducer _ruleBasedSegmentCacheProducer;
    private final RuleBasedSegmentParser _parserRBS;

    /**
     * Contains all the traffic types that are currently being used by the splits and also the count
     *
     * For example if there are three splits, one of traffic type "account" and two of traffic type "user",
     * this multiset will contain [{"user", 2}, {"account", 1}]
     *
     * The count is used to maintain how many splits are using a traffic type, so when
     * an ARCHIVED split is received, we know if we need to remove a traffic type from the multiset.
     */

    public SplitFetcherImp(SplitChangeFetcher splitChangeFetcher, SplitParser parser, SplitCacheProducer splitCacheProducer,
                           TelemetryRuntimeProducer telemetryRuntimeProducer, FlagSetsFilter flagSetsFilter,
                           RuleBasedSegmentParser parserRBS, RuleBasedSegmentCacheProducer ruleBasedSegmentCacheProducer) {
        _splitChangeFetcher = checkNotNull(splitChangeFetcher);
        _parser = checkNotNull(parser);
        _parserRBS = checkNotNull(parserRBS);
        _splitCacheProducer = checkNotNull(splitCacheProducer);
        _ruleBasedSegmentCacheProducer = checkNotNull(ruleBasedSegmentCacheProducer);
        _telemetryRuntimeProducer = checkNotNull(telemetryRuntimeProducer);
        _flagSetsFilter = flagSetsFilter;
    }

    @Override
    public FetchResult forceRefresh(FetchOptions options) {
        _log.debug("Force Refresh feature flags starting ...");
        final long INITIAL_CN = _splitCacheProducer.getChangeNumber();
        final long RBS_INITIAL_CN = _ruleBasedSegmentCacheProducer.getChangeNumber();
        Set segments = new HashSet<>();
        try {
            while (true) {
                long start = _splitCacheProducer.getChangeNumber();
                long startRBS = _ruleBasedSegmentCacheProducer.getChangeNumber();
                segments.addAll(runWithoutExceptionHandling(options));
                long end = _splitCacheProducer.getChangeNumber();
                long endRBS = _ruleBasedSegmentCacheProducer.getChangeNumber();

                // If the previous execution was the first one, clear the `cdnBypass` flag
                // for the next fetches. (This will clear a local copy of the fetch options,
                // not the original object that was passed to this method).
                FetchOptions.Builder optionsBuilder = new FetchOptions.Builder(options);
                if (INITIAL_CN == start) {
                    optionsBuilder.targetChangeNumber(FetchOptions.DEFAULT_TARGET_CHANGENUMBER);
                }

                if (RBS_INITIAL_CN == startRBS) {
                    optionsBuilder.targetChangeNumberRBS(FetchOptions.DEFAULT_TARGET_CHANGENUMBER);
                }

                options = optionsBuilder.build();

                if (start >= end && startRBS >= endRBS) {
                    return new FetchResult(true, false, segments);
                }
            }
        } catch (UriTooLongException u) {
            return new FetchResult(false, false, new HashSet<>());
        } catch (InterruptedException e) {
            _log.warn("Interrupting split fetcher task");
            Thread.currentThread().interrupt();
            return new FetchResult(false, true, new HashSet<>());
        } catch (Exception e) {
            _log.error("RefreshableSplitFetcher failed: " + e.getMessage());
            if (_log.isDebugEnabled()) {
                _log.debug("Reason:", e);
            }
            return new FetchResult(false, true, new HashSet<>());
        }
    }

    @Override
    public void run() {
        this.forceRefresh(new FetchOptions.Builder().cacheControlHeaders(false).build());
    }

    private Set runWithoutExceptionHandling(FetchOptions options) throws InterruptedException, UriTooLongException {
        SplitChange change = _splitChangeFetcher.fetch(_splitCacheProducer.getChangeNumber(),
                _ruleBasedSegmentCacheProducer.getChangeNumber(), options);
        Set segments = new HashSet<>();

        if (change == null) {
            throw new IllegalStateException("SplitChange was null");
        }

        if (change.clearCache) {
            _splitCacheProducer.clear();
            _ruleBasedSegmentCacheProducer.clear();
        }

        if (checkExitConditions(change.featureFlags, _splitCacheProducer.getChangeNumber()) ||
            checkExitConditions(change.ruleBasedSegments, _ruleBasedSegmentCacheProducer.getChangeNumber())) {
            return segments;
        }

        if (change.featureFlags.d.isEmpty()) {
            _splitCacheProducer.setChangeNumber(change.featureFlags.t);
        }

        if (change.ruleBasedSegments.d.isEmpty()) {
            _ruleBasedSegmentCacheProducer.setChangeNumber(change.ruleBasedSegments.t);
        }
        
        if (change.featureFlags.d.isEmpty() && change.ruleBasedSegments.d.isEmpty()) {
            return segments;
        }


        synchronized (_lock) {
            // check state one more time.
            if (checkExitConditions(change.featureFlags, _splitCacheProducer.getChangeNumber()) ||
                    checkExitConditions(change.ruleBasedSegments, _ruleBasedSegmentCacheProducer.getChangeNumber())) {
                // some other thread may have updated the shared state. exit
                return segments;
            }
            FeatureFlagsToUpdate featureFlagsToUpdate = processFeatureFlagChanges(_parser, change.featureFlags.d, _flagSetsFilter);
            segments = featureFlagsToUpdate.getSegments();
            _splitCacheProducer.update(featureFlagsToUpdate.getToAdd(), featureFlagsToUpdate.getToRemove(), change.featureFlags.t);

            RuleBasedSegmentsToUpdate ruleBasedSegmentsToUpdate = processRuleBasedSegmentChanges(_parserRBS,
                    change.ruleBasedSegments.d);
            segments.addAll(ruleBasedSegmentsToUpdate.getSegments());
            _ruleBasedSegmentCacheProducer.update(ruleBasedSegmentsToUpdate.getToAdd(),
                    ruleBasedSegmentsToUpdate.getToRemove(), change.ruleBasedSegments.t);
            _telemetryRuntimeProducer.recordSuccessfulSync(LastSynchronizationRecordsEnum.SPLITS, System.currentTimeMillis());
        }

        return segments;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy