Scheduling

Commands can be run at certain times on recurring schedules.

Scheduled commands are different from queued commands. A command is queued when it is desirable to run the command once, as soon as possible. Queued commands are typically configured right before being dispatched. A command is scheduled when is it is desirable to run the command many times, on a definite schedule. Scheduled commands are configured when their schedule is defined, in code.

InEngine.NET takes a strictly programmatic approach to defining a schedule. A command schedule is defined in code, not in an external data store. There are several advantages to this approach. One advantage is that the schedule can be versioned alongside the command code. Another advantage is that there is no overhead from managing additional state in an external data store.

Scheduling a Command

A job schedule is created by adding a class to a plugin assembly that extends the InEngine.Core.AbstractPlugin class.

This is a simple example:

using System;
using InEngine.Core;

namespace MyCommandPlugin
{
    public class MySchedulePlugin : AbstractPlugin
    {
        public override void Schedule(ISchedule schedule)
        {
            // Schedule some jobs
        }
    }
}

The InEngine.NET scheduler automatically discovers this class in your plugin assembly. It will call the AbstractPlugin.Schedule method with an initialized InEngine.Scheduling.Schedule object.

This is a command schedule class with a few scheduling examples:

using System;
using InEngine.Core;

namespace MyCommandPlugin
{
    public class MySchedulePlugin : AbstractPlugin
    {
        public override void Schedule(ISchedule schedule)
        {
            /* 
             * Run MyCommand every five minutes. 
             */
            schedule.Command(new MyCommand()).EveryFiveMinutes();

            /* 
             * Run a lambda expression every ten minutes. 
             */
            schedule.Command(() => Console.WriteLine("Hello, world!")).EveryTenMinutes();
        }
    }
}

A command can be run on a custom cron schedule:

schedule.Command(new MyCommand()).Cron("15 * * * * ?");

There are a number of additional methods for scheduling commands to run on common intervals.

Run a command every second:

schedule.Command(new MyCommand()).EverySecond();

Run a command every minute:

schedule.Command(new MyCommand()).EveryMinute();

Run a command every 5 minutes:

schedule.Command(new MyCommand()).EveryFiveMinutes();

Run a command every 10 minutes:

schedule.Command(new MyCommand()).EveryTenMinutes();

Run a command every 15 minutes:

schedule.Command(new MyCommand()).EveryFifteenMinutes();

Run a command every 30 minutes:

schedule.Command(new MyCommand()).EveryThirtyMinutes();

Run a command hourly:

schedule.Command(new MyCommand()).Hourly();

Run a command hourly at a certain number of minutes past the hour (27 minutes in this example):

schedule.Command(new MyCommand()).HourlyAt(27);

Run a command daily:

schedule.Command(new MyCommand()).Daily();

Run a command daily at a specific time (at 10:30pm in this example):

schedule.Command(new MyCommand()).DailyAt(22, 30);

In a Chain

A group of commands can be scheduled to run as an atomic batch.

This simple example schedules a group of imaginary file transfer commands to run in a chain:

schedule.Command(new[] {
    new MyFileTransfer(filePath1),
    new MyFileTransfer(filePath2),
    new MyFileTransfer(filePath3),
})
.Daily();

The chain of commands will stop executing if one of them fails. The method Failed of the command that failed will be called. This is a good place to add special logic that will allow the command to be recovered later, or to alert someone that manual intervention is necessary.

With Life Cycle Methods

Commands have optional life cycle methods that are initialized when a command is scheduled.

The Before and After methods allow for some custom logic to be called before and after the command is run:

schedule.Command(new Command())
    .EveryFiveMinutes()
    .Before(x => x.Info("X refers to the command. Its methods can be used."))
    .After(x => x.Info("X refers to the command"));

It is often useful to hit a URL before or after as well:

schedule.Command(new Command())
    .EveryFiveMinutes()
    .PingBefore("http://example.com")
    .PingAfter("http://example.com");

The AbstractCommand class has an instance of InEngine.Core.IO.Write. This class is more than just a wrapper for Console.WriteLine.

It also allows these life cycle methods to send the command's text output to files or an email:

schedule.Command(new Command)
    .EveryFiveMinutes()
    .WriteOutputTo("/some/path")
    .AppendOutputTo("/some/path")
    .EmailOutputTo("example@inengine.net");

The Before and After methods allow for some custom logic to be called before and after the command is run:

schedule.Command(new Command)
    .EveryFiveMinutes()
    .Before(x => x.Info("X refers to the command. Its methods can be used."))
    .After(x => x.Info("X refers to the command"));

Command State

Commands can have properties like any C# class. When running from the command line these properties are usually initialized with command line arguments. When run by the scheduler, the properties are specified when the command is scheduled. For example, this command's Foo property will be auto-wired to "bar" when the command is later executed by the scheduler.

schedule
    .Job(new MyCommand() {
        Foo = "bar"
    })
    .EveryFiveMinutes();

If it is not desirable to auto-wire a property for some reason, simply decorate the property in the command class with the InEngine.Core.Scheduling.DoNotAutoWireAttribute class.

using System;
using InEngine.Core;

namespace MyCommandPlugin
{
    public class MyCommand : AbstractCommand
    {
        [DoNotAutoWire]
        public string Foo { get; set; }

        public override void Run()
        {
            // Foo will be null here even if it is initialized before being scheduled. 
        }
    }
}

Running the Scheduler

There are a variety of ways to run the scheduler. The simplest way is from the command line.

Running the scheduler from the CommandLine is useful for debugging or local development:

inengine.exe -s

It can also be run on Mac and Linux with Mono via a shell wrapper script:

./inengine -s