package de.otto.edison.jobs.service; import de.otto.edison.jobs.domain.JobMeta; import de.otto.edison.jobs.domain.RunningJob; import de.otto.edison.jobs.repository.JobBlockedException; import de.otto.edison.jobs.repository.JobMetaRepository; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.HashSet; import java.util.Set; import static java.lang.String.format; import static org.slf4j.LoggerFactory.getLogger; /** * A service used to manage locking of jobs. */ @Service public class JobMetaService { private static final Logger LOG = getLogger(JobMetaService.class); private final JobMetaRepository jobMetaRepository; private final JobMutexGroups mutexGroups; @Autowired public JobMetaService(final JobMetaRepository jobMetaRepository, final JobMutexGroups mutexGroups) { this.jobMetaRepository = jobMetaRepository; this.mutexGroups = mutexGroups; } /** * Marks a job as running or throws JobBlockException if it is either disabled, was marked running before or is * blocked by some other job from the mutex group. This operation must be implemented atomically on the persistent * datastore (i. e. test and set) to make sure a job is never marked as running twice. * * @param jobId the id of the job * @param jobType the type of the job * @throws JobBlockedException if at least one of the jobTypes in the jobTypesMutex set is already marked running, or * if the job type was disabled. */ public void aquireRunLock(final String jobId, final String jobType) throws JobBlockedException { // check for disabled lock: final JobMeta jobMeta = getJobMeta(jobType); if (jobMeta.isDisabled()) { throw new JobBlockedException(format("Job '%s' is currently disabled", jobType)); } // aquire lock: if (jobMetaRepository.setRunningJob(jobType, jobId)) { // check for mutually exclusive running jobs: mutexGroups.mutexJobTypesFor(jobType) .stream() .filter(mutexJobType -> jobMetaRepository.getRunningJob(mutexJobType) != null) .findAny() .ifPresent(running -> { releaseRunLock(jobType); throw new JobBlockedException(format("Job '%s' blocked by currently running job '%s'", jobType, running)); }); } else { throw new JobBlockedException(format("Job '%s' is already running", jobType)); } } /** * Clears the job running mark of the jobType. Does nothing if not mark exists. * * @param jobType the job type */ public void releaseRunLock(final String jobType) { jobMetaRepository.clearRunningJob(jobType); } /** * @return All Running Jobs as specified by the markJobAsRunningIfPossible method. */ public Set<RunningJob> runningJobs() { final Set<RunningJob> runningJobs = new HashSet<>(); jobMetaRepository.findAllJobTypes() .forEach(jobType -> { final String jobId = jobMetaRepository.getRunningJob(jobType); if (jobId != null) { runningJobs.add(new RunningJob(jobId, jobType)); } }); return runningJobs; } /** * Disables a job type, i.e. prevents it from being started * * @param jobType the disabled job type * @param comment an optional comment */ public void disable(final String jobType, final String comment) { jobMetaRepository.disable(jobType, comment); } /** * Reenables a job type that was disabled * * @param jobType the enabled job type */ public void enable(final String jobType) { jobMetaRepository.enable(jobType); } public JobMeta getJobMeta(final String jobType) { return jobMetaRepository.getJobMeta(jobType); } }