Module: ChronoForge::Executor::Methods::DurablyRepeat
- Included in:
- ChronoForge::Executor::Methods
- Defined in:
- lib/chrono_forge/executor/methods/durably_repeat.rb
Instance Method Summary collapse
-
#durably_repeat(method, every:, till:, start_at: nil, max_attempts: 3, timeout: 1.hour, on_error: :continue, name: nil) ⇒ nil
Schedules a method to be called repeatedly at specified intervals until a condition is met.
Instance Method Details
#durably_repeat(method, every:, till:, start_at: nil, max_attempts: 3, timeout: 1.hour, on_error: :continue, name: nil) ⇒ nil
Schedules a method to be called repeatedly at specified intervals until a condition is met.
This method provides durable, idempotent periodic task execution with automatic catch-up for missed executions using timeout-based fast-forwarding. Each repetition gets its own execution log, ensuring proper tracking and retry behavior.
Behavior
Method Parameters
Your periodic method can optionally receive the scheduled execution time:
-
Method with no parameters: ‘def my_task; end` - called as `my_task()`
-
Method with parameter: ‘def my_task(next_execution_at); end` - called as `my_task(scheduled_time)`
This allows methods to:
-
Log lateness/timing information
-
Perform time-based calculations
-
Include scheduled time in notifications
-
Generate reports for specific time periods
Idempotency
Each execution gets a unique step name based on the scheduled execution time, ensuring that workflow replays don’t create duplicate tasks.
Catch-up Mechanism
If a workflow is paused and resumes later, the timeout parameter handles catch-up:
-
Executions older than ‘timeout` are automatically skipped
-
The periodic schedule integrity is maintained
-
Eventually reaches current/future execution times
Error Handling
-
Individual execution failures are retried up to ‘max_attempts` with exponential backoff
-
After max attempts, behavior depends on ‘on_error` parameter:
-
‘:continue`: Failed execution is logged, next execution is scheduled
-
‘:fail_workflow`: ExecutionFailedError is raised, failing the entire workflow
-
-
Timeouts are not considered errors and always continue to the next execution
Execution Logs
Creates two types of execution logs:
-
Coordination log: ‘durably_repeat$#name` - tracks overall periodic task state
-
Repetition logs: ‘durably_repeat$#name$#timestamp` - tracks individual executions
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
# File 'lib/chrono_forge/executor/methods/durably_repeat.rb', line 103 def durably_repeat(method, every:, till:, start_at: nil, max_attempts: 3, timeout: 1.hour, on_error: :continue, name: nil) step_name = "durably_repeat$#{name || method}" # Get or create the main coordination log for this periodic task coordination_log = ExecutionLog.create_or_find_by!( workflow: @workflow, step_name: step_name ) do |log| log.started_at = Time.current log. = {last_execution_at: nil} end # Return if already completed return if coordination_log.completed? # Update coordination log attempt tracking coordination_log.update!( attempts: coordination_log.attempts + 1, last_executed_at: Time.current ) # Check if we should stop repeating condition_met = if till.is_a?(Symbol) send(till) else till.call(context) end if condition_met coordination_log.update!( state: :completed, completed_at: Time.current ) return end # Calculate next execution time = coordination_log. last_execution_at = ["last_execution_at"] ? Time.parse(["last_execution_at"]) : nil next_execution_at = if last_execution_at last_execution_at + every elsif start_at start_at else coordination_log.created_at + every end execute_or_schedule_repetition(method, coordination_log, next_execution_at, every, max_attempts, timeout, on_error) nil end |