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

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

There is a newer version: 9.1.7.Final
Show newest version

package org.jgroups.util;


import org.jgroups.Global;
import org.jgroups.annotations.GuardedBy;
import org.jgroups.logging.Log;
import org.jgroups.logging.LogFactory;

import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


/**
 * Implementation of {@link org.jgroups.util.TimeScheduler}. Uses a thread pool and a single thread which waits for the
 * next task to be executed. When ready, it passes the task to the associated pool to get executed. When multiple tasks
 * are scheduled to get executed at the same time, they're collected in a queue associated with the task execution
 * time, and are executed together.
 *
 * @author Bela Ban
 * @deprecated Use {@link org.jgroups.util.TimeScheduler3} instead
 */
@Deprecated
public class TimeScheduler2 implements TimeScheduler, Runnable  {
    private final ThreadPoolExecutor pool;

    private final ConcurrentSkipListMap tasks=new ConcurrentSkipListMap();

    private Thread runner=null;

    private final Lock lock=new ReentrantLock();

    private final Condition tasks_available=lock.newCondition();

    @GuardedBy("lock")
    private long next_execution_time=0;

    /** Needed to signal going from 0 tasks to non-zero (we cannot use tasks.isEmpty() here ...) */
    protected final AtomicBoolean no_tasks=new AtomicBoolean(true);

    protected volatile boolean running=false;

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

    protected ThreadFactory timer_thread_factory=null;

    protected static final long SLEEP_TIME=10000;


    /**
     * Create a scheduler that executes tasks in dynamically adjustable intervals
     */
    public TimeScheduler2() {
        pool=new ThreadPoolExecutor(4, 10,
                                    5000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(5000),
                                    Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
        init();
    }


    public TimeScheduler2(ThreadFactory factory, int min_threads, int max_threads, long keep_alive_time, int max_queue_size,
                          String rejection_policy) {
        timer_thread_factory=factory;
        RejectedExecutionHandler tmp=Util.parseRejectionPolicy(rejection_policy);
        pool=new ThreadPoolExecutor(min_threads, max_threads,keep_alive_time, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue(max_queue_size),
                                    factory, tmp);
        init();
    }

    public TimeScheduler2(int corePoolSize) {
        pool=new ThreadPoolExecutor(corePoolSize, corePoolSize * 2,
                                    5000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(5000),
                                    Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
        init();
    }


    public void setThreadFactory(ThreadFactory f) {pool.setThreadFactory(f);}
    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();
        for(Entry entry: tasks.values()) {
            sb.append(entry.dump()).append("\n");
        }
        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)
            return null;

        Future retval=null;

        long key=System.currentTimeMillis() + TimeUnit.MILLISECONDS.convert(delay, unit); // execution time
        Entry task=new Entry(work);
        while(!isShutdown()) {
            Entry existing=tasks.putIfAbsent(key, task);
            if(existing == null) {
                retval=task.getFuture();
                break; // break out of the while loop
            }
            if((retval=existing.add(work)) != null)
                break;

            // Else the existing entry is completed.  It'll be removed shortly, so we just loop around again.
            // Don't remove the entry ourselves - see JGRP-1457.
        }

        if(key < next_execution_time || no_tasks.compareAndSet(true, false)) {
            if(key >= next_execution_time)
                key=0L;
            taskReady(key);
        }

        return retval;
    }



    public Future scheduleWithFixedDelay(Runnable task, long initial_delay, long delay, TimeUnit unit) {
        if(task == null)
            throw new NullPointerException();
        if (isShutdown())
            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())
            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.TimeScheduler2.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()) 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; Collection values=tasks.values(); for(Entry entry: values) retval+=entry.size(); return retval; } 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(); java.util.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) { } for(Entry entry: tasks.values()) entry.cancel(); tasks.clear(); } public boolean isShutdown() { return pool.isShutdown(); } public void run() { while(running) { try { _run(); } catch(Throwable t) { log.error("failed executing tasks(s)", t); } } } protected void _run() { ConcurrentNavigableMap head_map; // head_map = entries which are <= curr time (ready to be executed) if(!(head_map=tasks.headMap(System.currentTimeMillis(), true)).isEmpty()) { final List keys=new LinkedList(); for(Map.Entry entry: head_map.entrySet()) { final Long key=entry.getKey(); final Entry val=entry.getValue(); Runnable task=new Runnable() {public void run() {val.execute();}}; try { pool.execute(task); } catch(RejectedExecutionException rejected) { // only thrown if rejection policy is "abort" Thread thread=timer_thread_factory != null? timer_thread_factory.newThread(task, "Timer temp thread") : new Thread(task, "Timer temp thread"); thread.start(); } keys.add(key); } // we cannot use headMap.clear(); removed performance hotspot (https://issues.jboss.org/browse/JGRP-1490) for(Long key: keys) tasks.remove(key); } if(tasks.isEmpty()) { no_tasks.compareAndSet(false, true); waitFor(); // sleeps until time elapses, or a task with a lower execution time is added } else waitUntilNextExecution(); // waits until next execution, or a task with a lower execution time is added } protected void init() { startRunner(); } /** * Sleeps until the next task in line is ready to be executed */ protected void waitUntilNextExecution() { lock.lock(); try { if(!running) return; next_execution_time=tasks.firstKey(); long sleep_time=next_execution_time - System.currentTimeMillis(); tasks_available.await(sleep_time, TimeUnit.MILLISECONDS); } catch(InterruptedException e) { } finally { lock.unlock(); } } protected void waitFor() { lock.lock(); try { if(!running) return; tasks_available.await(SLEEP_TIME, TimeUnit.MILLISECONDS); } catch(InterruptedException e) { } finally { lock.unlock(); } } /** * Signals that a task with a lower execution time than next_execution_time is ready */ protected void taskReady(long trigger_time) { lock.lock(); try { if(trigger_time > 0) next_execution_time=trigger_time; tasks_available.signal(); } finally { lock.unlock(); } } 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; tasks_available.signal(); } finally { lock.unlock(); } } private static class Entry { private final MyTask task; // the task (wrapper) to execute private MyTask last; // points to the last task private final Lock lock=new ReentrantLock(); @GuardedBy("lock") private boolean completed=false; // set to true when the task has been executed private Entry(Runnable task) { last=this.task=new MyTask(task); } Future getFuture() { return task; } Future add(Runnable task) { lock.lock(); try { if(completed) return null; MyTask retval=new MyTask(task); last.next=retval; last=last.next; return retval; } finally { lock.unlock(); } } void execute() { lock.lock(); try { if(completed) return; completed=true; for(MyTask tmp=task; tmp != null; tmp=tmp.next) { if(!(tmp.isCancelled() || tmp.isDone())) { try { tmp.run(); } catch(Throwable t) { log.error("task execution failed", t); } finally { tmp.done=true; } } } } finally { lock.unlock(); } } void cancel() { lock.lock(); try { if(completed) return; for(MyTask tmp=task; tmp != null; tmp=tmp.next) tmp.cancel(true); } finally { lock.unlock(); } } int size() { int retval=1; for(MyTask tmp=task.next; tmp != null; tmp=tmp.next) retval++; return retval; } public String toString() { return size() + " tasks"; } public String dump() { StringBuilder sb=new StringBuilder(); boolean first=true; for(MyTask tmp=task; tmp != null; tmp=tmp.next) { if(!first) sb.append(", "); else first=false; sb.append(tmp); } return sb.toString(); } } /** * 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 MyTask(Runnable task) { this.task=task; } 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; private 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(task + ", cancelled=" + isCancelled()); return sb.toString(); } } private class FixedIntervalTask extends RecurringTask { final long interval; private 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; private 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 { private 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