feat(deferred): add periodic task scheduling#277
feat(deferred): add periodic task scheduling#277Abishekcs wants to merge 1 commit intorage-rb:mainfrom
Conversation
54bb689 to
5d0f4de
Compare
|
I have kept the design proposal same as suggested by @ShashantNagpure and @pratyush07-hub |
rsamoilov
left a comment
There was a problem hiding this comment.
Hi @Abishekcs ,
Left several comments. Apart from that, this looks great!
| def self.start(tasks) | ||
| return if tasks.empty? | ||
|
|
||
| elect_leader { register_timers(tasks) } |
There was a problem hiding this comment.
There's already an existing Rage::Internal.pick_a_worker method - you can use it instead.
|
|
||
| # Registers a task to run on a fixed interval (in seconds) | ||
| def every(interval, task:) | ||
| @scheduled_tasks << { interval:, task: } |
There was a problem hiding this comment.
Do you think it makes sense to validate that task is a class that includes Rage::Deferred::Task?
There was a problem hiding this comment.
That makes sense, since it will prevent newbie users like me from passing anything other than Rage::Deferred::Task when using Rails with Rage.
|
|
||
| # Evaluates the scheduling DSL block, making `every` available as a method | ||
| def schedule(&block) | ||
| instance_eval(&block) |
There was a problem hiding this comment.
This won't work with user-level constants - they are loaded after the framework is configured. So if a background task is defined in app/tasks, it won't be available during the configuration phase.
To fix this you'll need to update schedule to only store the block, postponing the execution of this block to the time when the scheduler starts. Also, keep in mind there could be multiple schedule calls.
There was a problem hiding this comment.
Thanks for the review. I will make the required changes.
5d0f4de to
8d4ad8c
Compare
Adds native recurring task scheduling to Rage::Deferred via a simple DSL, without relying on external tools like cron or third-party libraries.
## Public API
Rage.configure do
config.deferred.schedule do
every 1.hour, task: CleanupExpiredInvites
every 1.minute, task: ResetCache
end
end
## How It Works
- Uses Iodine's run_every timer primitive within the existing runtime.
- Schedule blocks are stored during configuration and evaluated at boot,
after all app constants are loaded. Multiple schedule calls are supported.
## Leader Election
Uses Rage::Internal.pick_a_worker with a fixed shared lock path so all workers compete on the same file. The winner registers timers, others stand by. When the leader dies, the OS releases the lock and the next worker to boot takes over. Timers reset on leader change.
## Design Decisions
- Overlaps allowed by default.
- First run waits for the first interval.
- Arguments not supported - tasks discover their own data at runtime.
- task must include Rage::Deferred::Task, validated at boot with ArgumentError.
- pick_a_worker updated to accept lock_path and handle Iodine.running?.
## Tests
Covers timer registration, task enqueue, lock path, and empty task list.
8d4ad8c to
9b8bc2a
Compare
|
Hi @rsamoilov, made the required changes based upon the comments you left, here is a quick summary:
|
#233
What this PR Does
Adds native recurring task scheduling to
Rage::Deferred, allowing tasks to run on a fixed interval without relying on external tools likecronor third-party libraries.Usage
Where
CleanupExpiredInvitesandResetCacheare standardRage::Deferred::Taskclasses with aperformmethod.Implementation
Uses Iodine's
run_everytimer primitive. No new threads or processes the scheduler runs within the existing Iodine runtime. When a timer fires, it callstask.enqueuewhich goes through the normal Rage::Deferred path WAL write, middleware, worker execution.Schedule blocks are stored during configuration and evaluated at boot after all app constants are loaded, so task classes defined in
app/tasks/are always available.Leader election uses
File#flockthe same pattern used byRage::Cable. Each worker tries a non-blocking exclusive lock on a shared file at boot. The winner registers timers, others stand by. When the leader dies, the OS releases the lock and the next spawned worker takes over.Design Decisions
Screenrecording:
4 workers, 1 worker wins leader election and 2 task scheduled4-workers-1-worker-wins-leader-election-2-task-scheduled_AWocXcyV.mp4
4 workers, 1 worker wins leader election and 2 task scheduled and leader worker dies after some time4-workers-1-worker-wins-leader-election-2-task-scheduled-leader-worker-dies.mp4