EasyCronJob icon indicating copy to clipboard operation
EasyCronJob copied to clipboard

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

Open binn opened this issue 3 years ago • 16 comments

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 avatar Jan 01 '22 02:01 binn

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

Seems to be something wrong with the monthly cron.

binn avatar Jan 01 '22 02:01 binn

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

furkandeveloper avatar Jan 01 '22 11:01 furkandeveloper

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

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

binn avatar Jan 17 '22 02:01 binn

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.

furkandeveloper avatar Jan 18 '22 12:01 furkandeveloper

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?

binn avatar Jan 18 '22 13:01 binn

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

furkandeveloper avatar Jan 18 '22 15:01 furkandeveloper

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

enisn avatar Jan 19 '22 05:01 enisn

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

binn avatar Jan 19 '22 08:01 binn

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 avatar Apr 03 '23 10:04 Alchoder

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 avatar Apr 03 '23 11:04 Alchoder

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;
});

Alchoder avatar Apr 03 '23 11:04 Alchoder

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

furkandeveloper avatar Apr 03 '23 22:04 furkandeveloper

Hello @furkandeveloper,

You're welcome :)

I understand.

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

Alchoder avatar Apr 19 '23 09:04 Alchoder

Hello,

It worked well for me :)

Alchoder avatar May 02 '23 07:05 Alchoder

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

Alchoder avatar Sep 08 '23 12:09 Alchoder