package org.fastcatsearch.job; import java.util.Comparator; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.PriorityQueue; import java.util.Queue; import org.fastcatsearch.control.ResultFuture; import org.fastcatsearch.exception.FastcatSearchException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 입력된 여러 스케쥴은 한번에 하나씩만 수행된다. 스케쥴 시간이 겹치는 경우, guarantee job이 우선하고, * guarantee job끼리 시간이 겹치는 경우는 어느것이 먼저 실행될지 알수없으나, 모두 실행되는 것이 보장된다. * */ public class PriorityScheduledJob extends ScheduledJob { private static final long serialVersionUID = -8978933991846840288L; private Queue<ScheduledJobEntry> priorityJobQueue; protected boolean isCanceled; private final static ScheduledJobEntryComparator comparator = new ScheduledJobEntryComparator(); public PriorityScheduledJob(String key, ScheduledJobEntry scheduledJobEntry) { super(key); this.priorityJobQueue = new PriorityQueue<ScheduledJobEntry>(3, comparator); priorityJobQueue.add(scheduledJobEntry); } public PriorityScheduledJob(String key, List<ScheduledJobEntry> list) { super(key); this.priorityJobQueue = new PriorityQueue<ScheduledJobEntry>(3, comparator); priorityJobQueue.addAll(list); } @Override public void cancel() { isCanceled = true; synchronized (this) { this.notify(); } } @Override public boolean isCanceled() { return isCanceled; } @Override public JobResult doRun() throws FastcatSearchException { //처음에 시작시간을 모두 업데이트 해준다. Iterator<ScheduledJobEntry> iterator = priorityJobQueue.iterator(); while(iterator.hasNext()){ ScheduledJobEntry e = iterator.next(); updateStartTimeByNow(e); } while (!isCanceled) { try { ScheduledJobEntry entry = priorityJobQueue.poll(); Job actualJob = entry.getJob(); long timeToWait = getTimeToWaitInMillisecond(entry); if (timeToWait < 0) { // 이미 지났을 경우. if (entry.isExecuteGuarantee()) { // 1) guarantee job: 바로실행한다. timeToWait = 0; } else { // 2) normal job: next start time을 계산하여 다시 Q에 집어넣는다. // logger.debug("지난 작업 스킵 : {}", entry); updateStartTimeByNow(entry); priorityJobQueue.offer(entry); continue; } } logger.info("Next {} indexing will run {} at {} after waiting {}ms", key(), actualJob.getClass().getSimpleName(), entry.getStartTime(), timeToWait); if(timeToWait > 0){ synchronized (this) { wait(timeToWait); } } if (isCanceled) { break; } try { logger.debug("##### Scheduled Job offer {} : {}", actualJob, entry); ResultFuture resultFuture = jobExecutor.offer(actualJob); Object result = null; if (resultFuture == null) { // ignore logger.debug("Scheduled job {} is ignored.", actualJob); } else { result = resultFuture.take(); logger.debug("Scheduled Job Finished. {} > {}, execution[{}]", actualJob, result, entry.executeInfo()); } } finally { // 실행한 job에 대해서는 반드시 다음 시간에 실행되도록 update time후 offer되도록 한다. entry.executeInfo().incrementExecution(); updateStartTimeByNow(entry); priorityJobQueue.offer(entry); } } catch (InterruptedException e) { // InterruptedException 은 thread를 끝내게 한다. logger.info("[{}] is interrupted!", getClass().getSimpleName()); break; } catch (Throwable t) { // 죽지마. logger.error("", t); } } if (isCanceled) { logger.info("[{}] is canceled >> {}", getClass().getSimpleName(), priorityJobQueue); } return new JobResult(); } // 현시간기준으로 다음 시작시간으로 업데이트. protected void updateStartTimeByNow(ScheduledJobEntry entry) { Date startTime = entry.getStartTime(); int periodInSecond = entry.getPeriodInSecond(); periodInSecond *= 1000L; long nextStartTime = startTime.getTime(); long now = System.currentTimeMillis(); if (nextStartTime < now) { // 현 시간보다 커질때까지 더한다. if(periodInSecond > 0){ //주기가 0이 아닐때만 더해서 다음 시간을 구한다. while (nextStartTime < now) { nextStartTime += periodInSecond;// increase by period } }else{ //주기가 0 이라면 바로시작한다. delay time = 3초. nextStartTime = now + 3000; } entry.setStartTime(new Date(nextStartTime)); } else { // 현시간보다 크면 그대로 둔다. } } protected long getTimeToWaitInMillisecond(ScheduledJobEntry entry) { long nextStartTime = entry.getStartTime().getTime(); return nextStartTime - System.currentTimeMillis(); } } class ScheduledJobEntryComparator implements Comparator<ScheduledJobEntry> { protected static Logger logger = LoggerFactory.getLogger(ScheduledJobEntryComparator.class); @Override public int compare(ScheduledJobEntry o1, ScheduledJobEntry o2) { // logger.debug("1>>{}", o1); // logger.debug("2>>{}", o2); int c = o1.getStartTime().compareTo(o2.getStartTime()); if(c == 0){ if(o1.isExecuteGuarantee()){ return -1; }else if(o2.isExecuteGuarantee()){ return 1; } return 0; }else{ return c; } } }