/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.activemq.artemis.core.server; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.concurrent.Executor; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.apache.activemq.artemis.utils.ActiveMQThreadFactory; import org.jboss.logging.Logger; /** * This is for components with a scheduled at a fixed rate. */ public abstract class ActiveMQScheduledComponent implements ActiveMQComponent, Runnable { private static final Logger logger = Logger.getLogger(ActiveMQScheduledComponent.class); private ScheduledExecutorService scheduledExecutorService; private boolean startedOwnScheduler; private long period; private long millisecondsPeriod; private TimeUnit timeUnit; private final Executor executor; private ScheduledFuture future; private final boolean onDemand; long lastTime = 0; private final AtomicInteger delayed = new AtomicInteger(0); public ActiveMQScheduledComponent(ScheduledExecutorService scheduledExecutorService, Executor executor, long checkPeriod, TimeUnit timeUnit, boolean onDemand) { this.executor = executor; this.scheduledExecutorService = scheduledExecutorService; this.period = checkPeriod; this.timeUnit = timeUnit; this.onDemand = onDemand; } /** * This is useful for cases where we want our own scheduler executor. * * @param checkPeriod * @param timeUnit * @param onDemand */ public ActiveMQScheduledComponent(long checkPeriod, TimeUnit timeUnit, boolean onDemand) { this(null, null, checkPeriod, timeUnit, onDemand); } @Override public synchronized void start() { if (future != null) { // already started return; } if (scheduledExecutorService == null) { scheduledExecutorService = new ScheduledThreadPoolExecutor(1, getThreadFactory()); startedOwnScheduler = true; } if (onDemand) { return; } this.millisecondsPeriod = timeUnit.convert(period, TimeUnit.MILLISECONDS); if (period >= 0) { future = scheduledExecutorService.scheduleWithFixedDelay(runForScheduler, period, period, timeUnit); } else { logger.tracef("did not start scheduled executor on %s because period was configured as %d", this, period); } } protected ActiveMQThreadFactory getThreadFactory() { return new ActiveMQThreadFactory(this.getClass().getSimpleName() + "-scheduled-threads", false, getThisClassLoader()); } private ClassLoader getThisClassLoader() { return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() { @Override public ClassLoader run() { return ActiveMQScheduledComponent.this.getClass().getClassLoader(); } }); } public void delay() { int value = delayed.incrementAndGet(); if (value > 10) { delayed.decrementAndGet(); } else { // We only schedule up to 10 periods upfront. // this is to avoid a window where a current one would be running and a next one is coming. // in theory just 2 would be enough. I'm using 10 as a precaution here. scheduledExecutorService.schedule(runForScheduler, Math.min(period, period * value), timeUnit); } } public long getPeriod() { return period; } public synchronized ActiveMQScheduledComponent setPeriod(long period) { this.period = period; restartIfNeeded(); return this; } public TimeUnit getTimeUnit() { return timeUnit; } public synchronized ActiveMQScheduledComponent setTimeUnit(TimeUnit timeUnit) { this.timeUnit = timeUnit; restartIfNeeded(); return this; } @Override public synchronized void stop() { if (future != null) { future.cancel(false); future = null; } if (startedOwnScheduler) { this.scheduledExecutorService.shutdownNow(); scheduledExecutorService = null; startedOwnScheduler = false; } } @Override public synchronized boolean isStarted() { return future != null; } // this will restart the scheduled component upon changes private void restartIfNeeded() { if (isStarted()) { stop(); start(); } } final Runnable runForExecutor = new Runnable() { @Override public void run() { if (onDemand && delayed.get() > 0) { delayed.decrementAndGet(); } if (!onDemand && lastTime > 0) { if (System.currentTimeMillis() - lastTime < millisecondsPeriod) { logger.trace("Execution ignored due to too many simultaneous executions, probably a previous delayed execution"); return; } } lastTime = System.currentTimeMillis(); ActiveMQScheduledComponent.this.run(); } }; final Runnable runForScheduler = new Runnable() { @Override public void run() { if (executor != null) { executor.execute(runForExecutor); } else { runForExecutor.run(); } } }; }