
com.graphaware.runtime.schedule.RotatingTaskScheduler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of runtime Show documentation
Show all versions of runtime Show documentation
Runtime for GraphAware Runtime Modules
/*
* Copyright (c) 2013-2016 GraphAware
*
* This file is part of the GraphAware Framework.
*
* GraphAware Framework is free software: you can redistribute it and/or modify it under the terms of
* the GNU General Public License as published by the Free Software Foundation, either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. You should have received a copy of
* the GNU General Public License along with this program. If not, see
* .
*/
package com.graphaware.runtime.schedule;
import com.graphaware.common.util.Pair;
import com.graphaware.runtime.config.TimerDrivenModuleConfiguration;
import com.graphaware.runtime.metadata.DefaultTimerDrivenModuleMetadata;
import com.graphaware.runtime.metadata.ModuleMetadataRepository;
import com.graphaware.runtime.metadata.TimerDrivenModuleContext;
import com.graphaware.runtime.module.TimerDrivenModule;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Transaction;
import org.neo4j.kernel.internal.GraphDatabaseAPI;
import org.neo4j.kernel.internal.KernelData;
import org.neo4j.logging.Log;
import org.neo4j.management.HighAvailability;
import org.neo4j.management.Neo4jManager;
import org.slf4j.Logger;
import com.graphaware.common.log.LoggerFactory;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import static com.graphaware.runtime.config.TimerDrivenModuleConfiguration.InstanceRolePolicy.*;
import static com.graphaware.runtime.schedule.TimingStrategy.NEVER_RUN;
import static com.graphaware.runtime.schedule.TimingStrategy.UNKNOWN;
/**
* {@link TaskScheduler} that delegates to the registered {@link TimerDrivenModule}s in round-robin fashion, in the order
* in which the modules were registered. All work performed by this implementation is done by a single thread.
*/
public class RotatingTaskScheduler implements TaskScheduler {
private static final Log LOG = LoggerFactory.getLogger(RotatingTaskScheduler.class);
private final GraphDatabaseService database;
private final ModuleMetadataRepository repository;
private final TimingStrategy timingStrategy;
//these two should be made concurrent if we use more than 1 thread for the background work
private final Map moduleContexts = new LinkedHashMap<>();
private Iterator> moduleContextIterator;
private final ScheduledExecutorService worker = Executors.newSingleThreadScheduledExecutor();
private final HighAvailability haBean;
/**
* Construct a new task scheduler.
*
* @param database against which the modules are running.
* @param repository for persisting metadata.
* @param timingStrategy strategy for timing the work delegation.
*/
public RotatingTaskScheduler(GraphDatabaseService database, ModuleMetadataRepository repository, TimingStrategy timingStrategy) {
this.database = database;
this.repository = repository;
this.timingStrategy = timingStrategy;
this.haBean = getHaBean();
}
private HighAvailability getHaBean() {
try {
HighAvailability bean = Neo4jManager.get(((GraphDatabaseAPI) database).getDependencyResolver().resolveDependency(KernelData.class).instanceId()).getHighAvailabilityBean();
LOG.info("Running in a clustered architecture, obtained HA bean.");
return bean;
} catch (NoSuchElementException e) {
LOG.info("Running in a single-node architecture.");
return null;
} catch (Exception e) {
LOG.warn("Failed to obtain Neo4j Manager, assuming a single-node architecture.", e);
return null;
} catch (NoClassDefFoundError error) {
LOG.info("Running in a single-node architecture (Neo4j Community)");
return null;
}
}
/**
* {@inheritDoc}
*/
@Override
public > void registerModuleAndContext(T module, C context) {
if (moduleContextIterator != null) {
throw new IllegalStateException("Task scheduler can not accept modules after it has been started. This is a bug.");
}
LOG.info("Registering module " + module.getId() + " and its context with the task scheduler.");
moduleContexts.put(module, context);
}
/**
* {@inheritDoc}
*/
@Override
public void start() {
if (moduleContexts.isEmpty()) {
LOG.info("There are no timer-driven runtime modules. Not scheduling any tasks.");
return;
}
LOG.info("There are " + moduleContexts.size() + " timer-driven runtime modules. Scheduling the first task...");
timingStrategy.initialize(database);
scheduleNextTask(NEVER_RUN);
}
/**
* {@inheritDoc}
*/
@Override
public void stop() {
LOG.info("Terminating task scheduler...");
worker.shutdown();
try {
worker.awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
LOG.warn("Did not manage to finish all tasks in 5 seconds.");
}
LOG.info("Task scheduler terminated successfully.");
}
/**
* Schedule next task.
*
* @param lastTaskDuration duration of the last task in millis, negative if unknown.
*/
private void scheduleNextTask(long lastTaskDuration) {
long nextDelayMillis = timingStrategy.nextDelay(lastTaskDuration);
LOG.debug("Scheduling next task with a delay of %s ms.", nextDelayMillis);
worker.schedule(nextTask(), nextDelayMillis, TimeUnit.MILLISECONDS);
}
/**
* Create next task wrapped in a {@link Runnable}. The {@link Runnable} schedules the next task when finished.
*
* @return next task to be run wrapped in a {@link Runnable}.
*/
protected Runnable nextTask() {
return () -> {
long totalTime = UNKNOWN;
try {
LOG.debug("Running a scheduled task...");
long startTime = System.currentTimeMillis();
runNextTask();
totalTime = (System.currentTimeMillis() - startTime);
LOG.debug("Successfully completed scheduled task in " + totalTime + " ms");
} catch (Exception e) {
LOG.warn("Task execution threw an exception: " + e.getMessage(), e);
} finally {
scheduleNextTask(totalTime);
}
};
}
/**
* Run the next task.
*
* @param type of the context passed into the module below.
* @param module type of the module that will be delegated to.
*/
private > void runNextTask() {
if (!database.isAvailable(0)) {
LOG.warn("Database not available, probably shutting down...");
return;
}
Pair moduleAndContext = findNextModuleAndContext();
if (moduleAndContext == null) {
return; //no module wishes to run
}
T module = moduleAndContext.first();
C context = moduleAndContext.second();
try (Transaction tx = database.beginTx()) {
C newContext = module.doSomeWork(context, database);
repository.persistModuleMetadata(module, new DefaultTimerDrivenModuleMetadata(newContext));
moduleContexts.put(module, newContext);
tx.success();
}
}
/**
* Find the next module that is ready to be delegated to, and its context.
*
* @param context type.
* @param module type.
* @return module & context.
*/
private > Pair findNextModuleAndContext() {
int totalModules = moduleContexts.size();
long now = System.currentTimeMillis();
for (int i = 0; i < totalModules; i++) {
Pair candidate = nextModuleAndContext();
if (hasCorrectRole(candidate.first()) && (candidate.second() == null || candidate.second().earliestNextCall() <= now)) {
return candidate;
}
}
return null;
}
/**
* Check if the given module has the correct role (e.g. master or slave) to run.
*
* @param module to check for.
* @return true
iff can run.
*/
private boolean hasCorrectRole(TimerDrivenModule> module) {
if (haBean == null) {
return true; //no HA
}
TimerDrivenModuleConfiguration.InstanceRolePolicy policy = module.getConfiguration().getInstanceRolePolicy();
if (policy.equals(ANY)) {
return true;
}
String currentRole = haBean.getRole();
return "master".equals(currentRole) && policy.equals(MASTER_ONLY) || "slave".equals(currentRole) && policy.equals(SLAVES_ONLY);
}
/**
* Find the next module whose turn it would be and its context.
*
* @param context type.
* @param module type.
* @return module & context.
*/
private > Pair nextModuleAndContext() {
if (moduleContextIterator == null || !moduleContextIterator.hasNext()) {
moduleContextIterator = moduleContexts.entrySet().iterator();
}
Map.Entry entry = moduleContextIterator.next();
//noinspection unchecked
return new Pair<>((T) entry.getKey(), (C) entry.getValue());
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy