/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * Copyright (c) 2013, MPL CodeInside http://codeinside.ru */ package ru.codeinside.gses.webui; import org.activiti.engine.ActivitiException; import org.activiti.engine.impl.cmd.ExecuteJobsCmd; import org.activiti.engine.impl.context.Context; import org.activiti.engine.impl.interceptor.CommandExecutor; import org.activiti.engine.impl.jobexecutor.JobExecutor; import org.activiti.engine.impl.jobexecutor.JobExecutorContext; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.annotation.Resource; import javax.ejb.DependsOn; import javax.ejb.Lock; import javax.ejb.LockType; import javax.ejb.Singleton; import javax.ejb.TransactionManagement; import javax.ejb.TransactionManagementType; import javax.transaction.HeuristicMixedException; import javax.transaction.HeuristicRollbackException; import javax.transaction.NotSupportedException; import javax.transaction.RollbackException; import javax.transaction.SystemException; import javax.transaction.TransactionManager; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; @Singleton @Lock(LockType.READ) @DependsOn("BaseBean") @TransactionManagement(TransactionManagementType.BEAN) public class ActivitiJob implements ActivitiJobProvider { final Logger logger = Logger.getLogger(getClass().getName()); final AtomicInteger total = new AtomicInteger(0); volatile JobExecutor jobExecutor; volatile ExecutorService executorService; /** * Код внутри Activiti использует suspend() и resume(tx) для того чтобы открепить транзакцию от потока * а потом снова прикрепить, поэтому UserTransaction не подойдёт! */ @Resource TransactionManager transactionManager; final class Executor extends JobExecutor { Executor() { // Сколько "забирать" задач из базы за один запрос setMaxJobsPerAcquisition(Runtime.getRuntime().availableProcessors()); // Время бездействия когда нет задач setWaitTimeInMillis(9 * 1000); } @Override protected void startExecutingJobs() { logger.info("Запуск потока выборки задач"); startJobAcquisitionThread(); } @Override protected void stopExecutingJobs() { if (jobAcquisitionThread != null) { logger.info("Остановка потока выборки задач"); stopJobAcquisitionThread(); } } @Override protected void executeJobs(List<String> jobIds) { for (String jobId : jobIds) { scheduleJob(jobId); } } } void scheduleJob(String jobId) { if (executorService == null) { throw new IllegalStateException("Нет исполнителя для " + jobId); } executorService.submit(new SingleJobExecution(jobId)); } void executeJob(final String jobId, final int num) { final JobExecutorContext jobExecutorContext = new JobExecutorContext(); Context.setJobExecutorContext(jobExecutorContext); final List<String> currentProcessorJobQueue = jobExecutorContext.getCurrentProcessorJobQueue(); currentProcessorJobQueue.add(jobId); try { transactionManager.begin(); boolean doCommit = false; try { final CommandExecutor commandExecutor = jobExecutor.getCommandExecutor(); while (!currentProcessorJobQueue.isEmpty()) { final String jobToExecute = currentProcessorJobQueue.remove(0); commandExecutor.execute(new ExecuteJobsCmd(jobToExecute)); } doCommit = true; } catch (ActivitiException e) { // Стек уже журналирован в commandExecutor! logger.log(Level.SEVERE, "Сбой исполнения задачи #" + num + "/" + jobId); } catch (Exception e) { logger.log(Level.SEVERE, "Сбой исполнения задачи #" + num + "/" + jobId, e); } if (doCommit) { transactionManager.commit(); // Запускаем лишь при успешном коммите for (final String id : currentProcessorJobQueue) { scheduleJob(id); } } else { transactionManager.rollback(); } } catch (NotSupportedException e) { logger.log(Level.SEVERE, "Транзакции не поддерживаются", e); } catch (SystemException e) { logger.log(Level.SEVERE, "Системная ошибка", e); } catch (HeuristicRollbackException e) { logger.log(Level.SEVERE, "Ошибка отката", e); } catch (RollbackException e) { logger.log(Level.SEVERE, "Ошибка отката", e); } catch (HeuristicMixedException e) { logger.log(Level.SEVERE, "Смешанная ошибка", e); } finally { Context.removeJobExecutorContext(); } } @PostConstruct void afterConstruct() { if (executorService == null) { logger.info("Создание исполнителя фоновых задач"); executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactory() { final AtomicInteger serialNumber = new AtomicInteger(); /** * Ставим имя и низкий приоритет на всякий случай. */ @Override public Thread newThread(final Runnable r) { final Thread thread = new Thread(r, "Activiti-" + serialNumber.incrementAndGet()); thread.setPriority(Thread.MIN_PRIORITY); return thread; } }); } } @PreDestroy void beforeDestroy() { if (jobExecutor != null) { jobExecutor.shutdown(); jobExecutor = null; } if (executorService != null) { logger.info("Остановка исполнителя фоновых задач"); executorService.shutdownNow(); executorService = null; } } @Override public void startNow() { if (jobExecutor != null) { jobExecutor.start(); } else { logger.info("Нет исполнителя для запуска"); } } /** * Предполагается что лишь Engine будет создавать исполнителя. */ public JobExecutor createJobExecutor() { if (jobExecutor != null) { jobExecutor.shutdown(); } jobExecutor = new Executor(); return jobExecutor; } final class SingleJobExecution implements Runnable { private final String jobId; private final int num; public SingleJobExecution(String jobId) { this.jobId = jobId; num = total.incrementAndGet(); } @Override public void run() { if (jobExecutor == null) { logger.severe("Нет исполнителя для задачи #" + num + "/" + jobId); } else { try { executeJob(jobId, num); } catch (Exception e) { logger.log(Level.SEVERE, "Ошибка исполнения задачи #" + num + "/" + jobId, e); } } } } }