/* * Copyright (c) 1998-2010 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package com.caucho.util; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.LockSupport; import java.util.logging.Level; import java.util.logging.Logger; import com.caucho.env.thread.TaskWorker; import com.caucho.env.thread.ThreadPool; import com.caucho.loader.ClassLoaderListener; import com.caucho.loader.DynamicClassLoader; /** * The alarm class provides a lightweight event scheduler. This allows * an objects to schedule a timeout without creating a new thread. * * <p>A separate thread periodically tests the queue for alarms ready. */ public class Alarm implements ThreadTask, ClassLoaderListener { private static final Logger log = Logger.getLogger(Alarm.class.getName()); private static final ClassLoader _systemLoader; private static final AlarmThread _alarmThread; private static final CoordinatorThread _coordinatorThread; private static volatile long _currentTime; private static volatile boolean _isCurrentTimeUsed; private static volatile boolean _isSlowTime; private static final AlarmHeap _heap = new AlarmHeap(); private static final AtomicInteger _runningAlarmCount = new AtomicInteger(); private static final boolean _isStressTest; private static long _testTime; private static long _testNanoDelta; private long _wakeTime; private AlarmListener _listener; private ClassLoader _contextLoader; private String _name; private boolean _isPriority = true; private int _heapIndex = 0; private volatile boolean _isRunning; /** * Create a new wakeup alarm with a designated listener as a callback. * The alarm is not scheduled. */ protected Alarm() { this("alarm"); } protected Alarm(String name) { _name = name; addEnvironmentListener(); } /** * Create a new wakeup alarm with a designated listener as a callback. * The alarm is not scheduled. */ public Alarm(AlarmListener listener) { this("alarm[" + listener + "]", listener); } /** * Create a new wakeup alarm with a designated listener as a callback. * The alarm is not scheduled. */ public Alarm(String name, AlarmListener listener) { this(name, listener, Thread.currentThread().getContextClassLoader()); } /** * Create a new wakeup alarm with a designated listener as a callback. * The alarm is not scheduled. */ public Alarm(String name, AlarmListener listener, ClassLoader loader) { this(name); setListener(listener); setContextLoader(loader); } /** * Create a new wakeup alarm with a designated listener as a callback. * The alarm is not scheduled. */ public Alarm(String name, AlarmListener listener, long delta, ClassLoader loader) { this(name); setListener(listener); setContextLoader(loader); queue(delta); } /** * Creates a named alarm and schedules its wakeup. * * @param name the object prepared to receive the callback * @param listener the object prepared to receive the callback * @param delta the time in milliseconds to wake up */ public Alarm(String name, AlarmListener listener, long delta) { this(name, listener); queue(delta); } /** * Creates a new alarm and schedules its wakeup. * * @param listener the object prepared to receive the callback * @param delta the time in milliseconds to wake up */ public Alarm(AlarmListener listener, long delta) { this(listener); queue(delta); } /** * Returns the alarm name. */ public String getName() { return _name; } /** * Sets the alarm name. */ protected void setName(String name) { _name = name; } public static boolean isActive() { return _testTime == 0 && _alarmThread != null; } /** * Returns the approximate current time in milliseconds. * Convenient for minimizing system calls. */ public static long getCurrentTime() { // test avoids extra writes on multicore machines if (! _isCurrentTimeUsed) { if (_testTime > 0) return _testTime; if (_alarmThread == null) return System.currentTimeMillis(); if (_isSlowTime) { return System.currentTimeMillis(); } else { _isCurrentTimeUsed = true; } } return _currentTime; } /** * Gets current time, handling test */ public static long getCurrentTimeActual() { if (_testTime > 0) return System.currentTimeMillis(); else return getCurrentTime(); } /** * Returns the exact current time in milliseconds. */ public static long getExactTime() { if (_testTime > 0) return _testTime; else { return System.currentTimeMillis(); } } /** * Returns the exact current time in nanoseconds. */ public static long getExactTimeNanoseconds() { if (_testTime > 0) { // php/190u // System.nanoTime() is not related to currentTimeMillis(), so return // a different offset. See System.nanoTime() javadoc return (_testTime - 10000000) * 1000000L + _testNanoDelta; } return System.nanoTime(); } /** * Returns true for testing. */ public static boolean isTest() { return _testTime > 0; } /** * Yield if in test mode to maintain ordering */ public static void yieldIfTest() { if (_testTime > 0) { // Thread.yield(); } } /** * Returns the wake time of this alarm. */ public long getWakeTime() { return _wakeTime; } void setWakeTime(long wakeTime) { _wakeTime = wakeTime; } int getHeapIndex() { return _heapIndex; } void setHeapIndex(int index) { _heapIndex = index; } /** * Return the alarm's listener. */ public AlarmListener getListener() { return _listener; } /** * Sets the alarm's listener. */ public void setListener(AlarmListener listener) { _listener = listener; } /** * Sets the alarm's context loader */ public void setContextLoader(ClassLoader loader) { _contextLoader = loader; } /** * Sets the alarm's context loader */ public ClassLoader getContextLoader() { return _contextLoader; } /** * Returns true if the alarm is currently queued. */ public boolean isQueued() { return _heapIndex != 0; } /** * Returns true if the alarm is currently running */ boolean isRunning() { return _isRunning; } /** * True for a priority alarm (default) */ public void setPriority(boolean isPriority) { _isPriority = isPriority; } /** * True for a priority alarm (default) */ public boolean isPriority() { return _isPriority; } /** * Registers the alarm with the environment listener for auto-close */ protected void addEnvironmentListener() { // Environment.addClassLoaderListener(this); } /** * Queue the alarm for wakeup. * * @param delta time in milliseconds to wake */ public void queue(long delta) { long now = getCurrentTime(); boolean isNotify = _heap.queueAt(this, now + delta); if (isNotify) { _coordinatorThread.wake(); } } /** * Queue the alarm for wakeup. * * @param delta time in milliseconds to wake */ public void queueAt(long wakeTime) { boolean isNotify = _heap.queueAt(this, wakeTime); if (isNotify) { _coordinatorThread.wake(); } } /** * Remove the alarm from the wakeup queue. */ public void dequeue() { if (_heapIndex > 0) _heap.dequeue(this); } /** * Runs the alarm. This is only called from the worker thread. */ public void run() { try { handleAlarm(); } catch (Throwable e) { log.log(Level.WARNING, e.toString(), e); } finally { _isRunning = false; _runningAlarmCount.decrementAndGet(); } } /** * Handles the alarm. */ private void handleAlarm() { AlarmListener listener = getListener(); if (listener == null) return; Thread thread = Thread.currentThread(); ClassLoader loader = getContextLoader(); if (loader != null) thread.setContextClassLoader(loader); else thread.setContextClassLoader(_systemLoader); try { listener.handleAlarm(this); } finally { thread.setContextClassLoader(_systemLoader); } } /** * Handles the case where a class loader has completed initialization */ public void classLoaderInit(DynamicClassLoader loader) { } /** * Handles the case where a class loader is dropped. */ public void classLoaderDestroy(DynamicClassLoader loader) { close(); } /** * Closes the alarm instance */ public void close() { dequeue(); // server/16a{0,1} /* _listener = null; _contextLoader = null; */ } // test static void testClear() { _heap.testClear(); } static void setTestTime(long time) { _testTime = time; if (_testTime > 0) { if (time < _currentTime) { testClear(); } _currentTime = time; } else { _currentTime = System.currentTimeMillis(); } Alarm alarm; Thread thread = Thread.currentThread(); ClassLoader oldLoader = thread.getContextClassLoader(); try { while ((alarm = _heap.extractAlarm(getCurrentTime())) != null) { alarm.run(); } } finally { thread.setContextClassLoader(oldLoader); } } static void setTestNanoDelta(long delta) { _testNanoDelta = delta; } public String toString() { return "Alarm[" + _name + "]"; } static class AlarmThread extends Thread { AlarmThread() { super("resin-timer"); setDaemon(true); setPriority(Thread.MAX_PRIORITY); } public void run() { int idleCount = 0; while (true) { try { if (_testTime > 0) { _currentTime = _testTime; LockSupport.park(); continue; } long now = System.currentTimeMillis(); _currentTime = now; boolean isCurrentTimeUsed = _isCurrentTimeUsed; _isCurrentTimeUsed = false; if (isCurrentTimeUsed) { _isSlowTime = false; } else { idleCount++; if (idleCount == 10) { _isSlowTime = true; } } long sleepTime = _isSlowTime ? 1000L : 5L; LockSupport.parkNanos(sleepTime * 1000000L); } catch (Throwable e) { } } } } static class CoordinatorThread extends TaskWorker { @Override protected boolean isPermanent() { return true; } /** * Runs the coordinator task. */ @Override public long runTask() { try { Alarm alarm; if ((alarm = _heap.extractAlarm(getCurrentTime())) != null) { // throttle alarm invocations by 5ms so quick alarms don't need // extra threads /* if (_concurrentAlarmThrottle < _runningAlarmCount.get()) { try { Thread.sleep(5); } catch (Throwable e) { } } */ _runningAlarmCount.incrementAndGet(); long now; if (_isStressTest) now = Alarm.getExactTime(); else now = Alarm.getCurrentTime(); long delta = now - alarm._wakeTime; if (delta > 10000) { log.warning(this + " slow alarm " + alarm + " " + delta + "ms"); } else if (_isStressTest && delta > 100) { System.out.println(this + " slow alarm " + alarm + " " + delta); } if (alarm.isPriority()) ThreadPool.getThreadPool().schedulePriority(alarm); else ThreadPool.getThreadPool().schedule(alarm); } } catch (Exception e) { log.log(Level.WARNING, e.toString(), e); } long next = _heap.nextAlarmTime(); // #3548 - getCurrentTime for consistency long now = getCurrentTime(); long delta; if (next < 0) delta = 120000L; else delta = next - now; return delta; } } static { _currentTime = System.currentTimeMillis(); ClassLoader systemLoader = null; AlarmThread alarmThread = null; CoordinatorThread coordinator = null; try { systemLoader = ClassLoader.getSystemClassLoader(); } catch (Throwable e) { } try { ClassLoader loader = Alarm.class.getClassLoader(); if (loader == null || loader == systemLoader || systemLoader != null && loader == systemLoader.getParent()) { alarmThread = new AlarmThread(); alarmThread.start(); coordinator = new CoordinatorThread(); Thread coordinatorThread = new Thread(coordinator); coordinatorThread.setDaemon(true); coordinatorThread.setPriority(Thread.MAX_PRIORITY); coordinatorThread.setName("alarm-coordinator"); coordinatorThread.start(); } } catch (Throwable e) { // should display for security manager issues log.fine("Alarm not started: " + e); } _systemLoader = systemLoader; _alarmThread = alarmThread; _coordinatorThread = coordinator; _isStressTest = System.getProperty("caucho.stress.test") != null; } }