package org.jgroups.util; import org.jgroups.Global; import org.jgroups.logging.Log; import org.jgroups.logging.LogFactory; import java.util.List; import java.util.concurrent.*; import java.util.function.Consumer; import java.util.function.Function; /** * Implementation of {@link TimeScheduler}. Uses a {@link DelayQueue} to order tasks according to execution times * @author Bela Ban * @since 3.3 */ public class TimeScheduler3 implements TimeScheduler, Runnable { /** Thread pool used to execute the tasks */ protected Executor pool; /** DelayQueue with tasks being sorted according to execution times (next execution first) */ protected final BlockingQueue<Task> queue=new DelayQueue<>(); /** Thread which removes tasks ready to be executed from the queue and submits them to the pool for execution */ protected volatile Thread runner; protected static final Log log=LogFactory.getLog(TimeScheduler3.class); protected ThreadFactory timer_thread_factory; // if true, non-blocking timer tasks are run directly by the runner thread and not submitted to the thread pool protected boolean non_blocking_task_handling=true; protected enum TaskType {dynamic, fixed_rate, fixed_delay} /** * Create a scheduler that executes tasks in dynamically adjustable intervals */ public TimeScheduler3() { pool=new ThreadPoolExecutor(4, 10, 30000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(100), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy()); start(); } public TimeScheduler3(ThreadFactory factory, int min_threads, int max_threads, long keep_alive_time, int max_queue_size, String rejection_policy) { this(factory, min_threads, max_threads, keep_alive_time, new ArrayBlockingQueue<>(max_queue_size), rejection_policy, true); } public TimeScheduler3(ThreadFactory factory, int min_threads, int max_threads, long keep_alive_time, BlockingQueue<Runnable> queue, String rejection_policy, boolean thread_pool_enabled) { timer_thread_factory=factory; pool=thread_pool_enabled? new ThreadPoolExecutor(min_threads, max_threads,keep_alive_time, TimeUnit.MILLISECONDS, queue, factory, Util.parseRejectionPolicy(rejection_policy)) : new DirectExecutor(); start(); } public TimeScheduler3(Executor thread_pool, ThreadFactory factory) { timer_thread_factory=factory; pool=thread_pool; start(); } public void setThreadFactory(ThreadFactory f) {condSet((p) -> p.setThreadFactory(f));} public void setThreadPool(Executor new_pool) {pool=new_pool;} public int getMinThreads() {return condGet(ThreadPoolExecutor::getCorePoolSize, 0);} public void setMinThreads(int size) {condSet(p -> p.setCorePoolSize(size));} public int getMaxThreads() {return condGet(ThreadPoolExecutor::getMaximumPoolSize, 0);} public void setMaxThreads(int size) {condSet(p -> p.setMaximumPoolSize(size));} public long getKeepAliveTime() {return condGet(p -> p.getKeepAliveTime(TimeUnit.MILLISECONDS), 0L);} public void setKeepAliveTime(long time) {condSet(p -> p.setKeepAliveTime(time, TimeUnit.MILLISECONDS));} public int getCurrentThreads() {return condGet(ThreadPoolExecutor::getPoolSize, 0);} public int getQueueSize() {return condGet(p -> p.getQueue().size(), 0);} public int size() {return queue.size();} public String toString() {return getClass().getSimpleName();} public boolean isShutdown() {return condGet(ThreadPoolExecutor::isShutdown, false);} public boolean getNonBlockingTaskHandling() {return non_blocking_task_handling;} public void setNonBlockingTaskHandling(boolean b) {this.non_blocking_task_handling=b;} public String dumpTimerTasks() { StringBuilder sb=new StringBuilder(); for(Task task: queue) { sb.append(task); if(task.isCancelled()) sb.append(" (cancelled)"); sb.append("\n"); } return sb.toString(); } public void execute(Runnable task, boolean can_block) { submitToPool(task instanceof TimeScheduler.Task? new RecurringTask(task, TaskType.dynamic, 0, ((TimeScheduler.Task)task).nextInterval(), TimeUnit.MILLISECONDS, can_block) : new Task(task, can_block)); // we'll execute the task directly } public Future<?> schedule(Runnable work, long initial_delay, TimeUnit unit, boolean can_block) { return doSchedule(new Task(work, initial_delay, unit, can_block), initial_delay); } public Future<?> scheduleWithFixedDelay(Runnable work, long initial_delay, long delay, TimeUnit unit, boolean can_block) { return scheduleRecurring(work, TaskType.fixed_delay, initial_delay, delay, unit, can_block); } public Future<?> scheduleAtFixedRate(Runnable work, long initial_delay, long delay, TimeUnit unit, boolean can_block) { return scheduleRecurring(work,TaskType.fixed_rate,initial_delay,delay,unit, can_block); } /** * Schedule a task for execution at varying intervals. After execution, the task will get rescheduled after * {@link org.jgroups.util.TimeScheduler.Task#nextInterval()} milliseconds. The task is never done until * nextInterval() returns a value <= 0 or the task is cancelled.<p/> * Note that the task is rescheduled relative to the last time is actually executed. This is similar to * {@link #scheduleWithFixedDelay(Runnable,long,long,java.util.concurrent.TimeUnit)}. * @param work the task to execute */ public Future<?> scheduleWithDynamicInterval(TimeScheduler.Task work, boolean can_block) { return scheduleRecurring(work, TaskType.dynamic, work.nextInterval(), 0, TimeUnit.MILLISECONDS, can_block); } protected void start() { startRunner(); } /** * Stops the timer, cancelling all tasks */ public void stop() { stopRunner(); // we may need to do multiple iterations as the iterator works on a copy and tasks might have been added just // after the iterator() call returned while(!queue.isEmpty()) for(Task entry: queue) { entry.cancel(true); queue.remove(entry); } queue.clear(); if(pool instanceof ThreadPoolExecutor) { ThreadPoolExecutor p=(ThreadPoolExecutor)pool; List<Runnable> remaining_tasks=p.shutdownNow(); remaining_tasks.stream().filter(task -> task instanceof Future).forEach(task -> ((Future)task).cancel(true)); p.getQueue().clear(); try { p.awaitTermination(Global.THREADPOOL_SHUTDOWN_WAIT_TIME, TimeUnit.MILLISECONDS); } catch(InterruptedException e) { } } // clears the threads list (https://issues.jboss.org/browse/JGRP-1971) if(timer_thread_factory instanceof LazyThreadFactory) ((LazyThreadFactory)timer_thread_factory).destroy(); } public void run() { while(Thread.currentThread() == runner) { try { Task task=queue.take(); submitToPool(task); } catch(InterruptedException interrupted) { // flag is cleared and we check if the loop should be terminated at the top of the loop } catch(Throwable t) { log.error(Util.getMessage("FailedSubmittingTaskToThreadPool"), t); } } } protected Future<?> scheduleRecurring(Runnable work, TaskType type, long initial_delay, long delay, TimeUnit unit, boolean can_block) { return doSchedule(new RecurringTask(work, type, initial_delay, delay, unit, can_block), initial_delay); } protected Future<?> doSchedule(Task task, long initial_delay) { if(task.getRunnable() == null) throw new NullPointerException(); if (isShutdown()) return null; if(initial_delay <= 0) { submitToPool(task); return task; } return add(task); } protected void condSet(Consumer<ThreadPoolExecutor> setter) { if(pool instanceof ThreadPoolExecutor) setter.accept((ThreadPoolExecutor)pool); } protected <T> T condGet(Function<ThreadPoolExecutor,T> getter, T default_value) { if(pool instanceof ThreadPoolExecutor) return getter.apply((ThreadPoolExecutor)pool); return default_value; } protected void submitToPool(Task task) { if(non_blocking_task_handling && !task.canBlock()) { task.run(); return; } 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(); } } protected Task add(Task task) { if(!isRunning()) return null; queue.add(task); return task; } protected boolean isRunning() { Thread tmp=runner; return tmp != null && tmp.isAlive(); } protected synchronized void startRunner() { stopRunner(); runner=timer_thread_factory != null? timer_thread_factory.newThread(this, "Timer runner") : new Thread(this, "Timer runner"); runner.start(); } protected synchronized void stopRunner() { Thread tmp=runner; runner=null; if(tmp != null) { tmp.interrupt(); try {tmp.join(500);} catch(InterruptedException e) {} } queue.clear(); } public static class Task implements Runnable, Delayed, Future { protected final Runnable runnable; // the task to execute protected long creation_time; // time (in ns) at which the task was created protected long delay; // time (in ns) after which the task should execute protected volatile boolean cancelled; protected volatile boolean done; protected final boolean can_block; public Task(Runnable runnable, boolean can_block) { this.runnable=runnable; this.can_block=can_block; } public Task(Runnable runnable, long initial_delay, TimeUnit unit, boolean can_block) { this.can_block=can_block; this.creation_time=System.nanoTime(); this.delay=TimeUnit.NANOSECONDS.convert(initial_delay, unit); this.runnable=runnable; if(runnable == null) throw new IllegalArgumentException("runnable cannot be null"); } public Runnable getRunnable() {return runnable;} public boolean canBlock() {return can_block;} public int compareTo(Delayed o) { long my_delay=getDelay(TimeUnit.NANOSECONDS), other_delay=o.getDelay(TimeUnit.NANOSECONDS); // return Long.compare(my_delay, other_delay); // JDK 7 only return (my_delay < other_delay) ? -1 : ((my_delay == other_delay) ? 0 : 1); } public long getDelay(TimeUnit unit) { // time (in ns) until execution, can be negative when already elapsed long remaining_time=delay - (System.nanoTime() - creation_time); return unit.convert(remaining_time, TimeUnit.NANOSECONDS); } 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 { runnable.run(); } catch(Throwable t) { log.error(Util.getMessage("FailedExecutingTask") + runnable, t); } finally { done=true; } } public String toString() { return String.format("%s (can block=%b)", runnable.toString(), can_block); } } /** Tasks which runs more than once, either dynamic, fixed-rate or fixed-delay, until cancelled */ protected class RecurringTask extends Task { protected final TaskType type; protected final long period; // ns protected final long initial_delay; // ns protected int cnt=1; // number of invocations (for fixed rate invocations) public RecurringTask(Runnable runnable, TaskType type, long initial_delay, long delay, TimeUnit unit, boolean can_block) { super(runnable, initial_delay, unit, can_block); this.initial_delay=TimeUnit.NANOSECONDS.convert(initial_delay, TimeUnit.MILLISECONDS); this.type=type; period=TimeUnit.NANOSECONDS.convert(delay, unit); if(type == TaskType.dynamic && !(runnable instanceof TimeScheduler.Task)) throw new IllegalArgumentException("Need to provide a TimeScheduler.Task as runnable when type is dynamic"); } public void run() { if(isDone()) return; super.run(); if(cancelled) return; done=false; // run again switch(type) { case dynamic: long next_interval=TimeUnit.NANOSECONDS.convert(((TimeScheduler.Task)runnable).nextInterval(), TimeUnit.MILLISECONDS); if(next_interval <= 0) { if(log.isTraceEnabled()) log.trace("task will not get rescheduled as interval is " + next_interval); done=true; return; } creation_time=System.nanoTime(); delay=next_interval; break; case fixed_rate: delay=initial_delay + cnt++ * period; break; case fixed_delay: creation_time=System.nanoTime(); delay=period; break; } add(this); // schedule this task again } } }