-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
Add Support for RRULE (iCalendar Recurrence) in Hangfire #2435
Comments
Thank you for raising this important issue regarding RRULE support in Hangfire. This feature would be precious, and I'd like to add my support and perspective to this discussion. The company I work for also desperately needs this feature. We're facing similar challenges with scheduling tasks requiring precise recurrence, which Cron expressions can only handle somewhat. Your examples clearly illustrate Cron's limitations when dealing with intervals that span time boundaries. This feature is so critical that we would be willing to pay for it, even if it were only available as part of Hangfire Pro. Using RRULE for scheduling would significantly improve our application's functionality and reliability. A native implementation within Hangfire would be the most robust and efficient solution. As a side note, I'd like to share some experience regarding the libraries mentioned. While your example uses ICal.NET, I've found that the EWSoftware.PDI NuGet package offers better performance and an API that more closely aligns with the RFC regarding the terminology used. To illustrate this, I've conducted a naive benchmark comparing EWSoftware.PDI and ICal.NET. While this is not an exhaustive test, it does demonstrate a significant performance difference: [MemoryDiagnoser]
[Orderer(BenchmarkDotNet.Order.SummaryOrderPolicy.FastestToSlowest)]
[RankColumn]
public class RRuleBenchmarks
{
private const string RRULE = "FREQ=HOURLY;INTERVAL=7";
private const string Start = "2024-08-23T06:00:00.000Z";
private static readonly DateTime StartDate = DateTime.Parse(Start, null, System.Globalization.DateTimeStyles.RoundtripKind);
private CalendarEvent _ical = null!;
private Recurrence _pdi = null!;
[GlobalSetup]
public void GlobalSetup()
{
_ical = new CalendarEvent
{
Start = new CalDateTime(StartDate),
RecurrenceRules = new List<RecurrencePattern> { new(RRULE) },
};
_pdi = new Recurrence(RRULE)
{
StartDateTime = StartDate
};
}
[Benchmark(Baseline = true)]
public List<DateTime> Ical_Benchmark() => _ical
.GetOccurrences(DateTime.UtcNow, DateTime.UtcNow.AddYears(1))
.Select(x => x.Period.StartTime.AsUtc)
.ToList();
[Benchmark]
public List<DateTime> PDI_Benchmark() => _pdi
.InstancesBetween(DateTime.UtcNow, DateTime.UtcNow.AddYears(1))
.ToList();
}
The results show that the PDI library significantly outperforms ICal.NET in speed and memory usage, which could be crucial for applications dealing with many recurring events. |
Thank you for the benchmark. I want to add one point I just give the example as demonstration purpose. For the the both solution the occurrence array length will be less than 2. So I think benchmark won't be a factor here. You need to handle the search end date based on your frequency. As I have mentioned solution 1 is straightforward only the drawback is hangfire server will be busy for every minute/hour interval which is not needed. Despite some drawbacks, I have implemented two solutions based on the procedure of 2nd approach. I am middle of something hopefully I will share the code within a couple of days. Also I want to know are there any other packages which support this kind of RRULE based feature (Even paid) for scheduling. |
Thank you for your thoughtful response and for sharing your progress on implementing solutions. I appreciate your insights and would like to address a few points:
Your work on this is greatly appreciated, and I'm looking forward to seeing your implementation. The ability to handle RRULE natively in Hangfire would be a significant improvement for many users, including myself and my company. |
You can use Ical.NET or PDI based on your requirement. But I see Ical.NET supports iCalendar (RFC 5545) where PDI doesn't mention about it. And according to chat-gpt The executions of the recurring job won't be group. The last job will register next job so hang-fire treat the job separately. |
Solution 1 based Dynamically Register Next Job: (Complete Solution)Interface
Service:
Model:
Here
Custom Message Model:
Extensions:
Note: Here i have used PostgresSQL that's why I have checked the current connection is Utility:
Controller:
Startup Registration:
Observations / Some Key Points:
Am I missing any kind of test case? Any feedback will be appreciated. |
Solution 2 based Dynamically Register Next Job: (Partial Solution)Interface:
Service:
How does the solution work?We need to track the schedule Job id against the reminder Id to delete or update the event. In previous solution I have solve it to query in Job table and then delete. In this solution I have solved it by storing it in the Hangfire How to make this solution more accurate?
As you see this solution is too complicated and it's possible hangfire will be internally broken if we don't careful enough. That's why I won't go with this solution. But the provided solution will work if we ignore transaction and lock. And also we can introduce |
@ShayMusachanov-dev Have you got the time to see the implementation? I have tried PDI |
I'm proposing the addition of RRULE support in Hangfire, similar to the recurrence rules used in Google Calendar's ICS files. Currently, Hangfire relies on Cron expressions for scheduling, but they fall short in scenarios where precise recurrence is needed, as defined by RRULE.
Problem:
Consider the following examples:
For example, the Cron expression for
FREQ=MINUTELY;INTERVAL=7
is*/7 * * * *
, which triggers at the 0th, 7th, 14th, 21st, etc., minute within each hour. However, this does not align with the intended behavior of running a job every 7 minutes from a specific start time.Let's say the first job runs at 00:07 (7th minute). The next expected run time should be 00:14, followed by 00:21, and so on. The issue arises at the 56th minute. After 00:56, the Cron expression */7 * * * * schedules the next run at 01:00, which is incorrect according to the RRULE. The correct next run time should be 01:03 (7 minutes after 00:56), as the job should run every 7 minutes continuously, not reset at the start of the hour.
This problem exists across all types of frequencies where Cron expressions are used, leading to misaligned scheduling when intervals span across hours, days, or other time units.
Proposed Solutions:
Sol 1 - Recurring Job with Frequent (Every minute, hour, daily, monthly based on the FREQ) Trigger:
Set up a recurring job to trigger every minute (* * * * *) or hour (
5 * * * *
), depending on the frequency.Use a library like ICal.NET to check whether the current time matches the next occurrence based on the RRULE.
Problem: This solution can lead to a significant increase in server load, as the job triggers frequently and performs additional checks, which is inefficient.
Sol 2 - Dynamically Register Next Job:
Calculate the first occurrence of the job based on the RRULE and register a
scheduled
job.When the job executes, calculate the next occurrence and register a new scheduled job accordingly.
Problem: While this reduces server load by only triggering jobs when needed, it introduces complexity. If the server fails to register the next job due to downtime or other issues, the recurrence chain could break. Although Hangfire preserves jobs if the server is temporarily down, there are still edge cases where failures could disrupt the job schedule.
Example Code:
Here's a basic example using ICal.NET to get the next occurrence:
Expectation:
It would be highly beneficial for Hangfire to support RRULEs natively, as this is a common requirement for scheduling tasks, especially in applications that handle calendar-like events. Moreover, RRULEs can also include complex exception rules, which are difficult to manage with Cron alone.
Is anyone else encountering similar challenges? How have you addressed them? I'd love to hear your solutions or thoughts on this feature request.
The text was updated successfully, but these errors were encountered: