001package org.javasimon;
002
003import org.slf4j.Logger;
004import org.slf4j.LoggerFactory;
005
006import java.util.concurrent.*;
007
008/**
009 * This class implements periodical removing of old incremental Simons for specified Manager.
010 * Unused incremental samples can cause memory leaks and performance degradation and should be purged regularly.
011 *
012 * Purger can be in one of two states: stopped or started. When an instance of the class is create it is in the
013 * stopped stated. Method {@link IncrementalSimonsPurger#start(long, java.util.concurrent.TimeUnit)}
014 * changes state of the class to started. In this state periodical Simons purging is performed. To stop
015 * purging one need to call {@link IncrementalSimonsPurger#cancel()}. This method will change current state of the object
016 * to stopped. All other state transitions except from stopped to started or from started to stopped are forbidden.
017 *
018 * This class is thread safe.
019 *
020 * Here is a code example of how to configure {@link org.javasimon.IncrementalSimonsPurger} to clean all outdated incremental
021 * Simons once a day for default manager:
022 * <pre>
023 * {@code
024 * Manager manager = SimonManager.manager();
025 * IncrementalSimonsPurger incrementalSimonsPurger = new IncrementalSimonsPurger(manager);
026 * incrementalSimonsPurger.start(1, TimeUnit.DAYS);
027 * }
028 * </pre>
029 *
030 * @author <a href="mailto:ivan.mushketyk@gmail.com">Ivan Mushketyk</a>
031 */
032public final class IncrementalSimonsPurger {
033
034        /** Manager where old incremental Simons will be purged */
035        private final Manager manager;
036
037        /** Scheduled executor service that should periodically execute purging task */
038        private final ScheduledExecutorService executorService;
039
040        /** Currently started purging task */
041        private ScheduledFuture<?> scheduledFuture;
042
043        /**
044         * Create Simons purger for the specified Manager.
045         *
046         * @param manager manager for which old incremental Simons will be purged
047         */
048        public IncrementalSimonsPurger(Manager manager) {
049                this(manager, createExecutorService());
050        }
051
052        IncrementalSimonsPurger(Manager manager, ScheduledExecutorService executorService) {
053                this.manager = manager;
054                this.executorService = executorService;
055        }
056
057        private static ScheduledExecutorService createExecutorService() {
058                ThreadFactory threadFactory = new DaemonThreadFactory();
059                return Executors.newSingleThreadScheduledExecutor(threadFactory);
060        }
061
062        /**
063         * Start periodical Simons purging with the specified period.
064         *
065         * @param period duration of purging period
066         * @param timeUnit time unit of period duration
067         */
068        public synchronized void start(long period, TimeUnit timeUnit) {
069                if (scheduledFuture == null) {
070                        PurgerRunnable runnable = new PurgerRunnable(manager);
071                        scheduledFuture = executorService.scheduleWithFixedDelay(runnable, period, period, timeUnit);
072                } else {
073                        throw new IllegalStateException("IncrementSimonPurger has already been started");
074                }
075        }
076
077        /**
078         * Cancel periodical Simons purging if it was started.
079         */
080        public synchronized void cancel() {
081                if (scheduledFuture != null) {
082                        scheduledFuture.cancel(false);
083                        scheduledFuture = null;
084                } else {
085                        throw new IllegalStateException("IncrementSimonPurger is either cancelled or was not started");
086                }
087        }
088
089        /**
090         * Task submitted to scheduled executor. It is periodically executed to remove
091         * old incremental Simons.
092         */
093        static class PurgerRunnable implements Runnable {
094
095                private static final Logger logger = LoggerFactory.getLogger(PurgerRunnable.class);
096
097                private Manager manager;
098                private long periodStartMs;
099
100                public PurgerRunnable(Manager manager) {
101                        this(manager, manager.milliTime());
102                }
103
104                PurgerRunnable(Manager manager, long periodStartMs) {
105                        this.manager = manager;
106                        this.periodStartMs = periodStartMs;
107                }
108
109                @Override
110                public void run() {
111                        logger.debug("Purging old incremental Simons");
112                        if (manager instanceof EnabledManager) {
113                                ((EnabledManager) manager).purgeIncrementalSimonsOlderThan(periodStartMs);
114                        } else if (manager instanceof SwitchingManager) {
115                                ((SwitchingManager) manager).purgeIncrementalSimonsOlderThan(periodStartMs);
116                        }
117                        periodStartMs = manager.milliTime();
118                }
119
120                Manager getManager() {
121                        return manager;
122                }
123        }
124
125        /**
126         * Thread factory that creates daemon thread for each Runnable. Using this factory
127         * for executor service will not prevent application stopping.
128         */
129        static class DaemonThreadFactory implements ThreadFactory {
130
131                private int threadNumber;
132
133                @Override
134                public synchronized Thread newThread(Runnable runnable) {
135                        Thread daemonThread = new Thread(runnable);
136                        daemonThread.setDaemon(true);
137                        daemonThread.setName("javasimon-simonsPurger-" + (++threadNumber));
138                        return daemonThread;
139                }
140        }
141}