package org.limewire.rudp; import java.util.ArrayList; import java.util.List; import org.limewire.concurrent.ManagedThread; /** * <p>Manages the timing of messages within {@link UDPConnection} processing. * </p><p> * To use the scheduler, you must first register and then schedule an event. * Events are submitted as objects that extend {@link UDPTimerEvent} with * a {@link UDPTimerEvent#handleEvent()} method defined. * </p><p> * Re-call {@link #scheduleEvent(UDPTimerEvent)} if the time of your event changes. * </p> * The events must be unregister when you are done with them via * {@link UDPTimerEvent#unregister()}. */ 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 List<UDPTimerEvent> _connectionEvents; /** The next event to be handled. */ private UDPTimerEvent _scheduledEvent; private boolean _started; /** 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 <code>UDPScheduler</code> 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 <code>UDPScheduler</code>. */ private UDPScheduler() { super(NAME_OF_THREAD); _connectionEvents = new ArrayList<UDPTimerEvent>(); _scheduledEvent = NO_EVENT; _started = false; _updateThread = null; } /** * Register a <code>UDPTimerEvent</code> 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 { List<UDPTimerEvent> _listSchedule; List<UDPTimerEvent> _listRegister; /** * Initialize the list of pending event updates. */ public UpdateThread() { super("UDPUpdateThread"); _listSchedule = new ArrayList<UDPTimerEvent>(); _listRegister = new ArrayList<UDPTimerEvent>(); } /** * 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. */ @Override public void run() { UDPTimerEvent evt; List<UDPTimerEvent> localListSchedule; List<UDPTimerEvent> 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 = new ArrayList<UDPTimerEvent>(_listSchedule); _listSchedule.clear(); localListRegister = new ArrayList<UDPTimerEvent>(_listRegister); _listRegister.clear(); } //then add any events for(int i = 0; i < localListRegister.size(); i++) registerSync(localListRegister.get(i)); //then reschedule any events for (int i=0; i < localListSchedule.size(); i++) { evt = 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. */ @Override public void run() { long waitTime; // 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 = _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); } @Override protected void doActualEvent(UDPConnectionProcessor udpCon) { } } }