/* * File : Timer.java * Created : 21-Nov-2003 * By : parg * * Azureus - a Java Bittorrent client * * This program 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. * * This program 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. See the * GNU General Public License for more details ( see the LICENSE file ). * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package org.gudy.azureus2.core3.util; /** * @author parg * */ import java.lang.ref.WeakReference; import java.util.*; public class Timer extends AERunnable implements SystemTime.ChangeListener { private static boolean DEBUG_TIMERS = true; private static ArrayList<WeakReference<Timer>> timers = null; private static AEMonitor timers_mon = new AEMonitor("timers list"); private ThreadPool thread_pool; private Set<TimerEvent> events = new TreeSet<TimerEvent>(); private long unique_id_next = 0; private long current_when; private volatile boolean destroyed; private boolean indestructable; private boolean log; private int max_events_logged; public Timer( String name ) { this( name, 1 ); } public Timer( String name, int thread_pool_size ) { this(name, thread_pool_size, Thread.NORM_PRIORITY); } public Timer( String name, int thread_pool_size, int thread_priority ) { if (DEBUG_TIMERS) { try { timers_mon.enter(); if (timers == null) { timers = new ArrayList<WeakReference<Timer>>(); AEDiagnostics.addEvidenceGenerator(new evidenceGenerator()); } timers.add(new WeakReference<Timer>(this)); } finally { timers_mon.exit(); } } thread_pool = new ThreadPool(name,thread_pool_size); SystemTime.registerClockChangeListener( this ); Thread t = new Thread(this, "Timer:" + name ); t.setDaemon( true ); t.setPriority(thread_priority); t.start(); } public void setIndestructable() { indestructable = true; } public synchronized List<TimerEvent> getEvents() { return( new ArrayList<TimerEvent>( events )); } public void setLogging( boolean _log ) { log = _log; } public boolean getLogging() { return log; } public void setWarnWhenFull() { thread_pool.setWarnWhenFull(); } public void setLogCPU() { thread_pool.setLogCPU(); } public void runSupport() { while( true ){ try{ TimerEvent event_to_run = null; synchronized(this){ if ( destroyed ){ break; } if ( events.isEmpty()){ // System.out.println( "waiting forever" ); try{ current_when = Integer.MAX_VALUE; this.wait(); }finally{ current_when = 0; } }else{ long now = SystemTime.getCurrentTime(); TimerEvent next_event = (TimerEvent)events.iterator().next(); long when = next_event.getWhen(); long delay = when - now; if ( delay > 0 ){ // System.out.println( "waiting for " + delay ); try{ current_when = when; this.wait(delay); }finally{ current_when = 0; } } } if ( destroyed ){ break; } if ( events.isEmpty()){ continue; } long now = SystemTime.getCurrentTime(); Iterator<TimerEvent> it = events.iterator(); TimerEvent next_event = it.next(); long rem = next_event.getWhen() - now; if ( rem <= SystemTime.TIME_GRANULARITY_MILLIS ){ event_to_run = next_event; it.remove(); /* if ( rem < -100 ){ System.out.println( "Late scheduling [" + (-rem) + "] of " + event_to_run.getString()); } */ } // System.out.println( getName() +": events=" + events.size() + ", to_run=" + (event_to_run==null?"null":event_to_run.getString())); } if ( event_to_run != null ){ event_to_run.setHasRun(); if (log) { System.out.println( "running: " + event_to_run.getString() ); } thread_pool.run(event_to_run.getRunnable()); } }catch( Throwable e ){ Debug.printStackTrace( e ); } } } public void clockChangeDetected( long current_time, long offset ) { if ( Math.abs( offset ) >= 60*1000 ){ // fix up the timers synchronized( this ){ Iterator<TimerEvent> it = events.iterator(); List<TimerEvent> updated_events = new ArrayList<TimerEvent>( events.size()); while (it.hasNext()){ TimerEvent event = (TimerEvent)it.next(); // absolute events don't have their timings fiddled with if ( !event.isAbsolute()){ long old_when = event.getWhen(); long new_when = old_when + offset; TimerEventPerformer performer = event.getPerformer(); // sanity check for periodic events if ( performer instanceof TimerEventPeriodic ){ TimerEventPeriodic periodic_event = (TimerEventPeriodic)performer; long freq = periodic_event.getFrequency(); if ( new_when > current_time + freq + 5000 ){ long adjusted_when = current_time + freq; //Debug.outNoStack( periodic_event.getName() + ": clock change sanity check. Reduced schedule time from " + old_when + "/" + new_when + " to " + adjusted_when ); new_when = adjusted_when; } } // don't wrap around by accident although this really shouldn't happen if ( old_when > 0 && new_when < 0 && offset > 0 ){ // Debug.out( "Ignoring wrap around for " + event.getName()); }else{ // System.out.println( " adjusted: " + old_when + " -> " + new_when ); event.setWhen( new_when ); } } updated_events.add( event ); } // resort - we have to use an alternative list of events as input because if we just throw the // treeset in the constructor optimises things under the assumption that the original set // was correctly sorted... events = new TreeSet<TimerEvent>( updated_events ); } } } public void clockChangeCompleted( long current_time, long offset ) { if ( Math.abs( offset ) >= 60*1000 ){ // there's a chance that between the change being notified and completed an event was scheduled // using an un-modified current time. Nothing can be done for non-periodic events but for periodic // ones we can santitize them to at least be within the periodic time period of the current time // important for when clock goes back but not forward obviously synchronized( this ){ Iterator<TimerEvent> it = events.iterator(); boolean updated = false; while ( it.hasNext()){ TimerEvent event = (TimerEvent)it.next(); // absolute events don't have their timings fiddled with if ( !event.isAbsolute()){ TimerEventPerformer performer = event.getPerformer(); // sanity check for periodic events if ( performer instanceof TimerEventPeriodic ){ TimerEventPeriodic periodic_event = (TimerEventPeriodic)performer; long freq = periodic_event.getFrequency(); long old_when = event.getWhen(); if ( old_when > current_time + freq + 5000 ){ long adjusted_when = current_time + freq; //Debug.outNoStack( periodic_event.getName() + ": clock change sanity check. Reduced schedule time from " + old_when + " to " + adjusted_when ); event.setWhen( adjusted_when ); updated = true; } } } } if ( updated ){ events = new TreeSet<TimerEvent>( new ArrayList<TimerEvent>( events )); } // must have this notify here as the scheduling code uses the current time to calculate // how long to sleep for and this needs to be guaranteed to be using the correct (new) time notify(); } } } public void adjustAllBy( long offset ) { // fix up the timers synchronized (this) { // as we're adjusting all events by the same amount the ordering remains valid Iterator<TimerEvent> it = events.iterator(); boolean resort = false; while (it.hasNext()) { TimerEvent event = it.next(); long old_when = event.getWhen(); long new_when = old_when + offset; // don't wrap around by accident if ( old_when > 0 && new_when < 0 && offset > 0 ){ // Debug.out( "Ignoring wrap around for " + event.getName()); resort = true; }else{ // System.out.println( " adjusted: " + old_when + " -> " + new_when ); event.setWhen( new_when ); } } if ( resort ){ events = new TreeSet<TimerEvent>( new ArrayList<TimerEvent>( events )); } notify(); } } public synchronized TimerEvent addEvent( long when, TimerEventPerformer performer ) { return( addEvent( SystemTime.getCurrentTime(), when, performer )); } public synchronized TimerEvent addEvent( String name, long when, TimerEventPerformer performer ) { return( addEvent( name, SystemTime.getCurrentTime(), when, performer )); } public synchronized TimerEvent addEvent( String name, long when, boolean absolute, TimerEventPerformer performer ) { return( addEvent( name, SystemTime.getCurrentTime(), when, absolute, performer )); } public synchronized TimerEvent addEvent( long creation_time, long when, TimerEventPerformer performer ) { return( addEvent( null, creation_time, when, performer )); } public synchronized TimerEvent addEvent( long creation_time, long when, boolean absolute, TimerEventPerformer performer ) { return( addEvent( null, creation_time, when, absolute, performer )); } public synchronized TimerEvent addEvent( String name, long creation_time, long when, TimerEventPerformer performer ) { return( addEvent( name, creation_time, when, false, performer )); } public synchronized TimerEvent addEvent( String name, long creation_time, long when, boolean absolute, TimerEventPerformer performer ) { TimerEvent event = new TimerEvent( this, unique_id_next++, creation_time, when, absolute, performer ); if ( name != null ){ event.setName( name ); } events.add( event ); if ( log ){ if ( events.size() > max_events_logged ){ max_events_logged = events.size(); System.out.println( "Timer '" + thread_pool.getName() + "' - events = " + max_events_logged ); } } // System.out.println( "event added (" + when + ") - queue = " + events.size()); if ( current_when == Integer.MAX_VALUE || when < current_when ){ notify(); } return( event ); } public synchronized TimerEventPeriodic addPeriodicEvent( long frequency, TimerEventPerformer performer ) { return( addPeriodicEvent( null, frequency, performer )); } public synchronized TimerEventPeriodic addPeriodicEvent( String name, long frequency, TimerEventPerformer performer ) { return( addPeriodicEvent( name, frequency, false, performer )); } public synchronized TimerEventPeriodic addPeriodicEvent( String name, long frequency, boolean absolute, TimerEventPerformer performer ) { TimerEventPeriodic periodic_performer = new TimerEventPeriodic( this, frequency, absolute, performer ); if ( name != null ){ periodic_performer.setName( name ); } if ( log ){ System.out.println( "Timer '" + thread_pool.getName() + "' - added " + periodic_performer.getString()); } return( periodic_performer ); } protected synchronized void cancelEvent( TimerEvent event ) { if ( events.contains( event )){ events.remove( event ); // System.out.println( "event cancelled (" + event.getWhen() + ") - queue = " + events.size()); notify(); } } public synchronized void destroy() { if ( indestructable ){ Debug.out( "Attempt to destroy indestructable timer '" + getName() + "'" ); }else{ destroyed = true; notify(); SystemTime.unregisterClockChangeListener( this ); } if (DEBUG_TIMERS) { try { timers_mon.enter(); // crappy for (Iterator iter = timers.iterator(); iter.hasNext();) { WeakReference timerRef = (WeakReference) iter.next(); Object timer = timerRef.get(); if (timer == null || timer == this) { iter.remove(); } } } finally { timers_mon.exit(); } } } public String getName() { return( thread_pool.getName()); } public synchronized void dump() { System.out.println( "Timer '" + thread_pool.getName() + "': dump" ); Iterator it = events.iterator(); while(it.hasNext()){ TimerEvent ev = (TimerEvent)it.next(); System.out.println( "\t" + ev.getString()); } } private class evidenceGenerator implements AEDiagnosticsEvidenceGenerator { public void generate(IndentWriter writer) { if (!DEBUG_TIMERS) { return; } ArrayList lines = new ArrayList(); int count = 0; try { try { timers_mon.enter(); // crappy for (Iterator iter = timers.iterator(); iter.hasNext();) { WeakReference timerRef = (WeakReference) iter.next(); Timer timer = (Timer) timerRef.get(); if (timer == null) { iter.remove(); } else { count++; List events = timer.getEvents(); lines.add(timer.thread_pool.getName() + ", " + events.size() + " events:"); Iterator it = events.iterator(); while (it.hasNext()) { TimerEvent ev = (TimerEvent) it.next(); lines.add(" " + ev.getString()); } } } } finally { timers_mon.exit(); } writer.println("Timers: " + count + " (time=" + SystemTime.getCurrentTime() + "/" + SystemTime.getMonotonousTime() + ")" ); writer.indent(); for (Iterator iter = lines.iterator(); iter.hasNext();) { String line = (String) iter.next(); writer.println(line); } writer.exdent(); } catch (Throwable e) { writer.println(e.toString()); } } } }