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

io.split.engine.segments.SegmentFetcherImp Maven / Gradle / Ivy

package io.split.engine.segments;

import com.google.common.annotations.VisibleForTesting;
import io.split.client.dtos.SegmentChange;
import io.split.engine.SDKReadinessGates;
import io.split.storages.SegmentCacheProducer;
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.ArrayList;
import java.util.List;

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

public class SegmentFetcherImp implements SegmentFetcher {
    private static final Logger _log = LoggerFactory.getLogger(SegmentFetcherImp.class);

    private final String _segmentName;
    private final SegmentChangeFetcher _segmentChangeFetcher;
    private final SegmentCacheProducer _segmentCacheProducer;
    private final SDKReadinessGates _gates;
    private final TelemetryRuntimeProducer _telemetryRuntimeProducer;

    private final Object _lock = new Object();

    public SegmentFetcherImp(String segmentName, SegmentChangeFetcher segmentChangeFetcher, SDKReadinessGates gates, SegmentCacheProducer segmentCacheProducer, TelemetryRuntimeProducer telemetryRuntimeProducer) {
        _segmentName = checkNotNull(segmentName);
        _segmentChangeFetcher = checkNotNull(segmentChangeFetcher);
        _segmentCacheProducer = checkNotNull(segmentCacheProducer);
        _gates = checkNotNull(gates);
        _telemetryRuntimeProducer = checkNotNull(telemetryRuntimeProducer);

        _segmentCacheProducer.updateSegment(segmentName, new ArrayList<>(), new ArrayList<>(), -1L);
    }

    @Override
    public void fetch(FetchOptions opts){
        try {
            fetchUntil(opts);
        } catch (Throwable t) {
            _log.error("RefreshableSegmentFetcher failed: " + t.getMessage());
            if (_log.isDebugEnabled()) {
                _log.debug("Reason:", t);
            }
        }
    }

    private void runWithoutExceptionHandling(FetchOptions options) {
        SegmentChange change = _segmentChangeFetcher.fetch(_segmentName, _segmentCacheProducer.getChangeNumber(_segmentName), options);

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

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

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


        if (change.added.isEmpty() && change.removed.isEmpty()) {
            // there are no changes. weird!
            _segmentCacheProducer.setChangeNumber(_segmentName,change.till);
            return;
        }

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

            if (!change.added.isEmpty()) {
                _log.info(_segmentName + " added keys: " + summarize(change.added));
            }

            if (!change.removed.isEmpty()) {
                _log.info(_segmentName + " removed keys: " + summarize(change.removed));
            }

            _telemetryRuntimeProducer.recordSuccessfulSync(LastSynchronizationRecordsEnum.SEGMENTS, System.currentTimeMillis());
        }
    }

    private String summarize(List changes) {
        StringBuilder bldr = new StringBuilder();
        bldr.append("[");
        for (int i = 0; i < Math.min(3, changes.size()); i++) {
            if (i != 0) {
                bldr.append(", ");
            }
            bldr.append(changes.get(i));
        }

        if (changes.size() > 3) {
            bldr.append("... ");
            bldr.append((changes.size() - 3));
            bldr.append(" others");
        }
        bldr.append("]");

        return bldr.toString();
    }

    @VisibleForTesting
    void fetchUntil(FetchOptions opts){
        final long INITIAL_CN = _segmentCacheProducer.getChangeNumber(_segmentName);
        while (true) {
            long start = _segmentCacheProducer.getChangeNumber(_segmentName);
            runWithoutExceptionHandling(opts);
            if (INITIAL_CN == start) {
                opts = new FetchOptions.Builder(opts).targetChangeNumber(FetchOptions.DEFAULT_TARGET_CHANGENUMBER).build();
            }
            long end = _segmentCacheProducer.getChangeNumber(_segmentName);
            if (start >= end) {
                break;
            }
        }
    }

    @Override
    public boolean runWhitCacheHeader(){
       return this.fetchAndUpdate(new FetchOptions.Builder().cacheControlHeaders(true).build());
    }

    /**
     * Calls callLoopRun and after fetchs segment.
     * @param opts contains all soft of options used when issuing the fetch request
     */
    @VisibleForTesting
    boolean fetchAndUpdate(FetchOptions opts) {
        try {
            // Do this again in case the previous call errored out.
            fetchUntil(opts);
            return true;

        } catch (Throwable t) {
            _log.error("RefreshableSegmentFetcher failed: " + t.getMessage());
            if (_log.isDebugEnabled()) {
                _log.debug("Reason:", t);
            }
            return false;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy