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

🐛 [BUG] Unhandled exception. System.ArgumentException: Invalid value '2671030968.9793' for parameter 'interval'. #22

Open
binn opened this issue Jan 1, 2022 · 15 comments
Assignees
Labels
bug Something isn't working
Projects

Comments

@binn
Copy link

binn commented Jan 1, 2022

Describe the bug
Not sure what's causing this, but it sometimes happens and sometimes doesn't happen.

To Reproduce
Steps to reproduce the behavior:
Here is the code I used.
image
The line in question I believe is the InactiveRemoverService.

Code of AddCronJob
image

Expected behavior
Job scheduled to run every two hours.

Desktop (please complete the following information):

  • OS: Ubuntu 18.04 I believe
  • Browser None
  • Version latest
@binn binn added the bug Something isn't working label Jan 1, 2022
@binn
Copy link
Author

binn commented Jan 1, 2022

My bad, it's this line
services.AddCronJob<MonthlyRemoverService>("0 0 1 * *");

Seems to be something wrong with the monthly cron.

@furkandeveloper furkandeveloper added this to To do in Bug Jan 1, 2022
@furkandeveloper
Copy link
Owner

Is the issue MonthlyRemoverService.cs or InactiveRemoverService.cs? @binn

@binn
Copy link
Author

binn commented Jan 17, 2022

@furkandeveloper sorry forgot to respond, it was MonthlyRemoverService.cs

Scheduling it every month causes this bug to happen for some reason

@furkandeveloper
Copy link
Owner

You're right, this error doesn't happen all the time.

I tried several times on the sample project, but it worked successfully every time.

image

I got the same error as you when I used the code below.

services.ApplyResulation<ConsoleCronJob>(options =>
{
         options.CronExpression = "* * * 12 *";
         options.TimeZoneInfo = TimeZoneInfo.Local;
});

I think the interval value should not be more than 25 days.

I tried the following value as I tried it on 18.01.

I got the same error as it was over 25 days.

services.ApplyResulation<ConsoleCronJob>(options =>
{
         options.CronExpression = "* * 13 2 *";
         options.TimeZoneInfo = TimeZoneInfo.Local;
});

I didn't get the error when the max was 25 days and the project was successful.

services.ApplyResulation<ConsoleCronJob>(options =>
{
         options.CronExpression = "* * 12 2 *";
         options.TimeZoneInfo = TimeZoneInfo.Local;
});

Let's summarize;

It uses Timer in EasyCronJob.

Timer cron is used to schedule jobs.

In schedule operations, the current time is subtracted from the time the job will run and is assigned as a delay.

Delay value must not be greater than int.MaxValue.

2671030968.9793 you are getting this error because this value is greater than int.MaxValue.

I guess there is nothing to do. You must use values not greater than int.MaxValue.

@binn
Copy link
Author

binn commented Jan 18, 2022

That is pretty weird, pretty faulty limitation. Is there a possible workaround?
The corn you specified will run once per 25 days, correct? (as in, the 25th of each month or something like that, regardless of the application's state / turn on / turn off)

In schedule operations, the current time is subtracted from the time the job will run and is assigned as a delay.
Delay value must not be greater than int.MaxValue.

Timer, being limited to an integer, may not be suitable then. Any other suggestions or patterns that may work out for the library?

@furkandeveloper
Copy link
Owner

I prepared a solution for you.

This solution contains Overengineering methods.

Startup.cs

services.ApplyResulation<YourCronJob>(options =>
{
    options.CronExpression = "0 0 25 * *";
    options.TimeZoneInfo = TimeZoneInfo.Local;
});

YourCronJob.cs

public class YourCronJob : CronJobService
{
    private System.Timers.Timer timer;
    private readonly ILogger<YourCronJob> logger;
    public YourCronJob(ICronConfiguration<YourCronJob> cronConfiguration, ILogger<YourCronJob> logger)
        : base(cronConfiguration.CronExpression, cronConfiguration.TimeZoneInfo)
    {
        this.logger = logger;
    }
    private TimeSpan CalculateTimerDelay()
    {
        DateTime next = new DateTime(DateTime.Now.Year, DateTime.Now.AddMonths(1).Month, 1);
        var delay = next - DateTimeOffset.Now;
        return delay;
    }
    public override Task StartAsync(CancellationToken cancellationToken)
    {
        var delay = CalculateTimerDelay();
        timer = new System.Timers.Timer(delay.TotalMilliseconds);
        logger.LogInformation("Start Your Job" + " Start Time : " + DateTime.UtcNow);
        return base.StartAsync(cancellationToken);
    }
    protected override Task ScheduleJob(CancellationToken cancellationToken)
    {
        logger.LogInformation("Schedule Your Job" + " Start Time : " + DateTime.UtcNow);
        return base.ScheduleJob(cancellationToken);
    }
    public override Task DoWork(CancellationToken cancellationToken)
    {
        logger.LogInformation("Timer Starting" + " Start Time : " + DateTime.UtcNow);
        timer.Elapsed += async (sender, args) =>
        {
            timer.Dispose();  // reset and dispose timer
            timer = null;
            Test();
        };
        timer.Start();
        return base.DoWork(cancellationToken);
    }
    public void Test()
    {
        var delay = CalculateTimerDelay();
        timer = new System.Timers.Timer(delay.TotalMilliseconds);
        logger.LogInformation("Working Your Job" + " Start Time : " + DateTime.UtcNow);
    }
}

Result

It will run on the 25th day of each month and create a timer until the first day of the next month.

So the int.MaxValue limit will not block you.

fyi @binn

@enisn
Copy link

enisn commented Jan 19, 2022

It's kinda workaround and seems like a good approach to get over the problem. Thanks @furkandeveloper

@binn
Copy link
Author

binn commented Jan 19, 2022

Thank you, I'll try it out in the coming days.

@Alchoder
Copy link

Alchoder commented Apr 3, 2023

Hello, I got the same problem. Maybe to fix this you could use something else than System.Timers.Timer here ?
image

Maybe System.Threading.Timer or
image

https://stackoverflow.com/questions/41284103/assign-a-double-value-to-timer-interval
https://stackoverflow.com/questions/34047810/timer-max-interval

@Alchoder
Copy link

Alchoder commented Apr 3, 2023

I'm trying to use your workaround, but I think it doesn't work like it is, I mean, if you call Test() (where I suppose one would put the CronJob code) and in this Test you set again the delay, this will be called on the first day of the month, the delay would be the difference between the first day of next month and the first day of current month, then you'll get a bigger delay that Int32.MaxValue and it will crash.

Or maybe I misunderstood something ?

Besides, I think that if your server is restarting for any reason, between the 25th and the 1st, you will have the same problem.

Finally, having the setting of the timer in StartAsync will give you problems if you start your server at the beginning of the month, for the same reasons we are having these problems. I moved the setting in the DoWork.

I think to complete this, the cron expression should be 0 0 15,25 * * or 0 0 10,25 * * instead of 0 0 25 * * so you won't have any trouble whenever your server is launched, the difference between the next occurence and the current day will always be less than 21 days (max value in days is 24.855)

I will post the solution I'm trying, not sure it will definitely work, I'll wait this month to see

@Alchoder
Copy link

Alchoder commented Apr 3, 2023

Not sure this will work, let me know about it.

    public class MyJob: CronJobService
    {
        private readonly ILogger<MyJob> _logger;
        private System.Timers.Timer timer;

        public MyJob(ICronConfiguration<MyJob> cronConfiguration,
                                        ILogger<MyJob> logger,
                                        IServiceProvider serviceProvider) :
            base(cronConfiguration, logger, serviceProvider)
        {
            _logger = logger;
        }

        public void DoJob()
        {
            _logger.LogInformation("DoJob - MyJob");

            //Code of what my job is supposed to do
        }

        public override Task StartAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation("Delayed Start [MyJob]");
            return base.StartAsync(cancellationToken);
        }

        protected override Task ScheduleJob(CancellationToken cancellationToken)
        {
            _logger.LogInformation("Custom Schedule [MyJob]");
            return base.ScheduleJob(cancellationToken);
        }

        public override Task DoWork(CancellationToken cancellationToken)
        {
            _logger.LogInformation("Delayed Timer Starting [MyJob]");
            var delay = CalculateTimerDelay();

            if (timer == null)
            {
                timer = new System.Timers.Timer(delay.TotalMilliseconds);

                timer.Elapsed += (sender, args) =>
                {
                    //Reset and dispose timer
                    timer.Dispose();
                    timer = null;
                    DoJob();
                };
                timer.Start();
            }

            return base.DoWork(cancellationToken);
        }

        private TimeSpan CalculateTimerDelay()
        {
            DateTime next = new DateTime(DateTime.Now.Year, DateTime.Now.AddMonths(1).Month, 1);
            var delay = next - DateTimeOffset.Now;
            return delay;
        }

    }

with EDITED

services.ApplyResulation<MyJob>(options =>
{
    options.CronExpression = "0 23 15,28,29,30,31 * *";
    options.TimeZoneInfo = TimeZoneInfo.Local;
});

@furkandeveloper
Copy link
Owner

Hello,
First of all, thank you for the detailed explanation.
Timer works with maximum int.MaxValue.
Therefore, some errors occur in such problems.
We've developed the solution you referenced above as a workaround.
Of course they may have mistakes.
The example implementation you shared seems to be correct.
DoWork is the part that will work for the cron job.
Here, when the time is up, resetting the timer object and re-defining a job may be the solution.
Please share the result with us at the end of the month.
If your implementation works correctly, we will add your solution to the Wiki section.

@Alchoder

@Alchoder
Copy link

Hello @furkandeveloper,

You're welcome :)

I understand.

I'll try to remember to let you know :)

@Alchoder
Copy link

Alchoder commented May 2, 2023

Hello,

It worked well for me :)

@Alchoder
Copy link

Alchoder commented Sep 8, 2023

Actually it did not work completely.
I replaced

services.ApplyResulation<MyJob>(options =>
{
    options.CronExpression = "0 0 15,25 * *";
    options.TimeZoneInfo = TimeZoneInfo.Local;
});

by

services.ApplyResulation<MyJob>(options =>
{
    options.CronExpression = "0 23 15,28,29,30,31 * *";
    options.TimeZoneInfo = TimeZoneInfo.Local;
});

Indeed, when the server was rebooting between the 25th and the end of the month, it could not compute the time when it should be launched.
With this new cron expression, it is launched every last day of month at 11P.M. Unfortunately, the way i wrote it, if the server reboots between 11P.M. and midnight, it won't work. I did set to 11:59 P.M. because I have treatments to make at 11:30.
Anyway, it should work 99,99% of time

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
Bug
To do
Development

No branches or pull requests

4 participants