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

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

There is a newer version: 4.13.0
Show newest version
package io.split.engine.experiments;

import io.split.client.dtos.Split;
import io.split.client.dtos.SplitChange;
import io.split.client.dtos.Status;
import io.split.engine.SDKReadinessGates;
import io.split.cache.SplitCache;
import io.split.engine.common.FetchOptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * 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 SplitCache _splitCache;
    private final SDKReadinessGates _gates;
    private final Object _lock = new Object();

    /**
     * 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, SDKReadinessGates gates, SplitCache splitCache) {
        _splitChangeFetcher = checkNotNull(splitChangeFetcher);
        _parser = checkNotNull(parser);
        _gates = checkNotNull(gates);
        _splitCache = checkNotNull(splitCache);
    }

    @Override
    public void forceRefresh(FetchOptions options) {
        _log.debug("Force Refresh splits starting ...");
        try {
            while (true) {
                long start = _splitCache.getChangeNumber();
                runWithoutExceptionHandling(options);
                long end = _splitCache.getChangeNumber();

                if (start >= end) {
                    break;
                }
            }
        } catch (InterruptedException e) {
            _log.warn("Interrupting split fetcher task");
            Thread.currentThread().interrupt();
        } catch (Throwable t) {
            _log.error("RefreshableSplitFetcher failed: " + t.getMessage());
        }
    }

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

    private void runWithoutExceptionHandling(FetchOptions options) throws InterruptedException {
        SplitChange change = _splitChangeFetcher.fetch(_splitCache.getChangeNumber(), options);

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

        if (change.till == _splitCache.getChangeNumber()) {
            // no change.
            return;
        }

        if (change.since != _splitCache.getChangeNumber() || change.till < _splitCache.getChangeNumber()) {
            // some other thread may have updated the shared state. exit
            return;
        }

        if (change.splits.isEmpty()) {
            // there are no changes. weird!
            _splitCache.setChangeNumber(change.till);
            return;
        }

        synchronized (_lock) {
            // check state one more time.
            if (change.since != _splitCache.getChangeNumber()
                    || change.till < _splitCache.getChangeNumber()) {
                // some other thread may have updated the shared state. exit
                return;
            }

            for (Split split : change.splits) {
                if (Thread.currentThread().isInterrupted()) {
                    throw new InterruptedException();
                }

                if (split.status != Status.ACTIVE) {
                    // archive.
                    _splitCache.remove(split.name);
                    continue;
                }

                ParsedSplit parsedSplit = _parser.parse(split);
                if (parsedSplit == null) {
                    _log.info("We could not parse the experiment definition for: " + split.name + " so we are removing it completely to be careful");

                    _splitCache.remove(split.name);
                    _log.debug("Deleted feature: " + split.name);

                    continue;
                }

                // If the split already exists, this is either an update, or the split has been
                // deleted and recreated (possibly with a different traffic type).
                // If it's an update, the traffic type should NOT be increased.
                // If it's deleted & recreated, the old one should be decreased and the new one increased.
                // To handle both cases, we simply delete the old one if the split is present.
                // The new one is always increased.
                ParsedSplit current = _splitCache.get(split.name);
                if (current != null) {
                    _splitCache.remove(split.name);
                }

                _splitCache.put(parsedSplit);
                _log.debug("Updated feature: " + parsedSplit.feature());
            }

            _splitCache.setChangeNumber(change.till);
        }
    }
    @Override
    public void fetchAll(FetchOptions options) {
        _log.debug("Fetch splits starting ...");
        long start = _splitCache.getChangeNumber();
        try {
            runWithoutExceptionHandling(options);
            _gates.splitsAreReady();
        } catch (InterruptedException e) {
            _log.warn("Interrupting split fetcher task");
            Thread.currentThread().interrupt();
        } catch (Throwable t) {
            _log.error("RefreshableSplitFetcher failed: " + t.getMessage());
            if (_log.isDebugEnabled()) {
                _log.debug("Reason:", t);
            }
        } finally {
            if (_log.isDebugEnabled()) {
                _log.debug("split fetch before: " + start + ", after: " + _splitCache.getChangeNumber());
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy