All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.mycore.services.queuedjob.MCRJobMaster Maven / Gradle / Ivy

There is a newer version: 2024.05
Show newest version
/*
 * 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;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy