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

org.jgroups.util.HashedTimingWheel Maven / Gradle / Ivy

Go to download

This artifact provides a single jar that contains all classes required to use remote Jakarta Enterprise Beans and Jakarta Messaging, including all dependencies. It is intended for use by those not using maven, maven users should just import the Jakarta Enterprise Beans and Jakarta Messaging BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up with different versions on classes on the class path).

There is a newer version: 35.0.0.Beta1
Show newest version
package org.jgroups.util;


import org.jgroups.Global;
import org.jgroups.annotations.Experimental;
import org.jgroups.annotations.Unsupported;
import org.jgroups.logging.Log;
import org.jgroups.logging.LogFactory;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


/**
 * Implementation of {@link TimeScheduler}. Uses a hashed timing wheel [1].
 *
 * [1] http://www.cse.wustl.edu/~cdgill/courses/cs6874/TimingWheels.ppt
 *
 * @author Bela Ban
 */
@Experimental @Unsupported
public class HashedTimingWheel implements TimeScheduler, Runnable  {
    private final ThreadPoolExecutor pool;

    private Thread runner=null;

    private final Lock lock=new ReentrantLock();

    protected volatile boolean running;

    protected static final Log log=LogFactory.getLog(HashedTimingWheel.class);

    protected ThreadFactory timer_thread_factory=null;

    protected int wheel_size=200;   // number of ticks on the timing wheel

    protected long tick_time=50L;  // number of milliseconds a tick has

    protected final long ROTATION_TIME;// time for 1 lap

    protected final List[] wheel;

    protected int wheel_position=0; // current position of the wheel, run() advances it by one (every TICK_TIME ms)


    /**
     * Create a scheduler that executes tasks in dynamically adjustable intervals
     */
    @SuppressWarnings("unchecked")
    public HashedTimingWheel() {
        ROTATION_TIME=wheel_size * tick_time;
        wheel=new List[wheel_size];
        pool=new ThreadPoolExecutor(4, 10,
                                    5000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(5000),
                                    Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
        init();
    }


    @SuppressWarnings("unchecked")
    public HashedTimingWheel(ThreadFactory factory, int min_threads, int max_threads, long keep_alive_time, int max_queue_size,
                             int wheel_size, long tick_time) {
        this.wheel_size=wheel_size;
        this.tick_time=tick_time;
        ROTATION_TIME=wheel_size * tick_time;
        wheel=new List[this.wheel_size];
        timer_thread_factory=factory;
        pool=new ThreadPoolExecutor(min_threads, max_threads,keep_alive_time, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue(max_queue_size),
                                    factory, new ThreadPoolExecutor.CallerRunsPolicy());
        init();
    }


    @SuppressWarnings("unchecked")
    public HashedTimingWheel(int corePoolSize) {
        ROTATION_TIME=wheel_size * tick_time;
        wheel=(List[])new List[wheel_size];
        pool=new ThreadPoolExecutor(corePoolSize, corePoolSize * 2,
                                    5000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(5000),
                                    Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
        init();
    }


    public void setThreadFactory(ThreadFactory factory) {
        pool.setThreadFactory(factory);
    }

    public int getMinThreads() {
        return pool.getCorePoolSize();
    }

    public void setMinThreads(int size) {
        pool.setCorePoolSize(size);
    }

    public int getMaxThreads() {
        return pool.getMaximumPoolSize();
    }

    public void setMaxThreads(int size) {
        pool.setMaximumPoolSize(size);
    }

    public long getKeepAliveTime() {
        return pool.getKeepAliveTime(TimeUnit.MILLISECONDS);
    }

    public void setKeepAliveTime(long time) {
        pool.setKeepAliveTime(time, TimeUnit.MILLISECONDS);
    }

    public int getCurrentThreads() {
        return pool.getPoolSize();
    }

    public int getQueueSize() {
        return pool.getQueue().size();
    }


    public String dumpTimerTasks() {
        StringBuilder sb=new StringBuilder();

        lock.lock();
        try {
            for(List list: wheel) {
                if(!list.isEmpty()) {
                    sb.append(list).append("\n");
                }
            }
        }
        finally {
            lock.unlock();
        }

        return sb.toString();
    }




    public void execute(Runnable task) {
        schedule(task, 0, TimeUnit.MILLISECONDS);
    }


    public Future schedule(Runnable work, long delay, TimeUnit unit) {
        if(work == null)
            throw new NullPointerException();
        if (isShutdown() || !running)
            return null;

        MyTask retval=null;
        long time=TimeUnit.MILLISECONDS.convert(delay, unit); // execution time

        lock.lock();
        try {
            int num_ticks=(int)Math.max(1, ((time % ROTATION_TIME) / tick_time));
            int position=(wheel_position + num_ticks) % wheel_size;
            int rounds=(int)(time / ROTATION_TIME);
            List list=wheel[position];
            retval=new MyTask(work, rounds);
            list.add(retval);
        }
        finally {
            lock.unlock();
        }

        return retval;
    }



    public Future scheduleWithFixedDelay(Runnable task, long initial_delay, long delay, TimeUnit unit) {
        if(task == null)
            throw new NullPointerException();
        if (isShutdown() || !running)
            return null;
        RecurringTask wrapper=new FixedIntervalTask(task, delay);
        wrapper.doSchedule(initial_delay);
        return wrapper;
    }


    public Future scheduleAtFixedRate(Runnable task, long initial_delay, long delay, TimeUnit unit) {
        if(task == null)
            throw new NullPointerException();
        if (isShutdown() || !running)
            return null;
        RecurringTask wrapper=new FixedRateTask(task, delay);
        wrapper.doSchedule(initial_delay);
        return wrapper;
    }


    /**
     * Schedule a task for execution at varying intervals. After execution, the task will get rescheduled after
     * {@link org.jgroups.util.HashedTimingWheel.Task#nextInterval()} milliseconds. The task is neve done until nextInterval()
     * return a value <= 0 or the task is cancelled.
     * @param task the task to execute
     * Task is rescheduled relative to the last time it actually started execution

* false:
Task is scheduled relative to its last execution schedule. This has the effect * that the time between two consecutive executions of the task remains the same.

* Note that relative is always true; we always schedule the next execution relative to the last *actual* */ public Future scheduleWithDynamicInterval(Task task) { if(task == null) throw new NullPointerException(); if (isShutdown() || !running) return null; RecurringTask task_wrapper=new DynamicIntervalTask(task); task_wrapper.doSchedule(); // calls schedule() in ScheduledThreadPoolExecutor return task_wrapper; } /** * Returns the number of tasks currently in the timer * @return The number of tasks currently in the timer */ public int size() { int retval=0; lock.lock(); try { for(List list: wheel) retval+=list.size(); return retval; } finally { lock.unlock(); } } public String toString() { return getClass().getSimpleName(); } /** * Stops the timer, cancelling all tasks * * @throws InterruptedException if interrupted while waiting for thread to return */ public void stop() { stopRunner(); List remaining_tasks=pool.shutdownNow(); for(Runnable task: remaining_tasks) { if(task instanceof Future) { Future future=(Future)task; future.cancel(true); } } pool.getQueue().clear(); try { pool.awaitTermination(Global.THREADPOOL_SHUTDOWN_WAIT_TIME, TimeUnit.MILLISECONDS); } catch(InterruptedException e) { } } public boolean isShutdown() { return pool.isShutdown(); } public void run() { final long base_time=System.currentTimeMillis(); long next_time, sleep_time; long cnt=0; while(running) { try { _run(); next_time=base_time + (++cnt * tick_time); sleep_time=Math.max(0, next_time - System.currentTimeMillis()); Util.sleep(sleep_time); } catch(Throwable t) { log.error("failed executing tasks(s)", t); } } } protected void _run() { lock.lock(); try { wheel_position=(wheel_position +1) % wheel_size; List list=wheel[wheel_position]; if(list.isEmpty()) return; for(Iterator it=list.iterator(); it.hasNext();) { MyTask tmp=it.next(); if(tmp.getAndDecrementRound() <= 0) { try { pool.execute(tmp); } catch(Throwable t) { log.error("failure submitting task to thread pool", t); } it.remove(); } } } finally { lock.unlock(); } } protected void init() { for(int i=0; i < wheel.length; i++) wheel[i]=new LinkedList<>(); startRunner(); } protected void startRunner() { running=true; runner=timer_thread_factory != null? timer_thread_factory.newThread(this, "Timer runner") : new Thread(this, "Timer runner"); runner.start(); } protected void stopRunner() { lock.lock(); try { running=false; for(List list: wheel) { if(!list.isEmpty()) { for(MyTask task: list) task.cancel(true); list.clear(); } } } finally { lock.unlock(); } } /** * Simple task wrapper, always executed by at most 1 thread. */ protected static class MyTask implements Future, Runnable { protected final Runnable task; protected volatile boolean cancelled=false; protected volatile boolean done=false; protected MyTask next; protected int round; public MyTask(Runnable task, int round) { this.task=task; this.round=round; } public int getRound() { return round; } public int getAndDecrementRound() { return round--; } public void setRound(int round) { this.round=round; } public boolean cancel(boolean mayInterruptIfRunning) { boolean retval=!isDone(); cancelled=true; return retval; } public boolean isCancelled() { return cancelled; } public boolean isDone() { return done || cancelled; } public Object get() throws InterruptedException, ExecutionException { return null; } public Object get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return null; } public void run() { if(isDone()) return; try { task.run(); } catch(Throwable t) { log.error("failed executing task " + task, t); } finally { done=true; } } public String toString() { return task.toString(); } } /** * Task which executes multiple times. An instance of this class wraps the real task and intercepts run(): when * called, it forwards the call to task.run() and then schedules another execution (until cancelled). The * {@link #nextInterval()} method determines the time to wait until the next execution. * @param */ private abstract class RecurringTask implements Runnable, Future { protected final Runnable task; protected volatile Future future; // cannot be null ! protected volatile boolean cancelled=false; public RecurringTask(Runnable task) { this.task=task; } /** * The time to wait until the next execution * @return Number of milliseconds to wait until the next execution is scheduled */ protected abstract long nextInterval(); protected boolean rescheduleOnZeroDelay() {return false;} public void doSchedule() { long next_interval=nextInterval(); if(next_interval <= 0 && !rescheduleOnZeroDelay()) { if(log.isTraceEnabled()) log.trace("task will not get rescheduled as interval is " + next_interval); return; } future=schedule(this, next_interval, TimeUnit.MILLISECONDS); if(cancelled) future.cancel(true); } public void doSchedule(long next_interval) { future=schedule(this, next_interval, TimeUnit.MILLISECONDS); if(cancelled) future.cancel(true); } public void run() { if(cancelled) { if(future != null) future.cancel(true); return; } try { task.run(); } catch(Throwable t) { log.error("failed running task " + task, t); } if(!cancelled) doSchedule(); } public boolean cancel(boolean mayInterruptIfRunning) { boolean retval=!isDone(); cancelled=true; if(future != null) future.cancel(mayInterruptIfRunning); return retval; } public boolean isCancelled() { return cancelled; } public boolean isDone() { return cancelled || (future == null || future.isDone()); } public V get() throws InterruptedException, ExecutionException { return null; } public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return null; } public String toString() { StringBuilder sb=new StringBuilder(); sb.append(getClass().getSimpleName() + ": task=" + task + ", cancelled=" + isCancelled()); return sb.toString(); } } private class FixedIntervalTask extends RecurringTask { final long interval; public FixedIntervalTask(Runnable task, long interval) { super(task); this.interval=interval; } protected long nextInterval() { return interval; } } private class FixedRateTask extends RecurringTask { final long interval; final long first_execution; int num_executions=0; public FixedRateTask(Runnable task, long interval) { super(task); this.interval=interval; this.first_execution=System.currentTimeMillis(); } protected long nextInterval() { long target_time=first_execution + (interval * ++num_executions); return target_time - System.currentTimeMillis(); } protected boolean rescheduleOnZeroDelay() {return true;} } private class DynamicIntervalTask extends RecurringTask { public DynamicIntervalTask(Task task) { super(task); } protected long nextInterval() { if(task instanceof Task) return ((Task)task).nextInterval(); return 0; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy