/* * JAME 6.2.1 * http://jame.sourceforge.net * * Copyright 2001, 2016 Andrea Medeghini * * This file is part of JAME. * * JAME is an application for creating fractals and other graphics artifacts. * * JAME 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. * * JAME 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 JAME. If not, see <http://www.gnu.org/licenses/>. * */ package net.sf.jame.queue.spool; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.concurrent.ThreadFactory; import java.util.logging.Logger; import net.sf.jame.core.util.DefaultThreadFactory; import net.sf.jame.core.util.Worker; /** * @author Andrea Medeghini */ public class DefaultJobService<T extends JobInterface> implements JobService<T> { private static final long CLEANUP_POLLINGTIME = 60 * 1000L; private static final long DISPATCHER_POLLINGTIME = 30 * 1000L; private static final int DEFAULT_MAX_STARTED_JOBS = 5; private static final long JOB_TIMEOUT = 60 * 1000L; private static final long JOB_LIFETIME = 120 * 1000L; private static final Logger logger = Logger.getLogger(DefaultJobService.class.getName()); private static final ThreadFactory factory = new DefaultThreadFactory("Thread", true, Thread.MIN_PRIORITY); private final List<JobServiceListener> listeners = new LinkedList<JobServiceListener>(); protected final HashMap<String, ScheduledJob> spooledJobs = new HashMap<String, ScheduledJob>(); protected final HashMap<String, ScheduledJob> scheduledJobs = new HashMap<String, ScheduledJob>(); protected final HashMap<String, ScheduledJob> startedJobs = new HashMap<String, ScheduledJob>(); protected final HashMap<String, ScheduledJob> terminatedJobs = new HashMap<String, ScheduledJob>(); private final JobFactory<T> jobFactory; private final Object dispatchMonitor = new Object(); private final Object serviceMonitor = new Object(); private final String serviceName; private final int maxJobCount; private final Worker worker; private Thread thread; private boolean running; private boolean dispatchDirty; private boolean serviceDirty; private volatile int jobCount; private final int serviceId; /** * @param serviceName * @param jobFactory * @param worker */ public DefaultJobService(final int serviceId, final String serviceName, final JobFactory<T> jobFactory, final Worker worker) { this.serviceId = serviceId; this.serviceName = serviceName; this.jobFactory = jobFactory; this.worker = worker; maxJobCount = Integer.getInteger("jame.maxJobCount", DEFAULT_MAX_STARTED_JOBS); } /** * @see net.sf.jame.queue.spool.JobService#getName() */ public String getName() { return serviceName; } /** * @see net.sf.jame.queue.spool.JobService#start() */ public void start() { if (thread == null) { thread = factory.newThread(new ServiceHandler()); thread.setName(serviceName + " JobService Thread"); running = true; thread.start(); } } /** * @see net.sf.jame.queue.spool.JobService#stop() */ public void stop() { if (thread != null) { running = false; thread.interrupt(); try { thread.join(); } catch (final InterruptedException e) { } thread = null; } synchronized (spooledJobs) { synchronized (scheduledJobs) { synchronized (startedJobs) { synchronized (terminatedJobs) { spooledJobs.clear(); scheduledJobs.clear(); startedJobs.clear(); terminatedJobs.clear(); } } } } } /** * @see net.sf.jame.queue.spool.JobService#getJobCount() */ public int getJobCount() { synchronized (spooledJobs) { return spooledJobs.size(); } } private boolean isBusy() { return (scheduledJobs.size() != 0) || (startedJobs.size() != 0) || (terminatedJobs.size() != 0); } /** * @see net.sf.jame.queue.spool.JobService#deleteJob(java.lang.String) */ public void deleteJob(final String jobId) { if (jobId == null) { throw new NullPointerException("jobId == null"); } synchronized (spooledJobs) { synchronized (scheduledJobs) { synchronized (startedJobs) { synchronized (terminatedJobs) { final ScheduledJob scheduledJob = spooledJobs.remove(jobId); scheduledJobs.remove(jobId); terminatedJobs.remove(jobId); if (scheduledJob != null) { worker.addTask(new DeleteTask(scheduledJob)); } fireStateChanged(isBusy() ? JobServiceListener.STATUS_BUSY : JobServiceListener.STATUS_IDLE, serviceName + " running"); } } } } } /** * @see net.sf.jame.queue.spool.JobService#stopJob(java.lang.String) */ public void stopJob(final String jobId) { if (jobId == null) { throw new NullPointerException("jobId == null"); } synchronized (spooledJobs) { synchronized (scheduledJobs) { synchronized (startedJobs) { scheduledJobs.remove(jobId); final ScheduledJob scheduledJob = startedJobs.remove(jobId); if (scheduledJob != null) { worker.addTask(new StopTask(scheduledJob)); } } } } } /** * @see net.sf.jame.queue.spool.JobService#abortJob(java.lang.String) */ public void abortJob(final String jobId) { if (jobId == null) { throw new NullPointerException("jobId == null"); } synchronized (spooledJobs) { synchronized (scheduledJobs) { synchronized (startedJobs) { final ScheduledJob scheduledJob = startedJobs.get(jobId); if (scheduledJob != null) { worker.addTask(new AbortTask(scheduledJob)); } } } } } /** * @see net.sf.jame.queue.spool.JobService#createJob(net.sf.jame.queue.spool.JobListener) */ public String createJob(final JobListener listener) { if (listener == null) { throw new NullPointerException("listener == null"); } synchronized (spooledJobs) { final T job = jobFactory.createJob(JobIDFactory.newJobId(), new ServiceJobListener(listener)); spooledJobs.put(job.getJobId(), new ScheduledJob(job)); return job.getJobId(); } } /** * @see net.sf.jame.queue.spool.JobService#setJobData(java.lang.String, JobData, int) */ public void setJobData(final String jobId, final JobData jobData, final int frameNumber) { if (jobId == null) { throw new NullPointerException("jobId == null"); } if (jobData == null) { throw new NullPointerException("jobData == null"); } synchronized (spooledJobs) { ScheduledJob job = spooledJobs.get(jobId); if (job != null) { job.getJob().setJobDataRow(jobData); job.getJob().setFirstFrameNumber(frameNumber); } } } /** * @see net.sf.jame.queue.spool.JobService#runJob(java.lang.String) */ public void runJob(final String jobId) { if (jobId == null) { throw new NullPointerException("jobId == null"); } synchronized (spooledJobs) { synchronized (scheduledJobs) { synchronized (startedJobs) { synchronized (terminatedJobs) { if (terminatedJobs.get(jobId) == null) { if (startedJobs.get(jobId) == null) { ScheduledJob scheduledJob = scheduledJobs.get(jobId); if (scheduledJob != null) { worker.addTask(new ResetTask(scheduledJob)); } else { scheduledJob = spooledJobs.get(jobId); if (scheduledJob != null) { scheduledJobs.put(jobId, scheduledJob); worker.addTask(new ResetTask(scheduledJob)); } } } } fireStateChanged(isBusy() ? JobServiceListener.STATUS_BUSY : JobServiceListener.STATUS_IDLE, serviceName + " running"); } } } } synchronized (dispatchMonitor) { dispatchDirty = true; dispatchMonitor.notify(); } } private class ServiceHandler implements Runnable { /** * @see java.lang.Runnable#run() */ public void run() { boolean interrupted = false; fireStateChanged(JobServiceListener.STATUS_BORN, serviceName + " started"); final Thread dispatcherThread = factory.newThread(new DispatcherHandler()); dispatcherThread.setName(serviceName + " Dispatcher Thread"); try { dispatcherThread.start(); Iterator<ScheduledJob> jobIterator = null; while (running) { synchronized (spooledJobs) { synchronized (scheduledJobs) { synchronized (startedJobs) { jobIterator = startedJobs.values().iterator(); while (jobIterator.hasNext()) { final ScheduledJob scheduledJob = jobIterator.next(); if (scheduledJob.isTerminated()) { jobIterator.remove(); terminatedJobs.put(scheduledJob.getJob().getJobId(), scheduledJob); worker.addTask(new StopTask(scheduledJob)); } else if ((System.currentTimeMillis() - scheduledJob.getJob().getLastUpdate()) > JOB_TIMEOUT) { jobIterator.remove(); worker.addTask(new ResetTask(scheduledJob)); } } jobIterator = scheduledJobs.values().iterator(); while (jobIterator.hasNext()) { final ScheduledJob scheduledJob = jobIterator.next(); if (scheduledJob.isTerminated()) { jobIterator.remove(); } } synchronized (terminatedJobs) { jobIterator = terminatedJobs.values().iterator(); while (jobIterator.hasNext()) { final ScheduledJob scheduledJob = jobIterator.next(); if ((System.currentTimeMillis() - scheduledJob.getJob().getLastUpdate()) > JOB_LIFETIME) { jobIterator.remove(); spooledJobs.remove(scheduledJob.getJob().getJobId()); worker.addTask(new DeleteTask(scheduledJob)); } } fireStateChanged(isBusy() ? JobServiceListener.STATUS_BUSY : JobServiceListener.STATUS_IDLE, serviceName + " running"); } } } } synchronized (serviceMonitor) { if (!serviceDirty) { serviceMonitor.wait(CLEANUP_POLLINGTIME); } // else { // Thread.yield(); // } serviceDirty = false; } } } catch (final InterruptedException e) { interrupted = true; } finally { dispatcherThread.interrupt(); try { dispatcherThread.join(); } catch (InterruptedException e) { interrupted = true; } fireStateChanged(JobServiceListener.STATUS_DEAD, serviceName + " stopped"); } if (interrupted) { Thread.currentThread().interrupt(); } } } private class DispatcherHandler implements Runnable { private final List<ScheduledJob> jobs = new LinkedList<ScheduledJob>(); /** * @see java.lang.Runnable#run() */ public void run() { try { while (running) { try { fetchJobs(jobs); dispatchJobs(jobs); jobs.clear(); } catch (final Exception e) { e.printStackTrace(); } synchronized (dispatchMonitor) { if (!dispatchDirty) { dispatchMonitor.wait(DISPATCHER_POLLINGTIME); } // else { // Thread.yield(); // } dispatchDirty = false; } } } catch (final InterruptedException e) { Thread.currentThread().interrupt(); } } private void fetchJobs(final List<ScheduledJob> jobs) { synchronized (scheduledJobs) { synchronized (startedJobs) { for (ScheduledJob scheduledJob : scheduledJobs.values()) { if (!scheduledJob.isAborted() && !startedJobs.containsKey(scheduledJob.getJob().getJobId()) && (startedJobs.size() < maxJobCount)) { startedJobs.put(scheduledJob.getJob().getJobId(), scheduledJob); jobs.add(scheduledJob); } } } } } private void dispatchJobs(final List<ScheduledJob> jobs) { for (ScheduledJob scheduledJob : jobs) { worker.addTask(new StartTask(scheduledJob)); } } } private class DeleteTask implements Runnable { private final ScheduledJob job; /** * @param job */ protected DeleteTask(final ScheduledJob job) { this.job = job; } /** * @see java.lang.Runnable#run() */ public void run() { job.dispose(); logger.info(serviceName + ": Job deleted " + job.getJob() + " (jobs = " + jobCount + ")"); } } private class StartTask implements Runnable { private final ScheduledJob job; /** * @param job */ protected StartTask(final ScheduledJob job) { this.job = job; } /** * @see java.lang.Runnable#run() */ public void run() { job.start(); jobCount += 1; logger.info(serviceName + ": Job started " + job.getJob() + " (jobs = " + jobCount + ")"); } } private class StopTask implements Runnable { private final ScheduledJob job; /** * @param job */ protected StopTask(final ScheduledJob job) { this.job = job; } /** * @see java.lang.Runnable#run() */ public void run() { job.stop(); if (jobCount > 0) { jobCount -= 1; } logger.info(serviceName + ": Job stopped " + job.getJob() + " (jobs = " + jobCount + ")"); } } private class AbortTask implements Runnable { private final ScheduledJob job; /** * @param job */ protected AbortTask(final ScheduledJob job) { this.job = job; } /** * @see java.lang.Runnable#run() */ public void run() { job.abort(); logger.info(serviceName + ": Job aborted " + job.getJob() + " (jobs = " + jobCount + ")"); } } private class ResetTask implements Runnable { private final ScheduledJob job; /** * @param job */ protected ResetTask(final ScheduledJob job) { this.job = job; } /** * @see java.lang.Runnable#run() */ public void run() { job.reset(); if (jobCount > 0) { jobCount -= 1; } logger.info(serviceName + ": Job resetted " + job.getJob() + " (jobs = " + jobCount + ")"); } } private class ServiceJobListener implements JobListener { private final JobListener listener; /** * @param listener */ protected ServiceJobListener(final JobListener listener) { this.listener = listener; } /** * @see net.sf.jame.queue.spool.JobListener#updated(java.lang.String, net.sf.jame.queue.spool.JobData) */ public void updated(final String jobId, final JobData job) { listener.updated(jobId, new DefaultJobData(job)); } /** * @see net.sf.jame.queue.spool.JobListener#started(java.lang.String, net.sf.jame.queue.spool.JobData) */ public void started(final String jobId, final JobData job) { listener.started(jobId, new DefaultJobData(job)); } /** * @see net.sf.jame.queue.spool.JobListener#stopped(java.lang.String, net.sf.jame.queue.spool.JobData) */ public void stopped(final String jobId, final JobData job) { listener.stopped(jobId, new DefaultJobData(job)); synchronized (dispatchMonitor) { dispatchDirty = true; dispatchMonitor.notify(); } } /** * @see net.sf.jame.queue.spool.JobListener#terminated(java.lang.String, net.sf.jame.queue.spool.JobData) */ public void terminated(final String jobId, final JobData job) { listener.terminated(jobId, new DefaultJobData(job)); synchronized (serviceMonitor) { serviceDirty = true; serviceMonitor.notify(); } } /** * @see net.sf.jame.queue.spool.JobListener#disposed(java.lang.String, net.sf.jame.queue.spool.JobData) */ public void disposed(final String jobId, final JobData job) { listener.disposed(jobId, new DefaultJobData(job)); } } protected class ScheduledJob { private final T job; /** * @param job */ public ScheduledJob(final T job) { this.job = job; } /** * @return */ public boolean isTerminated() { return job.isTerminated(); } /** * @return */ public boolean isAborted() { return job.isAborted(); } /** * @return */ public boolean isStarted() { return job.isStarted(); } /** * */ public void stop() { job.stop(); } /** * */ public void start() { job.start(); } /** * */ public void abort() { job.abort(); } /** * */ public void reset() { job.reset(); } /** * @return the job */ public T getJob() { return job; } /** * */ public void dispose() { job.dispose(); } } /** * @see net.sf.jame.queue.spool.JobService#addServiceListener(net.sf.jame.queue.spool.JobServiceListener) */ public void addServiceListener(final JobServiceListener listener) { listeners.add(listener); } /** * @see net.sf.jame.queue.spool.JobService#removeServiceListener(net.sf.jame.queue.spool.JobServiceListener) */ public void removeServiceListener(final JobServiceListener listener) { listeners.remove(listener); } /** * @param message */ protected void fireStateChanged(final int status, final String message) { for (JobServiceListener listener : listeners) { listener.stateChanged(serviceId, status, message); } } }