Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding new task to scheduler from another service #77

Closed
soend opened this issue Feb 5, 2019 · 6 comments
Closed

Adding new task to scheduler from another service #77

soend opened this issue Feb 5, 2019 · 6 comments

Comments

@soend
Copy link

soend commented Feb 5, 2019

I would like to add tasks to scheduler dynamically in a service what gets the execution times from configuration file. At the moment its done in the application Configure method and every time configuration is changed i would have to restart the whole program. Is this something what could be possible with current scheduler implementation?

@soend
Copy link
Author

soend commented Feb 5, 2019

For testing i tried tried injecting IScheduler and adding new task to it and it actually worked. Code what i tried:

public ScheduleOrderService(ILogger<ScheduleOrderService> logger, IOptions<AppConfig> config, IScheduler scheduler)
{
    _logger = logger;
    _config = config.Value;
    scheduler.Schedule(() => _logger.LogInformation("foobar") ).EveryMinute();
}

@jamesmh
Copy link
Owner

jamesmh commented Feb 5, 2019

If there's a way to hook into some event that's emited when the config files change, here's what you could do:

When scheduling, add an ID to each scheduled event (this is an internal method, which maybe I should make public via the interface...)

scheduler.EveryMinute().AssignUniqueIndentifier("SomeID");

Keep track of all the IDS that you have (statically).

Then, when you need to refresh - do this for each ID:

scheduler.TryUnschedule("SomeID");

Then re-schedule again.

Those are thread safe methods too.

Not necessarily recommended for production, but would work in dev for sure (assuming you can detect when the config files change - which Im looking into)

@jamesmh
Copy link
Owner

jamesmh commented Feb 5, 2019

So you can detect when the IConfiguration is changed.

Take a look here https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.configuration.iconfiguration.getreloadtoken?view=aspnetcore-2.2

@soend
Copy link
Author

soend commented Feb 5, 2019

There will be actually just one scheduled task ever created what i would like to change the execution time on. So if there would be a way to unschedule it and schedule new one that should do the business.

@jamesmh
Copy link
Owner

jamesmh commented Feb 5, 2019

So try in Configure()

ChangeToken.OnChange(() => this.Configuration.GetReloadToken(), () => {
    using (var scope = app.ApplicationServices.CreateScope())
    {
        IScheduler scheduler = scope.ServiceProvider.GetRequiredService<IScheduler>();
        IConfiguration config = scope.ServiceProvider.GetRequiredService<IConfiguration>();

        // Remove your past scheduled event/task
        (scheduler as Scheduler).TryUnschedule("SomeID");

        // Add a new one.
        var scheduledEvent = scheduler
            .Schedule(() => Console.WriteLine("A new scheduled task!"))
            .Cron(config["MyCronConfigValue"]);
        
        // Assign a unique ID so you can remove it next time.
        (scheduledEvent as ScheduledEvent).AssignUniqueIndentifier("SomeID");
        //Or
        scheduledEvent.PreventOverlapping("SomeID");
    }
});

@jhelmink
Copy link

jhelmink commented Feb 8, 2024

This can stay closed, but in case anyone is looking for a generic way to update scheduled tasks using reflection;

/// <summary>
/// Update the schedule for a specific service endpoint.
/// </summary>
/// <param name="fullServiceName">Fully qualified service name</param>
/// <param name="cron">string representation of a CRON schedule (5 values)</param>
/// <param name="scheduler">[FromServices] so not required</param>
/// <returns>string of result</returns>
public async Task<IResult> UpdateServiceSchedule(string fullServiceName, string cron, [FromServices] IScheduler scheduler)
{
    _log.LogInformation($"UpdateServiceSchedule called for {fullServiceName} setting CRON to: [{cron}]");

    if (string.IsNullOrEmpty(fullServiceName))
        return Results.BadRequest("Service Name is required");

    // Validate the cron
    if (string.IsNullOrEmpty(cron))
        return Results.BadRequest("CRON is required");
    else if (!CronSchedules.IsValidCRONSchedule(cron))
        return Results.BadRequest("CRON is not valid");

    // Try to reschedule as per;
    // https://github.com/jamesmh/coravel/issues/77#issuecomment-460785919
    try
    {
        // Remove your past scheduled event/task
        var unscheduleSuccess = (scheduler as Scheduler)!.TryUnschedule(fullServiceName);
        if (!unscheduleSuccess)
        {
            var errorMessage = $"Error unscheduling service: {fullServiceName}";
            _log.LogError(errorMessage);
            return Results.BadRequest(errorMessage);
        }

        // Check we have configured a class for this service
        var serviceType = Type.GetType("ServiceBroker.ScheduledInvocables." + fullServiceName);
        if (serviceType == null)
        {
            var errorMessage = $"Error finding service type for scheduling: {fullServiceName}";
            _log.LogError(errorMessage);
            return Results.BadRequest(errorMessage);
        }

        // Check if the serviceType implements the IInvocable interface
        Type interfaceType = typeof(IInvocable);
        bool implementsInterface = serviceType.GetInterfaces().Contains(interfaceType);
        if(!implementsInterface)
        {
            var errorMessage = $"Service type {fullServiceName} exists but is not invocable for scheduling";
            _log.LogError(errorMessage);
            return Results.BadRequest(errorMessage);
        }

        // Add a new cron schedule for the type we want to re-schedule
        var scheduledEvent = scheduler
            .ScheduleInvocableType(serviceType)
            .Cron(cron);

        // Assign a unique Id so you can remove and update it next time.
        var scheduleEventConfig = (scheduledEvent as ScheduledEvent)!.AssignUniqueIndentifier(fullServiceName);
        scheduleEventConfig = scheduledEvent.PreventOverlapping(fullServiceName);

        // We did it!
        var successMessage = $"UpdateServiceSchedule updated {fullServiceName} CRON setting to: [{cron}]";
        _log.LogInformation(successMessage);

        return Results.Ok(successMessage);
    }
    catch (Exception ex)
    {
        var exceptionMessage = $"Error updating service schedule: {fullServiceName} CRON setting to: [{cron}]";
        _log.LogError(ex, exceptionMessage);
        return Results.BadRequest(exceptionMessage);
    }
}

Many thanks to James for ScheduleInvocableType(serviceType) option!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants