/* * 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.geode.internal; import java.lang.ref.WeakReference; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Timer; import java.util.TimerTask; import org.apache.logging.log4j.Logger; import org.apache.geode.CancelException; import org.apache.geode.SystemFailure; import org.apache.geode.distributed.internal.InternalDistributedSystem; import org.apache.geode.internal.i18n.LocalizedStrings; import org.apache.geode.internal.logging.LogService; import org.apache.geode.internal.logging.log4j.LocalizedMessage; /** * Instances of this class are like {@link Timer}, but are associated with a "swarm", which can be * cancelled as a group with {@link #cancelSwarm(Object)}. * * @see Timer * @see TimerTask * * TODO -- with Java 1.5, this will be a template type so that the swarm's class can be * specified. */ public final class SystemTimer { private static final Logger logger = LogService.getLogger(); final static private boolean isIBM = "IBM Corporation".equals(System.getProperty("java.vm.vendor")); /** * Extra debugging for this class */ // private static final boolean DEBUG = true; static final boolean DEBUG = false; /** * the underlying {@link Timer} */ final private Timer timer; /** * True if this timer has been cancelled */ private boolean cancelled = false; /** * the swarm to which this timer belongs */ final private Object /* T */ swarm; @Override public String toString() { StringBuffer sb = new StringBuffer(); sb.append("SystemTimer["); sb.append("swarm = " + swarm); // sb.append("; timer = " + timer); sb.append("]"); return sb.toString(); } /** * List of all of the swarms in the system * * @guarded.By self */ // <T, HashMap<Object, ArrayList<WeakReference<SystemTimer>>>> static private final HashMap allSwarms = new HashMap(); /** * Add the given timer is in the given swarm. Used only by constructors. * * @param swarm swarm to add the timer to * @param t timer to add */ static private void addToSwarm(Object /* T */ swarm, SystemTimer t) { final boolean isDebugEnabled = logger.isTraceEnabled(); // Get or add list of timers for this swarm... ArrayList /* ArrayList<WeakReference<SystemTimer>> */ swarmSet; synchronized (allSwarms) { swarmSet = (ArrayList) allSwarms.get(swarm); if (swarmSet == null) { if (isDebugEnabled) { logger.trace("SystemTimer#addToSwarm: created swarm {}", swarm); } swarmSet = new ArrayList(); allSwarms.put(swarm, swarmSet); } } // synchronized // Add the timer to the swarm's list if (isDebugEnabled) { logger.trace("SystemTimer#addToSwarm: adding timer <{}>", t); } WeakReference /* WeakReference<SystemTimer> */ wr = new WeakReference(t); synchronized (swarmSet) { swarmSet.add(wr); } // synchronized } /** * time that the last sweep was done * * @see #sweepAllSwarms */ private static long lastSweepAllTime = 0; /** * Interval, in milliseconds, to sweep all swarms, measured from when the last sweep finished * * @see #sweepAllSwarms */ private static final long SWEEP_ALL_INTERVAL = 2 * 60 * 1000; // 2 minutes /** * Manually garbage collect {@link #allSwarms}, if it hasn't happened in a while. * * @see #lastSweepAllTime */ static private void sweepAllSwarms() { if (System.currentTimeMillis() < lastSweepAllTime + SWEEP_ALL_INTERVAL) { // Too soon. return; } final boolean isDebugEnabled = logger.isTraceEnabled(); synchronized (allSwarms) { Iterator it = allSwarms.entrySet().iterator(); while (it.hasNext()) { // iterate over allSwarms Map.Entry entry = (Map.Entry) it.next(); ArrayList swarm = (ArrayList) entry.getValue(); synchronized (swarm) { Iterator it2 = swarm.iterator(); while (it2.hasNext()) { // iterate over current swarm WeakReference wr = (WeakReference) it2.next(); SystemTimer st = (SystemTimer) wr.get(); if (st == null) { // Remove stale reference it2.remove(); continue; } // Get rid of a cancelled timer; it's not interesting. if (st.cancelled) { it2.remove(); continue; } } // iterate over current swarm if (swarm.size() == 0) { // Remove unused swarm it.remove(); if (isDebugEnabled) { logger.trace("SystemTimer#sweepAllSwarms: removed unused swarm {}", entry.getKey()); } } // Remove unused swarm } // synchronized swarm } // iterate over allSwarms } // synchronized allSwarms // Collect time at END of sweep. It means an extra call to the system // timer, but makes this potentially less active. lastSweepAllTime = System.currentTimeMillis(); } /** * Remove given timer from the swarm. * * @param t timer to remove * * @see #cancel() */ static private void removeFromSwarm(SystemTimer t) { final boolean isDebugEnabled = logger.isTraceEnabled(); synchronized (allSwarms) { // Get timer's swarm ArrayList swarmSet = (ArrayList) allSwarms.get(t.swarm); if (swarmSet == null) { if (isDebugEnabled) { logger.trace("SystemTimer#removeFromSwarm: timer already removed: {}", t); } return; // already gone } // Remove timer from swarm if (isDebugEnabled) { logger.trace("SystemTimer#removeFromSwarm: removing timer <{}>", t); } synchronized (swarmSet) { Iterator it = swarmSet.iterator(); while (it.hasNext()) { WeakReference ref = (WeakReference) it.next(); SystemTimer t2 = (SystemTimer) ref.get(); if (t2 == null) { // Since we've discovered an empty reference, we should remove it. it.remove(); continue; } if (t2 == t) { it.remove(); // Don't keep sweeping once we've found it; just quit. break; } if (t2.cancelled) { // But if we happen to run across a cancelled timer, // remove it. it.remove(); continue; } } // while // While we're here, if the swarm has gone to zero size, // we should remove it. if (swarmSet.size() == 0) { allSwarms.remove(t.swarm); // last reference if (isDebugEnabled) { logger.trace("SystemTimer#removeFromSwarm: removed last reference to {}", t.swarm); } } } // synchronized swarmSet } // synchronized allSwarms sweepAllSwarms(); // Occasionally check global list, use any available logger :-) } /** * Cancel all outstanding timers * * @param swarm the swarm to cancel */ public static void cancelSwarm(Object /* T */ swarm) { Assert.assertTrue(swarm instanceof InternalDistributedSystem); // TODO // Find the swarmSet and remove it ArrayList swarmSet; synchronized (allSwarms) { swarmSet = (ArrayList) allSwarms.get(swarm); if (swarmSet == null) { return; // already cancelled } // Remove before releasing synchronization, so any fresh timer ends up // in a new set with same key allSwarms.remove(swarmSet); } // synchronized // Empty the swarmSet synchronized (swarmSet) { Iterator it = swarmSet.iterator(); while (it.hasNext()) { WeakReference wr = (WeakReference) it.next(); SystemTimer st = (SystemTimer) wr.get(); // it.remove(); Not necessary, we're emptying the list... if (st != null) { st.cancelled = true; // for safety :-) st.timer.cancel(); // st.cancel() would just search for it again } } // while } // synchronized } public int timerPurge() { if (logger.isTraceEnabled()) { logger.trace("SystemTimer#timerPurge of {}", this); } // Fix 39585, IBM's java.util.timer's purge() has stack overflow issue if (isIBM) { return 0; } return this.timer.purge(); } // This creates a non-daemon timer thread. We don't EVER do this... // /** // * @see Timer#Timer() // * // * @param swarm the swarm this timer belongs to // */ // public SystemTimer(DistributedSystem swarm) { // this.timer = new Timer(); // this.swarm = swarm; // addToSwarm(swarm, this); // } /** * @see Timer#Timer(boolean) * @param swarm the swarm this timer belongs to, currently must be a DistributedSystem * @param isDaemon whether the timer is a daemon. Must be true for GemFire use. */ public SystemTimer(Object /* T */ swarm, boolean isDaemon) { Assert.assertTrue(isDaemon); // we don't currently allow non-daemon timers Assert.assertTrue(swarm instanceof InternalDistributedSystem, "Attempt to create swarm on " + swarm); // TODO allow template class? this.timer = new Timer(isDaemon); this.swarm = swarm; addToSwarm(swarm, this); } /** * @param name the name to give the timer thread * @param swarm the swarm this timer belongs to, currently must be a DistributedMember * @param isDaemon whether the timer is a daemon. Must be true for GemFire use. */ public SystemTimer(String name, Object /* T */ swarm, boolean isDaemon) { Assert.assertTrue(isDaemon); // we don't currently allow non-daemon timers Assert.assertTrue(swarm instanceof InternalDistributedSystem, "Attempt to create swarm on " + swarm); // TODO allow template class? this.timer = new Timer(name, isDaemon); this.swarm = swarm; addToSwarm(swarm, this); } private void checkCancelled() throws IllegalStateException { if (this.cancelled) { throw new IllegalStateException("This timer has been cancelled."); } } /** * @see Timer#schedule(TimerTask, long) */ public void schedule(SystemTimerTask task, long delay) { checkCancelled(); if (logger.isTraceEnabled()) { Date tilt = new Date(System.currentTimeMillis() + delay); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); logger.trace("SystemTimer#schedule (long): {}: expect task {} to fire around {}", this, task, sdf.format(tilt)); } timer.schedule(task, delay); } /** * @see Timer#schedule(TimerTask, Date) */ public void schedule(SystemTimerTask task, Date time) { checkCancelled(); if (logger.isTraceEnabled()) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); logger.trace("SystemTimer#schedule (Date): {}: expect task {} to fire around {}", this, task, sdf.format(time)); } timer.schedule(task, time); } // Not currently used, so don't complicate things // /** // * @see Timer#schedule(TimerTask, long, long) // */ // public void schedule(SystemTimerTask task, long delay, long period) { // // TODO add debug statement // checkCancelled(); // timer.schedule(task, delay, period); // } // Not currently used, so don't complicate things // /** // * @see Timer#schedule(TimerTask, Date, long) // */ // public void schedule(SystemTimerTask task, Date firstTime, long period) { // // TODO add debug statement // checkCancelled(); // timer.schedule(task, firstTime, period); // } /** * @see Timer#scheduleAtFixedRate(TimerTask, long, long) */ public void scheduleAtFixedRate(SystemTimerTask task, long delay, long period) { // TODO add debug statement checkCancelled(); timer.scheduleAtFixedRate(task, delay, period); } /** * @see Timer#schedule(TimerTask, long, long) */ public void schedule(SystemTimerTask task, long delay, long period) { // TODO add debug statement checkCancelled(); timer.schedule(task, delay, period); } // Not currently used, so don't complicate things // /** // * @see Timer#scheduleAtFixedRate(TimerTask, Date, long) // */ // public void scheduleAtFixedRate(SystemTimerTask task, Date firstTime, // long period) { // // TODO add debug statement // checkCancelled(); // timer.scheduleAtFixedRate(task, firstTime, period); // } /** * @see Timer#cancel() */ public void cancel() { this.cancelled = true; timer.cancel(); removeFromSwarm(this); } /** * Cover class to track behavior of scheduled tasks * * @see TimerTask */ public abstract static class SystemTimerTask extends TimerTask { protected static final Logger logger = LogService.getLogger(); /** * This is your executed action */ public abstract void run2(); /** * Does debug logging, catches critical errors, then delegates to {@link #run2()} */ @Override final public void run() { final boolean isDebugEnabled = logger.isTraceEnabled(); if (isDebugEnabled) { logger.trace("SystemTimer.MyTask: starting {}", this); } try { this.run2(); } catch (CancelException ignore) { // ignore: TimerThreads can fire during or near cache closure } catch (VirtualMachineError e) { SystemFailure.initiateFailure(e); throw e; } catch (Throwable t) { SystemFailure.checkFailure(); logger.warn(LocalizedMessage .create(LocalizedStrings.SystemTimer_TIMER_TASK_0_ENCOUNTERED_EXCEPTION, this), t); // Don't rethrow, it will just get eaten and kill the timer } if (isDebugEnabled) { logger.trace("SystemTimer.MyTask: finished {}", this); } } } }