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

sirius.kernel.timer.Timers Maven / Gradle / Ivy

/*
 * Made with all the love in the world
 * by scireum in Remshalden, Germany
 *
 * Copyright by scireum GmbH
 * http://www.scireum.de - [email protected]
 */

package sirius.kernel.timer;

import com.google.common.collect.Lists;
import sirius.kernel.Sirius;
import sirius.kernel.Startable;
import sirius.kernel.Stoppable;
import sirius.kernel.async.Orchestration;
import sirius.kernel.async.Tasks;
import sirius.kernel.commons.Explain;
import sirius.kernel.commons.Watch;
import sirius.kernel.di.PartCollection;
import sirius.kernel.di.std.Part;
import sirius.kernel.di.std.Parts;
import sirius.kernel.di.std.Register;
import sirius.kernel.health.Exceptions;
import sirius.kernel.health.Log;
import sirius.kernel.nls.NLS;

import javax.annotation.Nonnull;
import java.io.File;
import java.net.URISyntaxException;
import java.net.URL;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Internal service which is responsible for executing timers.
 * 

* Other than for statistical reasons, this class does not need to be called directly. It automatically * discovers all parts registered for one of the timer interfaces (EveryMinute, EveryTenMinutes, * EveryHour, EveryDay) and invokes them appropriately. *

* To access this class, a Part annotation can be used on a field of type TimerService. */ @Register(classes = {Timers.class, Startable.class, Stoppable.class}) public class Timers implements Startable, Stoppable { @SuppressWarnings("squid:S1192") @Explain("These constants are semantically different.") protected static final Log LOG = Log.get("timer"); private static final String TIMER = "timer"; /** * Contains the config prefix to load settings for daily tasks from. */ public static final String TIMER_DAILY_PREFIX = "timer.daily."; private static final int TEN_SECONDS_IN_MILLIS = 10000; @Part private Tasks tasks; @Part private Orchestration orchestration; @Parts(EveryTenSeconds.class) private PartCollection everyTenSeconds; private long lastTenSecondsExecution = 0; @Parts(EveryMinute.class) private PartCollection everyMinute; private long lastOneMinuteExecution = 0; @Parts(EveryTenMinutes.class) private PartCollection everyTenMinutes; private long lastTenMinutesExecution = 0; @Parts(EveryHour.class) private PartCollection everyHour; private long lastHourExecution = 0; @Parts(EveryDay.class) private PartCollection everyDay; private Timer timer; private ReentrantLock timerLock = new ReentrantLock(); /* * Contains the relative paths of all loaded files */ private List loadedFiles = Lists.newCopyOnWriteArrayList(); /* * Used to frequently check loaded properties when running in DEVELOP mode. */ private Timer reloadTimer; /* * Determines the interval which files are checked for update */ private static final int RELOAD_INTERVAL = 1000; /** * Determines the start and stop order of the timers lifecycle. Exposed as public so that * dependent lifecycles can determine their own priority based on this. */ public static final int LIFECYCLE_PRIORITY = 1000; @Override public int getPriority() { return LIFECYCLE_PRIORITY; } private class InnerTimerTask extends TimerTask { @Override public void run() { try { runTenSecondTimers(); if (TimeUnit.MINUTES.convert(System.currentTimeMillis() - lastOneMinuteExecution, TimeUnit.MILLISECONDS) >= 1) { runOneMinuteTimers(); } if (TimeUnit.MINUTES.convert(System.currentTimeMillis() - lastTenMinutesExecution, TimeUnit.MILLISECONDS) >= 10) { runTenMinuteTimers(); } if (TimeUnit.MINUTES.convert(System.currentTimeMillis() - lastHourExecution, TimeUnit.MILLISECONDS) >= 60) { runOneHourTimers(); runEveryDayTimers(LocalDateTime.now().getHour()); } } catch (Exception t) { Exceptions.handle(LOG, t); } } } /* * Used to monitor a resource for changes */ private static class WatchedResource { private File file; private long lastModified; private Runnable callback; } /** * Returns the timestamp of the last execution of the 10 second timer. * * @return a textual representation of the last execution of the ten seconds timer. Returns "-" if the timer didn't * run yet. */ public String getLastTenSecondsExecution() { if (lastTenSecondsExecution == 0) { return "-"; } return NLS.toUserString(Instant.ofEpochMilli(lastTenSecondsExecution)); } /** * Returns the timestamp of the last execution of the one minute timer. * * @return a textual representation of the last execution of the one minute timer. Returns "-" if the timer didn't * run yet. */ public String getLastOneMinuteExecution() { if (lastOneMinuteExecution == 0) { return "-"; } return NLS.toUserString(Instant.ofEpochMilli(lastOneMinuteExecution)); } /** * Returns the timestamp of the last execution of the ten minutes timer. * * @return a textual representation of the last execution of the ten minutes timer. Returns "-" if the timer didn't * run yet. */ public String getLastTenMinutesExecution() { if (lastTenMinutesExecution == 0) { return "-"; } return NLS.toUserString(Instant.ofEpochMilli(lastTenMinutesExecution)); } /** * Returns the timestamp of the last execution of the one hour timer. * * @return a textual representation of the last execution of the one hour timer. Returns "-" if the timer didn't * run yet. */ public String getLastHourExecution() { if (lastHourExecution == 0) { return "-"; } return NLS.toUserString(Instant.ofEpochMilli(lastHourExecution)); } @Override public void started() { if (Sirius.isFrameworkEnabled("kernel.timer")) { startTimer(); } if (Sirius.isDev()) { startResourceWatcher(); } } private void startResourceWatcher() { if (reloadTimer == null) { reloadTimer = new Timer(true); reloadTimer.schedule(new TimerTask() { @Override public void run() { watchLoadedResources(); } }, RELOAD_INTERVAL, RELOAD_INTERVAL); } } private void watchLoadedResources() { Thread.currentThread().setName("Resource-Watch"); for (WatchedResource res : loadedFiles) { long lastModified = res.file.lastModified(); if (lastModified > res.lastModified) { res.lastModified = res.file.lastModified(); LOG.INFO("Reloading: %s", res.file.toString()); try { res.callback.run(); } catch (Exception e) { Exceptions.handle() .withSystemErrorMessage("Error reloading %s: %s (%s)", res.file.toString()) .error(e) .handle(); } } } } private void startTimer() { try { timerLock.lock(); try { if (timer == null) { timer = new Timer(true); } else { timer.cancel(); timer = new Timer(true); } timer.schedule(new InnerTimerTask(), TEN_SECONDS_IN_MILLIS, TEN_SECONDS_IN_MILLIS); } finally { timerLock.unlock(); } } catch (Exception t) { Exceptions.handle(LOG, t); } } @Override public void stopped() { try { timerLock.lock(); try { if (timer != null) { timer.cancel(); } } finally { timerLock.unlock(); } } catch (Exception t) { Exceptions.handle(LOG, t); } } /** * Adds the given file to the list of watched resources in DEVELOP mode ({@link Sirius#isDev()}. *

* This is used to reload files like properties in development environments. In production systems, no * reloading will be performed. * * @param url the file to watch * @param callback the callback to invoke once the file has changed */ @SuppressWarnings("squid:S2250") @Explain("Resources are only collected once at startup, so there is no performance hotspot") public void addWatchedResource(@Nonnull URL url, @Nonnull Runnable callback) { try { WatchedResource res = new WatchedResource(); File file = new File(url.toURI()); res.file = file; res.callback = callback; res.lastModified = file.lastModified(); loadedFiles.add(res); } catch (IllegalArgumentException | URISyntaxException e) { Exceptions.ignore(e); Exceptions.handle() .withSystemErrorMessage("Cannot monitor URL '%s' for changes: %s (%s)", url) .to(LOG) .handle(); } } /** * Executes all one minute timers (implementing EveryTenSeconds) now (out of schedule). */ public void runTenSecondTimers() { for (final TimedTask task : everyTenSeconds.getParts()) { executeTask(task); } lastTenSecondsExecution = System.currentTimeMillis(); } /** * Executes all one minute timers (implementing EveryMinute) now (out of schedule). */ public void runOneMinuteTimers() { for (final TimedTask task : everyMinute.getParts()) { executeTask(task); } lastOneMinuteExecution = System.currentTimeMillis(); } private void executeTask(final TimedTask task) { tasks.executor(TIMER) .dropOnOverload(() -> Exceptions.handle() .to(LOG) .withSystemErrorMessage( "Dropping timer task '%s' (%s) due to system overload!", task, task.getClass()) .handle()) .start(() -> { try { Watch w = Watch.start(); task.runTimer(); if (w.elapsed(TimeUnit.SECONDS, false) > 1) { LOG.WARN("TimedTask '%s' (%s) took over a second to complete! " + "Consider executing the work in a separate executor!", task, task.getClass()); } } catch (Exception t) { Exceptions.handle(LOG, t); } }); } /** * Executes all ten minutes timers (implementing EveryTenMinutes) now (out of schedule). */ public void runTenMinuteTimers() { for (final TimedTask task : everyTenMinutes.getParts()) { executeTask(task); } lastTenMinutesExecution = System.currentTimeMillis(); } /** * Executes all one hour timers (implementing EveryHour) now (out of schedule). */ public void runOneHourTimers() { for (final TimedTask task : everyHour.getParts()) { executeTask(task); } lastHourExecution = System.currentTimeMillis(); } /** * Executes all daily timers (implementing EveryDay) if applicable, or if outOfASchedule is true. * * @param currentHour determines the current hour. Most probably this will be wall-clock time. However, for * out-of-schedule eecution, this can be set to any value. */ public void runEveryDayTimers(int currentHour) { for (final EveryDay task : getDailyTasks()) { runDailyTimer(currentHour, task); } } /** * Returns all known daily tasks. * * @return a collection of all known daily tasks */ public Collection getDailyTasks() { return Collections.unmodifiableCollection(everyDay.getParts()); } /** * Executes the given task if it is scheduled for the given hour. * * @param currentHour the hour to pretend * @param task the task to execute */ public void runDailyTimer(int currentHour, EveryDay task) { Optional executionHour = getExecutionHour(task); if (!executionHour.isPresent()) { LOG.WARN("Skipping daily timer %s as config key '%s' is missing!", task.getClass().getName(), TIMER_DAILY_PREFIX + task.getConfigKeyName()); return; } if (executionHour.get() != currentHour) { return; } if (orchestration != null && !orchestration.shouldRunDailyTask(task.getConfigKeyName())) { return; } executeTask(task); } /** * Determines the execution hour (0..23) in which the given task is to be executed. * * @param task the task to check * @return the execution hour wrapped as optional or an empty optional if the config is missing */ public Optional getExecutionHour(EveryDay task) { String configPath = TIMER_DAILY_PREFIX + task.getConfigKeyName(); if (!Sirius.getSettings().getConfig().hasPath(configPath)) { return Optional.empty(); } return Optional.of(Sirius.getSettings().getInt(configPath)); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy