/** * Construct a new poller which will run on the provided scheduler, and run at the specified * frequency. * <p> * This constructor additionally allows you to specify the maximum time in millseconds we should * wait for the condition to become true. At this point if we are still not seeing our expected * polling state, then the return future will be canceled. * * @param scheduler Scheduler to run polling task on * @param pollFrequency Time in milliseconds to wait between polling events * @param maxWaitTime Maximum time in milliseconds till returned futures should be canceled */ public Poller(SchedulerService scheduler, long pollFrequency, long maxWaitTime) { if (maxWaitTime > 0 && maxWaitTime != Long.MAX_VALUE) { futureWatchdog = new Watchdog(scheduler, maxWaitTime, false); } else { futureWatchdog = null; } this.runner = new PollRunner(scheduler, pollFrequency); }
@Override public void run() { Iterator<Watchdog> it = cachedDogs.values().iterator(); while (it.hasNext()) { if (! it.next().isActive()) { it.remove(); } } // must set unscheduled before checking if we need to reschedule cleanerScheduled.set(false); if (! cachedDogs.isEmpty()) { maybeScheduleCleaner(); } } }
/** * Watch suppliers returned condition. Once Supplier is witnessed in the {@code true} state, the * returned future is completed. Listeners and FutureCallback's executed on the returned future * without a specified pool will run on the polling thread, and so should be kept to a minimum. * * @param p Supplier to provide state for when poll has completed as expected * @return Future to complete once boolean state is witnessed as {@code true} */ public ListenableFuture<?> watch(Supplier<Boolean> p) { ListenableFuture<?> result = runner.watch(p); if (futureWatchdog != null) { futureWatchdog.watch(result); } return result; }
@Test public void isActiveTest() { assertFalse(watchdog.isActive()); ListenableFuture<?> future = FutureUtils.immediateResultFuture(null); watchdog.watch(future); assertFalse(watchdog.isActive()); SettableListenableFuture<?> slf = new SettableListenableFuture<>(); watchdog.watch(slf); assertTrue(watchdog.isActive()); TestUtils.blockTillClockAdvances(); assertEquals(1, scheduler.tick(null)); assertFalse(watchdog.isActive()); }
@Test public void rescheduledFutureCheckTest() throws InterruptedException { long delayTime = 100; // longer than constants DELAY_TIME to ensure we can tick BEFORE the second future times out watchdog = new Watchdog(scheduler, delayTime * 2, true); SettableListenableFuture<?> slf1 = new SettableListenableFuture<>(); watchdog.watch(slf1); TestUtils.sleep(delayTime); SettableListenableFuture<?> slf2 = new SettableListenableFuture<>(); watchdog.watch(slf2); assertEquals(1, scheduler.blockingTick(null)); assertTrue(slf1.isCancelled()); assertFalse(slf2.isCancelled()); assertEquals(1, scheduler.blockingTick(null)); assertTrue(slf1.isCancelled()); assertTrue(slf2.isCancelled()); } }
/** * Constructs a new {@link Watchdog}. This constructor will use a default static scheduler * (which is lazily constructed). This should be fine in most cases, but you can provide your * own scheduler if you have specific needs where the {@link CentralThreadlyPool} default is not * a good option. * * @param timeoutInMillis Time in milliseconds that futures will be set to error if they are not done * @param sendInterruptOnFutureCancel If {@code true}, and a thread is provided with the future, * an interrupt will be sent on timeout */ public Watchdog(long timeoutInMillis, boolean sendInterruptOnFutureCancel) { this(getStaticScheduler(), timeoutInMillis, sendInterruptOnFutureCancel); }
@Test public void getTimeoutInMillisTest() { assertEquals(TIMEOUT, watchdog.getTimeoutInMillis()); }
/** * Constructs a new {@link Watchdog}. This constructor will use a default static scheduler * (which is lazily constructed). This should be fine in most cases, but you can provide your * own scheduler if you have specific needs where the {@link CentralThreadlyPool} default is not * a good option. * * @param timeoutInMillis Time in milliseconds that futures will be set to error if they are not done * @param sendInterruptOnFutureCancel If {@code true}, and a thread is provided with the future, * an interrupt will be sent on timeout */ public Watchdog(long timeoutInMillis, boolean sendInterruptOnFutureCancel) { this(getStaticScheduler(), timeoutInMillis, sendInterruptOnFutureCancel); }
/** * Watch suppliers returned condition. Once Supplier is witnessed in the {@code true} state, the * returned future is completed. Listeners and FutureCallback's executed on the returned future * without a specified pool will run on the polling thread, and so should be kept to a minimum. * * @param p Supplier to provide state for when poll has completed as expected * @return Future to complete once boolean state is witnessed as {@code true} */ public ListenableFuture<?> watch(Supplier<Boolean> p) { ListenableFuture<?> result = runner.watch(p); if (futureWatchdog != null) { futureWatchdog.watch(result); } return result; }
/** * Construct a new poller which will run on the provided scheduler, and run at the specified * frequency. * <p> * This constructor additionally allows you to specify the maximum time in millseconds we should * wait for the condition to become true. At this point if we are still not seeing our expected * polling state, then the return future will be canceled. * * @param scheduler Scheduler to run polling task on * @param pollFrequency Time in milliseconds to wait between polling events * @param maxWaitTime Maximum time in milliseconds till returned futures should be canceled */ public Poller(SchedulerService scheduler, long pollFrequency, long maxWaitTime) { if (maxWaitTime > 0 && maxWaitTime != Long.MAX_VALUE) { futureWatchdog = new Watchdog(scheduler, maxWaitTime, false); } else { futureWatchdog = null; } this.runner = new PollRunner(scheduler, pollFrequency); }
/** * Constructs a new {@link WatchdogCache}. This constructor will use a default static scheduler * (which is lazily constructed). This should be fine in most cases, but you can provide your * own scheduler if you want to avoid the thread creation (which is shared among all instances * that were constructed with this constructor or {@link Watchdog#Watchdog(long, boolean)}}. * * @deprecated Please use {@link #centralWatchdogCache(boolean)} * * @param sendInterruptOnFutureCancel If {@code true}, and a thread is provided with the future, * an interrupt will be sent on timeout */ @Deprecated public WatchdogCache(boolean sendInterruptOnFutureCancel) { this(Watchdog.getStaticScheduler(), sendInterruptOnFutureCancel, DEFAULT_RESOLUTION_MILLIS); }
@Override public void run() { Iterator<Watchdog> it = cachedDogs.values().iterator(); while (it.hasNext()) { if (! it.next().isActive()) { it.remove(); } } // must set unscheduled before checking if we need to reschedule cleanerScheduled.set(false); if (! cachedDogs.isEmpty()) { maybeScheduleCleaner(); } } }
/** * Watch a given {@link ListenableFuture} to ensure that it completes within the provided * time limit. If the future is not marked as done by the time limit then it will be * completed by invoking {@link ListenableFuture#cancel(boolean)}. Weather a {@code true} or * {@code false} will be provided to interrupt the running thread is dependent on how this * {@link WatchdogCache} was constructed. * * @param future Future to inspect to ensure completion * @param timeoutInMillis Time in milliseconds that future should be completed within */ public void watch(ListenableFuture<?> future, long timeoutInMillis) { long adjustedTimeout = timeoutInMillis / resolutionMillis; adjustedTimeout *= resolutionMillis; // int division to zero out if (adjustedTimeout != timeoutInMillis) { adjustedTimeout += resolutionMillis; // prefer timing out later rather than early } // attempt around a cheap shortcut if (future == null || future.isDone()) { return; } cachedDogs.computeIfAbsent(adjustedTimeout, watchdogProducer) .watch(future); }
watchdogProducer = (timeout) -> { maybeScheduleCleaner(); return new Watchdog(scheduler, timeout, sendInterruptOnFutureCancel); }; cacheCleaner = new CleanRunner();
/** * Constructs a new {@link WatchdogCache}. This constructor will use a default static scheduler * (which is lazily constructed). This should be fine in most cases, but you can provide your * own scheduler if you want to avoid the thread creation (which is shared among all instances * that were constructed with this constructor or {@link Watchdog#Watchdog(long, boolean)}}. * * @deprecated Please use {@link #centralWatchdogCache(boolean)} * * @param sendInterruptOnFutureCancel If {@code true}, and a thread is provided with the future, * an interrupt will be sent on timeout */ @Deprecated public WatchdogCache(boolean sendInterruptOnFutureCancel) { this(Watchdog.getStaticScheduler(), sendInterruptOnFutureCancel, DEFAULT_RESOLUTION_MILLIS); }
/** * Convert a conventional {@link Future} into a {@link ListenableFuture}. As poller runs it * will check if the provided future has completed. Once it does complete the returned future * will also complete in the exact same way. Canceling the returned future will have NO impact * on the provided future (and thus the use with a timeout is not a concern to interrupting the * provided future). Because this is only checked at poll intervals the returned future's * completion will be delayed by that polling delay. * * @param <T> Type of object returned from future * @param f Future to monitor for completetion * @return ListenableFuture that will provide the result from the source future */ @SuppressWarnings("unchecked") public <T> ListenableFuture<T> watch(Future<? extends T> f) { if ((futureWatchdog == null || f.isDone()) && f instanceof ListenableFuture) { return (ListenableFuture<T>)f; } ListenableFuture<T> result = runner.watch(f); if (futureWatchdog != null) { futureWatchdog.watch(result); } return result; }
watchdogProducer = (timeout) -> { maybeScheduleCleaner(); return new Watchdog(scheduler, timeout, sendInterruptOnFutureCancel); }; cacheCleaner = new CleanRunner();
/** * Return a static / shared {@link WatchdogCache} instance. This instance is backed by the * {@link org.threadly.concurrent.CentralThreadlyPool} which should be fine in most cases, but if * you have specific needs you can construct your own instance by * {@link #WatchdogCache(SubmitterScheduler, boolean)}, or if you need to specify a specific * timeout resolution using the {@link #WatchdogCache(SubmitterScheduler, boolean, long)} * constructor. * <p> * As long as those special cases are not needed, using a shared instance allows for potentially * improved efficiency. * * @since 5.19 * @param sendInterruptOnFutureCancel If {@code true}, and a thread is provided with the future, * an interrupt will be sent on timeout * @return A shared {@link WatchdogCache} with the specified configuration */ public static final WatchdogCache centralWatchdogCache(boolean sendInterruptOnFutureCancel) { AtomicReference<WatchdogCache> ar = sendInterruptOnFutureCancel ? INTERRUPTING_WATCHDOG_CACHE : NONINTERRUPTING_WATCHDOG_CACHE; WatchdogCache wd = ar.get(); if (wd == null) { ar.compareAndSet(null, new WatchdogCache(Watchdog.getStaticScheduler(), sendInterruptOnFutureCancel)); wd = ar.get(); } return wd; }
/** * Watch a given {@link ListenableFuture} to ensure that it completes within the provided * time limit. If the future is not marked as done by the time limit then it will be * completed by invoking {@link ListenableFuture#cancel(boolean)}. Weather a {@code true} or * {@code false} will be provided to interrupt the running thread is dependent on how this * {@link WatchdogCache} was constructed. * * @param future Future to inspect to ensure completion * @param timeoutInMillis Time in milliseconds that future should be completed within */ public void watch(ListenableFuture<?> future, long timeoutInMillis) { long adjustedTimeout = timeoutInMillis / resolutionMillis; adjustedTimeout *= resolutionMillis; // int division to zero out if (adjustedTimeout != timeoutInMillis) { adjustedTimeout += resolutionMillis; // prefer timing out later rather than early } // attempt around a cheap shortcut if (future == null || future.isDone()) { return; } cachedDogs.computeIfAbsent(adjustedTimeout, watchdogProducer) .watch(future); }
@Before public void setup() { scheduler = new NoThreadScheduler(); watchdog = new Watchdog(scheduler, TIMEOUT, true); }