package com.limegroup.gnutella.udpconnect;
import java.util.ArrayList;
import java.util.Iterator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.limegroup.gnutella.util.ManagedThread;
/**
* Manage the timing of messages within UDPConnection processing. To use the
* scheduler, you must first register and then schedule an event. Unregister
* events when you are finally done with them. Recall scheduleEvent if the
* time of your event changes. Events are submitted as
* objects that extend UDPTimerEvent with a handleEvent method defined.
*/
public class UDPScheduler extends ManagedThread {
private static final Log LOG =
LogFactory.getLog(UDPScheduler.class);
/** This is the default event when nothing is scheduled */
public static final UDPTimerEvent NO_EVENT = new NoEvent(Long.MAX_VALUE);
/** The name that the scheduler thread will have */
private static final String NAME_OF_THREAD = "UDPScheduler";
/** The active list of scheduled events */
private ArrayList _connectionEvents;
/** The next event to be handled */
private UDPTimerEvent _scheduledEvent;
private boolean _started;
/** Maintain a handle to the main event processing thread */
private Thread _myThread;
/** Keep track of a singleton instance */
private static UDPScheduler _instance = null;
/** For offloading the synchronization issues, maintain a second thread
for updating events */
private UpdateThread _updateThread;
/**
* object used to make sure only one copy of the two threads exist per
* enclosing object
*/
private final Object _updateThreadLock = new Object();
private final Object _mainThreadLock = new Object();
/**
* Return the UDPScheduler singleton.
*/
public static synchronized UDPScheduler instance() {
// Create the singleton if it doesn't yet exist
if ( _instance == null ) {
_instance = new UDPScheduler();
}
return _instance;
}
/**
* Initialize the UDPScheduler.
*/
private UDPScheduler() {
super(NAME_OF_THREAD);
_connectionEvents = new ArrayList();
_scheduledEvent = NO_EVENT;
_started = false;
_updateThread = null;
}
/**
* Register a UDPTimerEvent for scheduling events
*/
public void register(UDPTimerEvent evt) {
startThreads();
_updateThread.registerEvent(evt);
}
private final synchronized void registerSync(UDPTimerEvent evt) {
_connectionEvents.add(evt);
}
/**
* starts both threads if they haven't been started yet.
*/
private final void startThreads() {
synchronized(_mainThreadLock) {
if ( !_started ) {
_started = true;
setDaemon(true);
start();
}
}
synchronized(_updateThreadLock) {
if ( _updateThread == null ) {
_updateThread = new UpdateThread();
_updateThread.setDaemon(true);
_updateThread.start();
}
}
}
/**
* Notify the scheduler that a connection has a new scheduled event
*/
public void scheduleEvent(UDPTimerEvent evt) {
startThreads();
// Pass the event update to the update thread.
_updateThread.addEvent(evt);
}
/**
* Shortcut test for a second thread to deal with the new schedule handoff
*/
class UpdateThread extends ManagedThread {
ArrayList _listSchedule,_listRegister;
/**
* Initialize the list of pending event updates
*/
public UpdateThread() {
super("UDPUpdateThread");
_listSchedule = new ArrayList();
_listRegister = new ArrayList();
}
/**
* Schedule an event for update in the main event list
*/
public synchronized void addEvent(UDPTimerEvent evt) {
_listSchedule.add(evt);
notify();
}
public synchronized void registerEvent(UDPTimerEvent evt) {
_listRegister.add(evt);
notify();
}
/**
* Process incoming event updates by interacting with the main thread.
*/
public void managedRun() {
UDPTimerEvent evt;
ArrayList localListSchedule,localListRegister;
while (true) {
// Make sure that there is some idle time in the event updating
// Otherwise, it will burn cpu
try {
Thread.sleep(1);
} catch(InterruptedException e) {}
// Clone list for safe unlocked access
synchronized(this) {
localListSchedule = (ArrayList) _listSchedule.clone();
_listSchedule.clear();
localListRegister = (ArrayList) _listRegister.clone();
_listRegister.clear();
}
//then add any events
for (Iterator iter = localListRegister.iterator();iter.hasNext();)
registerSync((UDPTimerEvent)iter.next());
//then reschedule any events
for (int i=0; i < localListSchedule.size(); i++) {
evt = (UDPTimerEvent) localListSchedule.get(i);
updateSchedule(evt);
}
// Wait for more event updates
synchronized(this) {
if (_listSchedule.size() > 0 ||
_listRegister.size() > 0)
continue;
try {
wait();
} catch(InterruptedException e) {}
}
}
}
/**
* Process the updating of an event
*/
private void updateSchedule(UDPTimerEvent evt) {
synchronized(UDPScheduler.this) {
// If the event is sooner and still active, make it current
if ( evt.getEventTime() < _scheduledEvent.getEventTime() &&
_connectionEvents.contains(evt) ) {
_scheduledEvent = evt;
// Notifying
UDPScheduler.this.notify();
}
}
}
}
/**
* Wait for scheduled events on UDPTimerEvent,
* run them and reschedule
*/
public void managedRun() {
long waitTime;
_myThread = Thread.currentThread();
// Specify that an interrupt is okay
while (true) {
// wait for an existing or future event
try {
synchronized(this) {
if ( _scheduledEvent == NO_EVENT ) {
// Wait a long time since there is nothing to do
waitTime = 0;
} else {
// Wait for specific event
waitTime = _scheduledEvent.getEventTime() -
System.currentTimeMillis();
if (waitTime ==0)
waitTime=-1;
}
if (waitTime >=0)
wait(waitTime);
}
} catch(InterruptedException e) {
}
// Determine whether to run existing event
// or to just sleep on a possibly changed event
synchronized(this) {
waitTime = _scheduledEvent.getEventTime() -
System.currentTimeMillis();
if ( waitTime > 0 )
continue;
}
// Run the event and rework the schedule.
runEvent();
reworkSchedule();
}
}
/**
* Run the scheduled UDPTimerEvent event
*/
private synchronized void runEvent() {
if (_scheduledEvent.shouldUnregister())
_connectionEvents.remove(_scheduledEvent);
else
_scheduledEvent.handleEvent();
}
/**
* Go through the active UDPTimerEvent and find the next event.
* For now, I don't think it is necessary to resort the list.
*/
private synchronized void reworkSchedule() {
UDPTimerEvent evt;
long time;
_scheduledEvent = NO_EVENT;
for (int i = 0; i < _connectionEvents.size(); i++) {
evt = (UDPTimerEvent) _connectionEvents.get(i);
time = evt.getEventTime();
if ( evt != NO_EVENT &&
(time < _scheduledEvent.getEventTime() ||
_scheduledEvent == NO_EVENT)) {
_scheduledEvent = evt;
}
}
}
private static final class NoEvent extends UDPTimerEvent {
public NoEvent(long time) {
super(time,null);
}
protected void doActualEvent(UDPConnectionProcessor udpCon) {
}
}
}