/*
* 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());
}
}
}
}