divconq.scheduler.Scheduler.java Source code

Java tutorial

Introduction

Here is the source code for divconq.scheduler.Scheduler.java

Source

/* ************************************************************************
#
#  DivConq
#
#  http://divconq.com/
#
#  Copyright:
#    Copyright 2014 eTimeline, LLC. All rights reserved.
#
#  License:
#    See the license.txt file in the project's top-level directory for details.
#
#  Authors:
#    * Andy White
#
************************************************************************ */
package divconq.scheduler;

import java.util.HashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.locks.ReentrantLock;

import org.joda.time.DateTimeUtils;
import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;
import org.joda.time.LocalTime;
import org.joda.time.Period;
import org.joda.time.ReadableInstant;

import divconq.hub.Hub;
import divconq.hub.ISystemWork;
import divconq.hub.SysReporter;
import divconq.lang.op.FuncResult;
import divconq.lang.op.OperationContext;
import divconq.lang.op.OperationResult;
import divconq.log.Logger;
import divconq.scheduler.common.CommonSchedule;
import divconq.scheduler.limit.LimitHelper;
import divconq.struct.ListStruct;
import divconq.util.StringUtil;
import divconq.work.Task;
import divconq.work.WorkPool;
import divconq.xml.XElement;

/**
 * Handles scheduling application tasks.
 * 
 * @author Andy White
 *
 */
// TODO add tracing settings
public class Scheduler {
    // the first node in the list of scheduled nodes - the head of the list is moved
    // forward as the items on the list get scheduled.  List is single linked list.
    protected SchedulerNode first = null;

    // how many are currently in the linked list?
    protected long nodeCnt = 0;

    // lock during adding and removing of scheduled work.  all add and remove operations
    // are thread safe
    protected ReentrantLock lock = new ReentrantLock();

    protected ScheduledFuture<?> clock = null;

    protected HashMap<String, LimitHelper> batches = new HashMap<String, LimitHelper>();

    protected ISchedulerDriver driver = null;

    public LimitHelper getBatch(String name) {
        return this.batches.get(name);
    }

    public void init(OperationResult or, XElement config) {
        // TODO

        if (config != null) {
            for (XElement el : config.selectAll("Batch")) {
                XElement bel = el.find("Limits");
                String name = el.getAttribute("Name");

                if (StringUtil.isNotEmpty(name) && (bel != null)) {
                    LimitHelper h = new LimitHelper();
                    h.init(bel);
                    this.batches.put(name, h);
                }
            }

            // setup the provider of the work queue
            String classname = config.getAttribute("InterfaceClass");

            if (StringUtil.isEmpty(classname))
                classname = "divconq.scheduler.LocalSchedulerDriver";

            if (StringUtil.isNotEmpty(classname)) {
                Object impl = Hub.instance.getInstance(classname);

                if ((impl == null) || !(impl instanceof ISchedulerDriver)) {
                    or.errorTr(227, classname);
                    return;
                }

                this.driver = (ISchedulerDriver) impl;
                this.driver.init(or, config);
            }
        }
    }

    public void start(OperationResult or) {
        Hub.instance.getClock().addFastSystemWorker(new ISystemWork() {
            @Override
            public void run(SysReporter reporter) {
                Scheduler.this.execute();
            }

            @Override
            public int period() {
                return 1;
            }
        });

        Hub.instance.getClock().addSlowSystemWorker(new ISystemWork() {
            @Override
            public void run(SysReporter reporter) {
                reporter.setStatus("before schedule update");
                // TODO check for updates to the schedule
                reporter.setStatus("after schedule update");
            }

            @Override
            public int period() {
                return 5;
            }
        });

        if (this.driver != null) {
            FuncResult<ListStruct> loadres = this.driver.loadSchedule();

            if (loadres.isNotEmptyResult()) {
                loadres.getResult().recordStream().forEach(rec -> {
                    XElement schedule = rec.getFieldAsXml("Schedule");

                    ISchedule sched = "CommonSchedule".equals(schedule.getName()) ? new CommonSchedule()
                            : new SimpleSchedule();

                    sched.init(schedule);

                    sched.setTask(new Task().withId(Task.nextTaskId("ScheduleLoader"))
                            .withTitle("Scheduled Task Loader: " + rec.getFieldAsString("Title")).withRootContext()
                            .withWork(trun -> {
                                FuncResult<ScheduleEntry> loadres2 = Scheduler.this.driver
                                        .loadEntry(rec.getFieldAsString("Id"));

                                if (loadres2.isNotEmptyResult()) {
                                    ScheduleEntry entry = loadres2.getResult();

                                    entry.setSchedule(sched);

                                    entry.submit(trun);
                                }

                                // we are done, no need to wait 
                                trun.complete();
                            }));

                    Scheduler.this.addNode(sched);
                });
            }
        }
    }

    public void stop(OperationResult or) {
        // TODO

        if (this.clock != null)
            this.clock.cancel(false);
    }

    public long size() {
        return this.nodeCnt;
    }

    // the scheduler runs on its own thread, this is the code that starts and runs the scheduler
    private void execute() {
        OperationContext.useHubContext();

        long loadcnt = this.nodeCnt;

        this.lock.lock();

        try {
            SchedulerNode curr = this.first;

            long now = DateTimeUtils.currentTimeMillis();
            WorkPool p = Hub.instance.getWorkPool();

            //System.out.println(new DateTime() +  " - scheduler - " + new DateTime(now) + " > " + new DateTime(curr.when));

            while ((curr != null) && (curr.when <= now)) {
                //System.out.println("Scheduled node: " + curr.scheduler.isCanceled());

                if (!curr.scheduler.isCanceled())
                    p.submit(curr.task, curr.scheduler);

                SchedulerNode old = curr;

                curr = old.next;
                this.first = curr;
                this.nodeCnt--;

                // reduce references for better GC
                old.next = null;
                old.scheduler = null;
                old.task = null;
            }

            loadcnt = this.nodeCnt;
        } catch (Exception x) {
            // TODO trace/log
        } finally {
            this.lock.unlock();
        }

        // it is possible due to race conditions to get a mis-ordered value in the counter
        // a) it doesn't matter 99.99999999% of the time, b) we cannot afford to do this in the lock
        Hub.instance.getCountManager().allocateSetNumberCounter("dcSchedulerLoad", loadcnt);
    }

    // add a work unit to run (almost) immediately - much the same as directly adding to the thread pool
    // any work unit submitted to the scheduler (or to any thread pool) will become owned by the
    // scheduler (or thread pool).
    public ISchedule runNow(Task work) {
        return this.addNode(new SimpleSchedule(work, DateTimeUtils.currentTimeMillis(), 0));
    }

    // run the work unit once in Sec seconds from now
    public ISchedule runIn(Task work, int secs) {
        return this.addNode(new SimpleSchedule(work, DateTimeUtils.currentTimeMillis() + (1000 * secs), 0));
    }

    // run the work unit once at the specified time.  If less than now then submits immediately
    // if in the distant future, it may not be run if the process is terminated.  adding working
    // to the schedule is no guarantee work will be run.
    public ISchedule runAt(Task work, ReadableInstant time) {
        return this.addNode(new SimpleSchedule(work, time.getMillis(), 0));
    }

    public ISchedule runAt(Task work, LocalDate date, Period period) {
        LocalDateTime ldt = date.toLocalDateTime(new LocalTime(0, 0).plus(period));
        return this.runAt(work, ldt.toDateTime());
    }

    // run the work unit repeatedly, every Secs seconds - note scheduler will not own work, you have to keep track of it
    public ISchedule runEvery(Task work, int secs) {
        return this.addNode(new SimpleSchedule(work, DateTimeUtils.currentTimeMillis() + (1000 * secs), secs));
    }

    public ISchedule addNode(ISchedule schedule) {
        long when = schedule.when();

        if (when < 0)
            return null;

        if ((schedule.task() == null) || (schedule.task().getContext() == null)) {
            Logger.warn("Schedule missing task or context: " + schedule.task());
            return null;
        }

        long loadcnt = this.nodeCnt;

        this.lock.lock();

        try {
            SchedulerNode snode = new SchedulerNode();
            snode.task = schedule.task();
            snode.when = when;
            snode.scheduler = schedule;

            SchedulerNode curr = this.first;
            SchedulerNode last = null;

            this.nodeCnt++;

            // loop through the scheduling linked list and find the right place to insert
            // the new TScheduleNode
            while (curr != null) {
                if (snode.when < curr.when) {
                    snode.next = curr;

                    if (last == null)
                        this.first = snode;
                    else
                        last.next = snode;

                    return schedule;
                }

                last = curr;
                curr = curr.next;
            }

            // none found then add to end
            if (last == null)
                this.first = snode;
            else
                last.next = snode;

            loadcnt = this.nodeCnt;
        } catch (Exception x) {
            // TODO

            return null;
        } finally {
            this.lock.unlock();
        }

        // it is possible, due to race conditions, to get a mis-ordered value in the counter
        // a) it doesn't matter 99.99999999% of the time, b) we cannot afford to do this in the lock
        Hub.instance.getCountManager().allocateSetNumberCounter("dcSchedulerLoad", loadcnt);

        return schedule;
    }

    public class SchedulerNode {
        protected SchedulerNode next = null;
        protected long when = 0;
        protected Task task = null;
        protected ISchedule scheduler = null;
    }

    public void dump() {
        SchedulerNode curr = this.first;
        int cnt = 0;

        while (curr != null) {
            //Logger.info("     + " + curr.task.getTitle());
            cnt++;
            curr = curr.next;
        }

        Logger.info("Outstanding schedule nodes: " + cnt);
    }
}