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.
org.mycore.services.queuedjob.MCRJobMaster Maven / Gradle / Ivy
/*
* This file is part of *** M y C o R e ***
* See http://www.mycore.de/ for details.
*
* MyCoRe 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.
*
* MyCoRe 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 MyCoRe. If not, see .
*/
package org.mycore.services.queuedjob;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.PersistenceException;
import javax.persistence.RollbackException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.mycore.backend.jpa.MCREntityManagerProvider;
import org.mycore.common.MCRSession;
import org.mycore.common.MCRSessionMgr;
import org.mycore.common.MCRSystemUserInformation;
import org.mycore.common.config.MCRConfiguration2;
import org.mycore.common.events.MCRShutdownHandler;
import org.mycore.common.events.MCRShutdownHandler.Closeable;
import org.mycore.common.inject.MCRInjectorConfig;
import org.mycore.common.processing.MCRProcessableCollection;
import org.mycore.common.processing.MCRProcessableDefaultCollection;
import org.mycore.common.processing.MCRProcessableRegistry;
import org.mycore.util.concurrent.processing.MCRProcessableExecutor;
import org.mycore.util.concurrent.processing.MCRProcessableFactory;
/**
* The master of all {@link MCRJobThread}s threads.
*
* @author Ren\u00E9 Adler
*/
public class MCRJobMaster implements Runnable, Closeable {
private static Map INSTANCES = new HashMap<>();
private static Logger LOGGER = LogManager.getLogger(MCRJobMaster.class);
private final MCRJobQueue jobQueue;
private Class action;
private MCRProcessableExecutor jobServe;
private MCRProcessableDefaultCollection processableCollection;
private volatile boolean running = true;
private ReentrantLock runLock;
private MCRJobMaster(Class action) {
MCRShutdownHandler.getInstance().addCloseable(this);
this.action = action;
runLock = new ReentrantLock();
jobQueue = MCRJobQueue.getInstance(action);
MCRProcessableRegistry registry = MCRInjectorConfig.injector().getInstance(MCRProcessableRegistry.class);
processableCollection = new MCRProcessableDefaultCollection(getName());
registry.register(processableCollection);
}
/**
* Returns an singleton instance of this class.
*
* @param action the {@link MCRJobAction} or null
* @return the instance of this class
*/
public static MCRJobMaster getInstance(Class action) {
String key = action != null && !MCRJobQueue.singleQueue ? action.getName() : "single";
MCRJobMaster master = INSTANCES.computeIfAbsent(key,
k -> new MCRJobMaster(MCRJobQueue.singleQueue ? null : action));
if (!master.running) {
return null;
}
return master;
}
/**
* Return if {@link MCRJobMaster} is running.
*
* @return if is running
*/
public static boolean isRunning(Class action) {
String key = action != null && !MCRJobQueue.singleQueue ? action.getName() : "single";
MCRJobMaster master = INSTANCES.get(key);
return master != null && master.running;
}
/**
* Starts the local {@link MCRJobMaster}.
* Can be auto started if "MCR.QueuedJob.{?MCRJobAction?.}autostart"
* is set to true
.
*/
public static void startMasterThread(Class action) {
if (!isRunning(action)) {
LOGGER.info("Starting job master thread{}\".", action == null ? "" : " for action \"" + action.getName());
final Thread master = new Thread(getInstance(action));
master.start();
}
}
/**
* Starts local threads ({@link MCRJobThread}) and gives {@link MCRJob} instances to them.
* Use property "MCR.QueuedJob.JobThreads"
to specify how many concurrent threads should be running.
* "MCR.QueuedJob.activated"
can be used activate or deactivate general {@link MCRJob} running.
*/
@Override
public void run() {
Thread.currentThread().setName(getName());
//get this MCRSession a speaking name
MCRSessionMgr.unlock();
MCRSession mcrSession = MCRSessionMgr.getCurrentSession();
mcrSession.setUserInformation(MCRSystemUserInformation.getSystemUserInstance());
boolean activated = MCRConfiguration2.getBoolean(MCRJobQueue.CONFIG_PREFIX + "activated").orElse(true);
activated = activated
&& MCRConfiguration2.getBoolean(MCRJobQueue.CONFIG_PREFIX + jobQueue.configPrefixAdd + "activated")
.orElse(true);
LOGGER.info("JobQueue{} is {}", MCRJobQueue.singleQueue ? "" : " for \"" + action.getName() + "\"",
activated ? "activated" : "deactivated");
if (activated) {
running = true;
int jobThreadCount = MCRConfiguration2.getInt(MCRJobQueue.CONFIG_PREFIX + "JobThreads").orElse(2);
jobThreadCount = MCRConfiguration2
.getInt(MCRJobQueue.CONFIG_PREFIX + jobQueue.configPrefixAdd + "JobThreads").orElse(jobThreadCount);
ThreadFactory slaveFactory = new ThreadFactory() {
AtomicInteger tNum = new AtomicInteger();
ThreadGroup tg = new ThreadGroup("MCRJob slave job thread group");
public Thread newThread(Runnable r) {
return new Thread(tg, r, getPreLabel() + "Slave#" + tNum.incrementAndGet());
}
};
final AtomicInteger activeThreads = new AtomicInteger();
final LinkedBlockingQueue workQueue = new LinkedBlockingQueue<>();
ThreadPoolExecutor executor = new ThreadPoolExecutor(jobThreadCount, jobThreadCount, 1, TimeUnit.DAYS,
workQueue,
slaveFactory) {
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
activeThreads.decrementAndGet();
}
@Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
activeThreads.incrementAndGet();
}
};
jobServe = MCRProcessableFactory.newPool(executor, processableCollection);
processableCollection.setProperty("running", running);
LOGGER.info("JobMaster{} with {} thread(s) is started",
MCRJobQueue.singleQueue ? "" : " for \"" + action.getName() + "\"", jobThreadCount);
while (running) {
try {
while (activeThreads.get() < jobThreadCount) {
runLock.lock();
try {
if (!running) {
break;
}
EntityManager em = MCREntityManagerProvider.getCurrentEntityManager();
EntityTransaction transaction = em.getTransaction();
MCRJob job = null;
MCRJobAction action = null;
try {
transaction.begin();
job = jobQueue.poll();
processableCollection.setProperty("queue size", jobQueue.size());
if (job != null) {
action = toMCRJobAction(job.getAction());
if (action != null && !action.isActivated()) {
job.setStatus(MCRJobStatus.NEW);
job.setStart(null);
}
}
transaction.commit();
} catch (RollbackException e) {
LOGGER.error("Error while getting next job.", e);
if (transaction != null) {
try {
transaction.rollback();
} catch (RuntimeException re) {
LOGGER.warn("Could not rollback transaction.", re);
}
}
} finally {
em.close();
}
if (job != null && action != null && action.isActivated()
&& !jobServe.getExecutor().isShutdown()) {
LOGGER.info("Creating:{}", job);
jobServe.submit(new MCRJobThread(job));
} else {
try {
synchronized (jobQueue) {
if (running) {
LOGGER.debug("No job in queue going to sleep");
//fixes a race conditioned deadlock situation
//do not wait longer than 60 sec. for a new MCRJob
jobQueue.wait(60000);
}
}
} catch (InterruptedException e) {
LOGGER.error("Job thread was interrupted.", e);
}
}
} finally {
runLock.unlock();
}
} // while(activeThreads.get() < jobThreadCount)
if (activeThreads.get() < jobThreadCount) {
try {
LOGGER.info("Waiting for a job to finish");
Thread.sleep(1000);
} catch (InterruptedException e) {
LOGGER.error("Job thread was interrupted.", e);
}
}
} catch (PersistenceException e) {
LOGGER.warn("We have an database error, sleep and run later.", e);
try {
Thread.sleep(60000);
} catch (InterruptedException ie) {
LOGGER.error("Waiting for database was interrupted.", ie);
}
} catch (Throwable e) {
LOGGER.error("Keep running while catching exceptions.", e);
}
} // while(running)
processableCollection.setProperty("running", running);
}
LOGGER.info("{} thread finished", getName());
MCRSessionMgr.releaseCurrentSession();
}
/**
* stops transmitting {@link MCRJob} to {@link MCRJobThread} and prepares shutdown.
*/
public void prepareClose() {
LOGGER.info("Closing master thread");
//signal master thread to stop now
running = false;
//Wake up, Neo!
synchronized (jobQueue) {
LOGGER.debug("Wake up queue");
jobQueue.notifyAll();
}
runLock.lock();
try {
if (jobServe != null) {
LOGGER.debug("Shutdown executor jobs.");
jobServe.getExecutor().shutdown();
try {
LOGGER.debug("Await termination of executor jobs.");
jobServe.getExecutor().awaitTermination(60, TimeUnit.SECONDS);
LOGGER.debug("All jobs finished.");
} catch (InterruptedException e) {
LOGGER.debug("Could not wait 60 seconds...", e);
}
}
} finally {
runLock.unlock();
}
}
/**
* Shuts down this thread and every local threads spawned by {@link #run()}.
*/
public void close() {
if (jobServe != null && !jobServe.getExecutor().isShutdown()) {
LOGGER.info("We are in a hurry, closing service right now");
jobServe.getExecutor().shutdownNow();
try {
jobServe.getExecutor().awaitTermination(60, TimeUnit.SECONDS);
} catch (InterruptedException e) {
LOGGER.debug("Could not wait 60 seconds...", e);
}
}
}
@Override
public int getPriority() {
return MCRShutdownHandler.Closeable.DEFAULT_PRIORITY - 1;
}
protected String getPreLabel() {
return (MCRJobQueue.singleQueue ? "Job" : action.getSimpleName());
}
/**
* Returns the name of this job master.
*
* @return
*/
public String getName() {
return getPreLabel() + " Master";
}
/**
* Returns the processable collection assigned to this job master.
*
* @return the processable collection
*/
public MCRProcessableCollection getProcessableCollection() {
return processableCollection;
}
private static MCRJobAction toMCRJobAction(Class actionClass) {
try {
Constructor actionConstructor = actionClass.getConstructor();
return actionConstructor.newInstance();
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
}
return null;
}
}