package com.limegroup.gnutella.uploader;
import java.io.IOException;
import java.io.OutputStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.limegroup.gnutella.RouterService;
import com.limegroup.gnutella.statistics.UploadStat;
/**
* Kills an OutputStream after a certain amount of time has passed.<p>
*
* The flow has the following methodology:
* When constructed, this class is inactive and does nothing.
* To activate the stall-checking, you must call 'activate'.
* When activate is called, it updates the 'check time' to be
* DELAY_TIME plus the current time and also schedules the task
* if necessary.
* To deactive the stall-checking, you must call 'deactivate'.
* Deactivate does NOT remove the task from the RouterService's schedule,
* but it does tell the checker to not kill the output stream when
* it is run.<p>
*
* Because the task can be reactivated without rescheduling, it is
* possible that RouterService may 'run' the task before the most
* recent delay time has expired. To counteract this, 'activate'
* will store the time that it expects 'run' to be called. If 'run' is
* called too soon, it will reschedule the task to be run at the
* appropriate time.<p>
*
* All methods are synchronized and guaranteed to not lock the timer queue.
*/
public final class StalledUploadWatchdog implements Runnable {
private static final Log LOG =
LogFactory.getLog(StalledUploadWatchdog.class);
/**
* The amount of time to wait before we close this connection
* if nothing has been written to the socket.
*
* Non final for testing.
*/
public static long DELAY_TIME = 1000 * 60 * 2; //2 minutes
private OutputStream ostream;
private boolean isScheduled;
private long nextCheckTime;
private boolean closed;
/**
* Deactives the killing of the NormalUploadState
*/
public synchronized boolean deactivate() {
if(LOG.isDebugEnabled())
LOG.debug("Deactived on: " + ostream);
nextCheckTime = -1; // don't reschedule.
ostream = null; //release the OutputStream
return closed;
}
/**
* Activates the checking.
*/
public synchronized void activate(OutputStream os) {
if(LOG.isDebugEnabled())
LOG.debug("Activated on: " + os);
// let run() know when we're expecting to run, so
// it can reschedule older schedulings if needed.
nextCheckTime = System.currentTimeMillis() + DELAY_TIME;
// if we're not already scheduled, schedule this task.
if ( !isScheduled ) {
RouterService.schedule(this, DELAY_TIME, 0);
isScheduled = true;
}
this.ostream = os;
}
/**
* Kills the upload if we're active, and tells the state
* to close the connection.
* Reschedules if needed.
*/
public synchronized void run() {
isScheduled = false;
// we deactivated, don't do anything.
if(nextCheckTime == -1) {
return;
}
long now = System.currentTimeMillis();
// if this was called before we should be checking,
// then reschedule it for the correct time.
if ( now < nextCheckTime ) {
RouterService.schedule(this, nextCheckTime - now, 0);
}
// otherwise, close the stream & remember we did it.
else {
closed = true;
try {
// If it was null, it was already closed
// by an outside source.
if( ostream != null ) {
if(LOG.isDebugEnabled())
LOG.debug("STALLED! Killing: " + ostream);
UploadStat.STALLED.incrementStat();
ostream.close();
}
} catch(IOException ignored) {
//this can be ignored because we're going to close
//the connection anyway.
}
ostream = null;
}
}
}