Class: Fractor::Workflow::JobExecutor
- Inherits:
-
Object
- Object
- Fractor::Workflow::JobExecutor
- Defined in:
- lib/fractor/workflow/execution/job_executor.rb
Overview
Executes a single workflow job, handling all aspects of job execution including input building, work creation, and supervisor orchestration.
Instance Attribute Summary collapse
-
#context ⇒ Object
readonly
Returns the value of attribute context.
-
#dead_letter_queue ⇒ Object
readonly
Returns the value of attribute dead_letter_queue.
-
#logger ⇒ Object
readonly
Returns the value of attribute logger.
Instance Method Summary collapse
-
#execute_once(job, job_trace = nil) ⇒ Object
Execute a job once (no retry logic).
-
#execute_with_circuit_breaker(job, work, _job_trace = nil) ⇒ Object
Execute a job with circuit breaker protection.
-
#execute_with_retry(job, job_trace = nil) ⇒ Object
Execute a job with retry logic.
-
#execute_with_supervisor(job, work) ⇒ Object
Execute a job using a supervisor.
-
#initialize(context, logger, workflow: nil, completed_jobs: nil, failed_jobs: nil, dead_letter_queue: nil, circuit_breakers: nil) ⇒ JobExecutor
constructor
Initialize the job executor.
Constructor Details
#initialize(context, logger, workflow: nil, completed_jobs: nil, failed_jobs: nil, dead_letter_queue: nil, circuit_breakers: nil) ⇒ JobExecutor
Initialize the job executor.
23 24 25 26 27 28 29 30 31 32 |
# File 'lib/fractor/workflow/execution/job_executor.rb', line 23 def initialize(context, logger, workflow: nil, completed_jobs: nil, failed_jobs: nil, dead_letter_queue: nil, circuit_breakers: nil) @context = context @logger = logger @workflow = workflow @completed_jobs = completed_jobs || Set.new @failed_jobs = failed_jobs || Set.new @dead_letter_queue = dead_letter_queue @circuit_breakers = circuit_breakers || CircuitBreakerRegistry.new end |
Instance Attribute Details
#context ⇒ Object (readonly)
Returns the value of attribute context.
12 13 14 |
# File 'lib/fractor/workflow/execution/job_executor.rb', line 12 def context @context end |
#dead_letter_queue ⇒ Object (readonly)
Returns the value of attribute dead_letter_queue.
12 13 14 |
# File 'lib/fractor/workflow/execution/job_executor.rb', line 12 def dead_letter_queue @dead_letter_queue end |
#logger ⇒ Object (readonly)
Returns the value of attribute logger.
12 13 14 |
# File 'lib/fractor/workflow/execution/job_executor.rb', line 12 def logger @logger end |
Instance Method Details
#execute_once(job, job_trace = nil) ⇒ Object
Execute a job once (no retry logic).
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/fractor/workflow/execution/job_executor.rb', line 39 def execute_once(job, job_trace = nil) # Build input for this job job_input = @context.build_job_input(job) job_trace&.set_input(job_input) # Create work item - if job_input is already a Work object, use it directly # to avoid double-wrapping (e.g., when using custom Work subclasses) work = if job_input.is_a?(Work) job_input else Work.new(job_input) end # Execute with circuit breaker if configured if job.circuit_breaker_enabled? execute_with_circuit_breaker(job, work, job_trace) else execute_with_supervisor(job, work) end end |
#execute_with_circuit_breaker(job, work, _job_trace = nil) ⇒ Object
Execute a job with circuit breaker protection.
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 |
# File 'lib/fractor/workflow/execution/job_executor.rb', line 123 def execute_with_circuit_breaker(job, work, _job_trace = nil) breaker_key = job.circuit_breaker_key # Get or create circuit breaker orchestrator for this job orchestrator = @circuit_breakers.get_or_create_orchestrator( breaker_key, **job.circuit_breaker_config.slice(:threshold, :timeout, :half_open_calls), job_name: job.name, debug: ENV["FRACTOR_DEBUG"] == "1", ) # Log circuit state before execution log_circuit_breaker_state(job, orchestrator) begin orchestrator.execute_with_breaker(job) do execute_with_supervisor(job, work) end rescue Workflow::CircuitOpenError => e log_circuit_breaker_open(job, orchestrator) raise WorkflowExecutionError, "Circuit breaker open for job '#{job.name}': #{e.}" end end |
#execute_with_retry(job, job_trace = nil) ⇒ Object
Execute a job with retry logic.
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/fractor/workflow/execution/job_executor.rb', line 65 def execute_with_retry(job, job_trace = nil) retry_config = job.retry_config # Create retry orchestrator with the job's retry configuration orchestrator = RetryOrchestrator.new(retry_config, debug: ENV["FRACTOR_DEBUG"] == "1") # Execute with retry logic orchestrator.execute_with_retry(job) do |j| execute_once(j, job_trace) end rescue StandardError => e # Get retry state for DLQ entry retry_state = orchestrator.state add_to_dead_letter_queue(job, e, retry_state) raise e end |
#execute_with_supervisor(job, work) ⇒ Object
Execute a job using a supervisor.
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/fractor/workflow/execution/job_executor.rb', line 88 def execute_with_supervisor(job, work) supervisor = Supervisor.new( worker_pools: [ { worker_class: job.worker_class, num_workers: job.num_workers || 1, }, ], ) supervisor.add_work_item(work) supervisor.run # Check for errors first (before checking results) unless supervisor.results.errors.empty? error = supervisor.results.errors.first raise WorkflowExecutionError, "Job '#{job.name}' encountered error: #{error.error}" end # Get the result results = supervisor.results.results if results.empty? raise WorkflowExecutionError, "Job '#{job.name}' produced no results" end results.first.result end |