All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
io.activej.async.service.EventloopTaskScheduler Maven / Gradle / Ivy
Go to download
A convenient way to organize asynchronous code.
Promises are a faster and more efficient version of JavaScript's Promise and Java's CompletionStage's.
/*
* Copyright (C) 2020 ActiveJ LLC.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.activej.async.service;
import io.activej.async.function.AsyncSupplier;
import io.activej.async.function.AsyncSuppliers;
import io.activej.common.api.WithInitializer;
import io.activej.eventloop.Eventloop;
import io.activej.eventloop.jmx.EventloopJmxBeanEx;
import io.activej.eventloop.schedule.ScheduledRunnable;
import io.activej.jmx.api.attribute.JmxAttribute;
import io.activej.jmx.api.attribute.JmxOperation;
import io.activej.promise.Promise;
import io.activej.promise.RetryPolicy;
import io.activej.promise.jmx.PromiseStats;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.Duration;
import static io.activej.common.Utils.nullify;
import static io.activej.promise.Promises.retry;
@SuppressWarnings("UnusedReturnValue")
public final class EventloopTaskScheduler implements EventloopService, WithInitializer, EventloopJmxBeanEx {
private static final Logger logger = LoggerFactory.getLogger(EventloopTaskScheduler.class);
private final Eventloop eventloop;
private final AsyncSupplier task;
private final PromiseStats stats = PromiseStats.create(Duration.ofMinutes(5));
private long initialDelay;
private Schedule schedule;
private RetryPolicy retryPolicy;
private boolean abortOnError = false;
private long lastStartTime;
private long lastCompleteTime;
@Nullable
private Throwable lastException;
private int errorCount;
@Nullable
private Duration period;
@Nullable
private Duration interval;
private boolean enabled = true;
@FunctionalInterface
public interface Schedule {
long nextTimestamp(long now, long lastStartTime, long lastCompleteTime);
/**
* Schedules immediate execution.
*/
static Schedule immediate() {
return (now, lastStartTime, lastCompleteTime) -> now;
}
/**
* Schedules task after delay.
*/
static Schedule ofDelay(Duration delay) {
return ofDelay(delay.toMillis());
}
/**
* @see #ofDelay(Duration)
*/
static Schedule ofDelay(long delay) {
return (now, lastStartTime, lastCompleteTime) -> now + delay;
}
/**
* @see #ofInterval(long)
*/
static Schedule ofInterval(Duration interval) {
return ofInterval(interval.toMillis());
}
/**
* Schedules task after last complete time and next task.
*/
static Schedule ofInterval(long interval) {
return (now, lastStartTime, lastCompleteTime) -> lastCompleteTime + interval;
}
/**
* @see #ofPeriod(long)
*/
static Schedule ofPeriod(Duration period) {
return ofPeriod(period.toMillis());
}
/**
* Schedules task in period of current and next task.
*/
static Schedule ofPeriod(long period) {
return (now, lastStartTime, lastCompleteTime) -> lastStartTime + period;
}
}
@Nullable
private ScheduledRunnable scheduledTask;
@Nullable
private Promise currentPromise;
private EventloopTaskScheduler(Eventloop eventloop, AsyncSupplier> task) {
this.eventloop = eventloop;
//noinspection unchecked
this.task = (AsyncSupplier) task;
}
public static EventloopTaskScheduler create(Eventloop eventloop, AsyncSupplier task) {
return new EventloopTaskScheduler(eventloop, task);
}
public EventloopTaskScheduler withInitialDelay(Duration initialDelay) {
this.initialDelay = initialDelay.toMillis();
return this;
}
public EventloopTaskScheduler withSchedule(Schedule schedule) {
this.schedule = schedule;
// for JMX:
this.period = null;
this.interval = null;
return this;
}
public EventloopTaskScheduler withPeriod(Duration period) {
setPeriod(period);
return this;
}
public EventloopTaskScheduler withInterval(Duration interval) {
setInterval(interval);
return this;
}
public EventloopTaskScheduler withRetryPolicy(RetryPolicy> retryPolicy) {
//noinspection unchecked
this.retryPolicy = (RetryPolicy) retryPolicy;
return this;
}
public EventloopTaskScheduler withAbortOnError(boolean abortOnError) {
this.abortOnError = abortOnError;
return this;
}
public EventloopTaskScheduler withStatsHistogramLevels(int[] levels) {
this.stats.setHistogram(levels);
return this;
}
@NotNull
@Override
public Eventloop getEventloop() {
return eventloop;
}
private void scheduleTask() {
if (schedule == null || scheduledTask != null && scheduledTask.isCancelled())
return;
if (!enabled) return;
long now = eventloop.currentTimeMillis();
long timestamp;
if (lastStartTime == 0) {
timestamp = now + initialDelay;
} else {
timestamp = schedule.nextTimestamp(now, lastStartTime, lastCompleteTime);
}
scheduledTask = eventloop.scheduleBackground(timestamp, doCall::get);
}
private final AsyncSupplier doCall = AsyncSuppliers.reuse(this::doCall);
private Promise doCall() {
lastStartTime = eventloop.currentTimeMillis();
return currentPromise = (retryPolicy == null ?
task.get() :
retry(task, ($, e) -> e == null || !enabled, retryPolicy))
.whenComplete(stats.recordStats())
.whenComplete((result, e) -> {
if (!enabled) return;
lastCompleteTime = eventloop.currentTimeMillis();
if (e == null) {
lastException = null;
errorCount = 0;
scheduleTask();
} else {
lastException = e;
errorCount++;
logger.error("Retry attempt " + errorCount, e);
if (abortOnError) {
scheduledTask = nullify(scheduledTask, ScheduledRunnable::cancel);
throw new RuntimeException(e);
} else {
scheduleTask();
}
}
})
.toVoid();
}
@NotNull
@Override
public Promise start() {
scheduleTask();
return Promise.complete();
}
@NotNull
@Override
public Promise stop() {
enabled = false;
scheduledTask = nullify(scheduledTask, ScheduledRunnable::cancel);
if (currentPromise == null) {
return Promise.complete();
}
return currentPromise.mapEx(($, e) -> null);
}
public void setSchedule(Schedule schedule) {
this.schedule = schedule;
if (stats.getActivePromises() != 0 && scheduledTask != null && !scheduledTask.isCancelled()) {
scheduledTask = nullify(scheduledTask, ScheduledRunnable::cancel);
scheduleTask();
}
}
public void setRetryPolicy(RetryPolicy> retryPolicy) {
//noinspection unchecked
this.retryPolicy = (RetryPolicy) retryPolicy;
if (stats.getActivePromises() != 0 && scheduledTask != null && !scheduledTask.isCancelled() && lastException != null) {
scheduledTask = nullify(scheduledTask, ScheduledRunnable::cancel);
scheduleTask();
}
}
@JmxAttribute
public boolean isEnabled() {
return enabled;
}
@JmxAttribute
public void setEnabled(boolean enabled) {
if (this.enabled == enabled) return;
this.enabled = enabled;
if (stats.getActivePromises() == 0) {
if (enabled) {
scheduleTask();
} else {
scheduledTask = nullify(scheduledTask, ScheduledRunnable::cancel);
}
}
}
@JmxAttribute(name = "")
public PromiseStats getStats() {
return stats;
}
@JmxAttribute
@Nullable
public Throwable getLastException() {
return lastException;
}
@JmxAttribute
public long getInitialDelay() {
return initialDelay;
}
@JmxAttribute
@Nullable
public Duration getPeriod() {
return period;
}
@JmxAttribute
public void setPeriod(Duration period) {
Schedule schedule = Schedule.ofPeriod(period);
setSchedule(schedule);
// for JMX:
this.period = period;
this.interval = null;
}
@JmxAttribute
@Nullable
public Duration getInterval() {
return interval;
}
@JmxAttribute
public void setInterval(Duration interval) {
setSchedule(Schedule.ofInterval(interval));
// for JMX:
this.period = null;
this.interval = interval;
}
@JmxOperation
public void startNow() {
doCall.get();
}
}