Module: ChronoForge::Executor::Methods::DurablyRepeat
- Defined in:
- lib/chrono_forge/executor/methods/durably_repeat.rb
Instance Method Summary collapse
-
#durably_repeat(method, every:, till:, start_at: nil, retry_policy: nil, 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, retry_policy: nil, 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 asmy_task() - Method with parameter:
def my_task(next_execution_at); end- called asmy_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
timeoutare automatically skipped - The periodic schedule integrity is maintained
- Eventually reaches current/future execution times
Error Handling
- Individual execution failures are retried per the resolved RetryPolicy
- After max attempts, behavior depends on
on_errorparameter::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
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 153 154 155 156 157 158 159 160 161 162 163 |
# File 'lib/chrono_forge/executor/methods/durably_repeat.rb', line 105 def durably_repeat(method, every:, till:, start_at: nil, retry_policy: nil, timeout: 1.hour, on_error: :continue, name: nil) policy = step_retry_policy(retry_policy) validate_step_name_segment!(name || method) step_name = "durably_repeat$#{name || method}" # Get or create the main coordination log for this periodic task. A fresh # coordinator records its first pass in the INSERT (attempts: 1, # last_executed_at); only later passes bump via UPDATE. coordination_log = find_or_create_execution_log!(step_name) do |log| now = Time.current log.started_at = now log.last_executed_at = now log.attempts = 1 log. = {last_execution_at: nil} end # Return if already completed return if coordination_log.completed? # Update coordination log attempt tracking (first pass already recorded # above on create). unless coordination_log.previously_new_record? coordination_log.update!( attempts: coordination_log.attempts + 1, last_executed_at: Time.current ) end # 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 next_execution_at = fast_forward_expired_prefix(coordination_log, next_execution_at, every, timeout) execute_or_schedule_repetition(method, coordination_log, next_execution_at, every, policy, timeout, on_error) nil end |