All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jsl.collider.TimerQueue Maven / Gradle / Ivy

There is a newer version: 0.2.5
Show newest version
/*
 * Copyright (C) 2013 Sergey Zubarev, [email protected]
 *
 * This file is a part of JS-Collider framework.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see .
 */

/*
 * TimerQueue implementation use one thread from ThreadPool for timers wait,
 * and all timers are executed in the ThreadPool as well.
 * Another implementation specific is that cancel() call is synchronous,
 * i.e. TimerQueue guaranties that there is no thread executing timer task
 * on return from cancel() method.
 *
 * Time resolution is milliseconds, will be enough for most cases.
 */

package org.jsl.collider;

import java.util.Map;
import java.util.HashMap;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;

public class TimerQueue
{
    private static final Logger s_logger = Logger.getLogger( TimerQueue.class.getName() );

    private final ThreadPool m_threadPool;
    private final ReentrantLock m_lock;
    private final Condition m_cond;
    private final TreeMap m_sortedTimers;
    private final Map m_timers;
    private final ThreadPool.Runnable m_worker;

    private class TimerInfo extends ThreadPool.Runnable
    {
        public TimerInfo prev;
        public TimerInfo next;
        public final Runnable task;
        public long fireTime; /* milliseconds */
        public long period;   /* milliseconds */
        public final boolean dynamicRate;
        public long threadID;
        public int waiters;

        public TimerInfo( Runnable task, long fireTime, long period, boolean dynamicRate )
        {
            this.task = task;
            this.fireTime = fireTime;
            this.period = period;
            this.dynamicRate = dynamicRate;
        }

        public void runInThreadPool()
        {
            assert( threadID == -1 );
            assert( prev == null );
            assert( next == null );

            /* threadID value is important for this thread only during task.run() call,
             * can modify it here without lock.
             */
            threadID = Thread.currentThread().getId();

            task.run();

            m_lock.lock();
            try
            {
                threadID = 0;
                if (waiters > 0)
                {
                    m_timers.remove( task );
                    m_cond.signalAll();
                }
                else if (period > 0)
                {
                    final long currentTime = System.currentTimeMillis();
                    if (dynamicRate)
                        fireTime = (fireTime + (((currentTime - fireTime) / period) + 1) * period);
                    else
                        fireTime = (currentTime + period);

                    if (m_sortedTimers.isEmpty())
                    {
                        m_sortedTimers.put( fireTime, this );
                        m_threadPool.execute( m_worker );
                    }
                    else
                    {
                        next = m_sortedTimers.get( fireTime );
                        m_sortedTimers.put( fireTime, this );
                        if ((m_sortedTimers.firstKey() == fireTime) && (next == null))
                            m_cond.signalAll();
                    }
                }
                else
                {
                    m_timers.remove( task );
                    if (m_timers.isEmpty())
                        m_cond.signalAll();
                }
            }
            finally
            {
                m_lock.unlock();
            }
        }
    }

    private void run_i()
    {
        if (s_logger.isLoggable(Level.FINE))
            s_logger.fine( "TimerQueue worker started" );

        m_lock.lock();
        try
        {
            while (!m_sortedTimers.isEmpty())
            {
                final Map.Entry firstEntry = m_sortedTimers.firstEntry();
                assert( firstEntry != null );

                final long currentTime = System.currentTimeMillis();
                if (firstEntry.getKey() <= currentTime)
                {
                    TimerInfo timerInfo = firstEntry.getValue();
                    assert( timerInfo != null );
                    do
                    {
                        assert( timerInfo.threadID == 0 );
                        final TimerInfo next = timerInfo.next;
                        timerInfo.prev = null;
                        timerInfo.next = null;
                        /* Special value indicating the timer is being fired,
                         * will be changed to the proper value right before timer task execution.
                         */
                        timerInfo.threadID = -1;

                        m_threadPool.execute( timerInfo );
                        timerInfo = next;
                    }
                    while (timerInfo != null);
                    m_sortedTimers.remove( firstEntry.getKey() );
                }
                else
                {
                    final long sleepTime = (firstEntry.getKey() - currentTime);
                    try
                    {
                        m_cond.awaitNanos( TimeUnit.MILLISECONDS.toNanos(sleepTime) );
                    }
                    catch (InterruptedException ex)
                    {
                        s_logger.warning( ex.toString() );
                    }
                }
            }
        }
        finally
        {
            m_lock.unlock();
        }

        if (s_logger.isLoggable(Level.FINE))
            s_logger.fine( "TimerQueue worker finished" );
    }

    private void removeTimerLocked( TimerInfo timerInfo )
    {
        /* We will need to wake up worker thread only if timer
         * had the lowest firing time, and it was a one.
         */
        boolean wakeUpThread = false;

        if (timerInfo.prev == null)
        {
            if (timerInfo.next == null)
            {
                wakeUpThread = (m_sortedTimers.firstKey() == timerInfo.fireTime);
                m_sortedTimers.remove( timerInfo.fireTime );
            }
            else
            {
                m_sortedTimers.put( timerInfo.fireTime, timerInfo.next );
                timerInfo.next = null;
            }
        }
        else
        {
            timerInfo.prev = timerInfo.next;
            if (timerInfo.next != null)
            {
                timerInfo.next.prev = timerInfo.prev;
                timerInfo.next = null;
            }
            timerInfo.prev = null;
        }

        m_timers.remove( timerInfo.task );

        if (wakeUpThread)
            m_cond.signal();
    }

    private int schedule_i( Runnable task, long delay, long period, boolean dynamicRate )
    {
        m_lock.lock();
        try
        {
            if (m_timers.containsKey(task))
            {
                /* Timer already scheduled. */
                return -1;
            }

            boolean wasEmpty = m_sortedTimers.isEmpty();

            final long fireTime = (System.currentTimeMillis() + delay);
            final TimerInfo timerInfo = new TimerInfo( task, fireTime, period, dynamicRate );
            timerInfo.next = m_sortedTimers.get( fireTime );
            m_sortedTimers.put( fireTime, timerInfo );
            m_timers.put( task, timerInfo );

            if (!wasEmpty)
            {
                /* It make sense to wake up worker thread
                 * only if new timer is sooner than all previous.
                 */
                if (fireTime < m_sortedTimers.firstKey())
                    m_cond.signal();
                return 0;
            }
        }
        finally
        {
            m_lock.unlock();
        }

        /* Worker will be started only if timer queue was empty. */
        m_threadPool.execute( m_worker );
        return 0;
    }

    /**
     * Public methods
     */
    public TimerQueue( ThreadPool threadPool )
    {
        m_threadPool = threadPool;
        m_lock = new ReentrantLock();
        m_cond = m_lock.newCondition();
        m_sortedTimers = new TreeMap();
        m_timers = new HashMap();
        m_worker = new ThreadPool.Runnable() { public void runInThreadPool() { run_i(); } };
    }

    /**
     * Schedules the specified task for execution after the specified delay.
     */
    public final int schedule( Runnable task, long delay, TimeUnit unit )
    {
        return schedule_i( task, unit.toMillis(delay), 0, /*dynamic rate*/ false );
    }

    /**
     * Schedules the specified task for repeated fixed-rate execution,
     * beginning after the specified delay. Subsequent executions
     * take place at approximately regular intervals, separated by the specified period.
     * Working time line looks like:
     * +---------+-----------------+--------+------------------
     *    delay  |                 | period |
     *
     * +---------+-timer-work-time-+--------+------------------
     *                                       \
     *                                        next fire time
     */
    public final int scheduleAtFixedRate( Runnable task, long delay, long period, TimeUnit timeUnit )
    {
        return schedule_i( task, timeUnit.toMillis(delay), timeUnit.toMillis(period), /*dynamic rate*/ false );
    }

    /**
     * Schedules the specified task for repeated dynamic-rate execution,
     * beginning after the specified delay. Subsequent executions
     * take place at approximately regular intervals, separated by the specified period.
     * Working time line looks like:
     * +---------+----------+----------+------------------
     *    delay  |  period  |  period  |
     *
     * +---------+-timer-work-time-+---+------------------
     *                                  \
     *                                   next fire time
     * So timer will be executed exactly at a (delay + period*n) time,
     * skipping time if timer handler execution took too much time.
     */
    public final int scheduleAtDynamicRate( Runnable task, long delay, long period, TimeUnit timeUnit )
    {
        return schedule_i( task, timeUnit.toMillis(delay), timeUnit.toMillis(period), /*dynamic rate*/ true );
    }

    /**
     * Cancel timer,
     * waits if timer is being firing at the moment,
     * so it guarantees that timer handler is not executed
     * on return.
     */
    public final int cancel( Runnable task ) throws InterruptedException
    {
        m_lock.lock();
        try
        {
            final TimerInfo timerInfo = m_timers.get( task );
            if (timerInfo == null)
            {
                /* Timer already canceled or was not scheduled. */
                return -1;
            }

            if (timerInfo.threadID != 0)
            {
                /* Timer task is being executed now. */
                if (timerInfo.threadID == Thread.currentThread().getId())
                {
                    /* Called from the timer callback,
                     * let's just reset the period to avoid timer rescheduling.
                     */
                    timerInfo.period = 0;
                }
                else
                {
                    timerInfo.waiters++;
                    while (timerInfo.threadID != 0)
                        m_cond.await();
                    timerInfo.waiters--;
                }
                return 0;
            }

            removeTimerLocked( timerInfo );
        }
        finally
        {
            m_lock.unlock();
        }
        return 0;
    }

    /**
     * Cancel timer,
     * do not wait if runnable is being executed at that moment,
     * return > 0 in this case.
     */
    public final int cancelNoWait( Runnable task )
    {
        m_lock.lock();
        try
        {
            final TimerInfo timerInfo = m_timers.get( task );
            if (timerInfo == null)
            {
                /* Timer already canceled or was not scheduled. */
                return -1;
            }

            if (timerInfo.threadID != 0)
            {
                /* Timer task is being executed now. */
                return 1;
            }

            removeTimerLocked( timerInfo );
        }
        finally
        {
            m_lock.unlock();
        }
        return 0;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy