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

io.rtr.alchemy.caching.CachingContext Maven / Gradle / Ivy

package io.rtr.alchemy.caching;

import io.rtr.alchemy.db.ExperimentsCache;
import io.rtr.alchemy.models.Experiment;

import java.io.Closeable;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * A context object that allows you to interact safely with the cache, preventing multiple calls to
 * invalidate from triggering redundant cache reloads and allowing the user the option to invalidate
 * the cache asynchronously
 */
public class CachingContext implements Closeable {
    private final ExperimentsCache cache;
    private final Experiment.BuilderFactory builderFactory;
    private final AtomicBoolean lock;
    private final ExecutorService executorService;
    private final boolean ownsExecutorService;
    private final ConcurrentMap experimentLocks;

    public CachingContext(
            ExperimentsCache cache,
            Experiment.BuilderFactory builderFactory,
            ExecutorService executorService) {
        this.cache = cache;
        this.builderFactory = builderFactory;
        this.executorService =
                executorService != null ? executorService : Executors.newSingleThreadExecutor();
        this.ownsExecutorService = executorService == null;
        this.experimentLocks = new ConcurrentHashMap<>();
        this.lock = new AtomicBoolean(false);
    }

    public CachingContext(ExperimentsCache cache, Experiment.BuilderFactory builderFactory) {
        this(cache, builderFactory, null);
    }

    /** Forces cache to reload all data from storage */
    public void invalidateAll(boolean async) {
        if (async) {
            executorService.execute(
                    new Runnable() {
                        @Override
                        public void run() {
                            safeInvalidateAll(builderFactory);
                        }
                    });
        } else {
            safeInvalidateAll(builderFactory);
        }
    }

    private void safeInvalidateAll(Experiment.BuilderFactory builderFactory) {
        if (!lock.compareAndSet(false, true)) {
            return;
        }

        try {
            cache.invalidateAll(builderFactory);
        } finally {
            lock.set(false);
        }
    }

    private AtomicBoolean getExperimentLock(String experimentName) {
        final AtomicBoolean newLock = new AtomicBoolean(false);
        final AtomicBoolean prevLock = experimentLocks.putIfAbsent(experimentName, newLock);
        return prevLock != null ? prevLock : newLock;
    }

    /** Forces cache to reload a specific experiment from storage */
    public void invalidate(final String experimentName, boolean async) {
        if (async) {
            executorService.execute(
                    new Runnable() {
                        @Override
                        public void run() {
                            safeInvalidate(
                                    experimentName, builderFactory.createBuilder(experimentName));
                        }
                    });
        } else {
            safeInvalidate(experimentName, builderFactory.createBuilder(experimentName));
        }
    }

    private void safeInvalidate(String experimentName, Experiment.Builder builder) {
        final AtomicBoolean lock = getExperimentLock(experimentName);

        if (!lock.compareAndSet(false, true)) {
            return;
        }

        try {
            cache.invalidate(experimentName, builder);
        } finally {
            lock.set(false);
            experimentLocks.remove(experimentName);
        }
    }

    /** Updates the cache with a newly loaded experiment */
    public void update(Experiment experiment) {
        cache.update(experiment);
    }

    /** Updates the cache with a recently deleted experiment */
    public void delete(String experimentName) {
        cache.delete(experimentName);
    }

    /** Checks whether any experiments are stale */
    public boolean checkIfAnyStale() {
        return cache.checkIfAnyStale();
    }

    /** Checks whether a given experiment is stale */
    public boolean checkIfStale(String experimentName) {
        return cache.checkIfStale(experimentName);
    }

    @Override
    public void close() throws IOException {
        if (ownsExecutorService) {
            executorService.shutdownNow();
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy