JasperFx 0.9.0


Next

Stateful Sagas

Previous

Routing Messages

Scheduled Message Execution Edit on GitHub


Note! This functionality is perfect for "timeout" conditions like "send an email if this issue isn't solved within 3 days." Internally, Jasper uses this functionality for the "retry later" error handling.

Most times you probably want a message whether enqueued locally or published to another system, to be processed as soon as possible. In some cases though, you may want to say that the work represented by handling a message should be processed later, either by a specified time delay or at a certain time in the future. For this reason, Jasper supports the scheduled message execution to do just that. Unlike many other tools, Jasper depends on the downstream receivers being responsible for scheduling the execution rather than trying to do a delayed delivery of the messages. For now, this pretty well means that there needs to be another Jasper application on the other side.

How it Works

Note! Why didn't we just use Hangfire? We looked at it, but thought that our current solution avoided having to have more dependencies and the database mechanism was actually easier for diagnostic views of the scheduled messages. We do recommend Hangfire if you really just want scheduled job execution.

No points for style here, the scheduled message execution simply polls in a background agent for any messages that are ready to execute. There is an in memory model by default that's meant to be just good enough for the message retries, but for any kind of durable scheduling that can survive beyond one node process, please use either the Marten-backed message persistence or the Sql Server-backed persistence.

In both cases, there is a guarantee that each scheduled message will be executed on only one node.

Schedule Execution Locally

You can schedule a message to be executed in the local system. Either by specifying a TimeSpan delay like this:


public async Task schedule_locally(IMessageContext context, Guid issueId)
{
    var timeout = new WarnIfIssueIsStale
    {
        IssueId = issueId
    };

    // Process the issue timeout logic 3 days from now
    // in *this* system
    await context.Schedule(timeout, 3.Days());
}

Or specify the execution at an exact time like this:


public async Task schedule_locally_at_5_tomorrow_afternoon(IMessageContext context, Guid issueId)
{
    var timeout = new WarnIfIssueIsStale
    {
        IssueId = issueId
    };

    var time = DateTime.Today.AddDays(1).AddHours(17);


    // Process the issue timeout at 5PM tomorrow
    // in *this* system
    // Do note that Jasper quietly converts this
    // to universal time in storage
    await context.ScheduleSend(timeout, time);
}

Note that if you are using Durable Messaging, the message could be executed by any running node within the system rather than the currently running process. If you aren't using durable messaging, the message is kept and scheduled in memory. Do be aware of that for the sake of memory usage and whether or not the scheduled execution should survive past the lifetime of the current process.

Schedule a Message to Another System

Likewise, you can send a message to another system and request that the message be executed later, either by a time delay:


public async Task schedule_send(IMessageContext context, Guid issueId)
{
    var timeout = new WarnIfIssueIsStale
    {
        IssueId = issueId
    };

    // Process the issue timeout logic 3 days from now
    await context.ScheduleSend(timeout, 3.Days());
}

or at a certain time:


public async Task schedule_send_at_5_tomorrow_afternoon(IMessageContext context, Guid issueId)
{
    var timeout = new WarnIfIssueIsStale
    {
        IssueId = issueId
    };

    var time = DateTime.Today.AddDays(1).AddHours(17);


    // Process the issue timeout at 5PM tomorrow
    // Do note that Jasper quietly converts this
    // to universal time in storage
    await context.ScheduleSend(timeout, time);
}

If you wanted to, the methods above are really just syntactical sugar for this below:


public async Task send_at_5_tomorrow_afternoon_yourself(IMessageContext context, Guid issueId)
{
    var timeout = new WarnIfIssueIsStale
    {
        IssueId = issueId
    };

    var time = DateTime.Today.AddDays(1).AddHours(17);


    // Process the issue timeout at 5PM tomorrow
    // Do note that Jasper quietly converts this
    // to universal time in storage
    await context.Send(timeout, e => e.ExecutionTime = time);
}

Schedule Execution From Cascading Messages

To schedule a message to another system as a cascading message from a message handler, you can return the ScheduledResponse object like this:


public class ScheduledResponseHandler
{
    public ScheduledResponse Consume(DirectionRequest request)
    {
        // Process GoWest in 5 minutes from now
        return new ScheduledResponse(new GoWest(), TimeSpan.FromMinutes(5));
    }

    public ScheduledResponse Consume(MyMessage message)
    {
        // Process GoEast at 8 PM local time
        return new ScheduledResponse(new GoEast(), DateTime.Today.AddHours(20));
    }
}

RequeueLater

The RetryLater() mechanism in message error handling is using the scheduled execution. See Error Handling for more information.