conductor_ruby 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +142 -0
- data/LICENSE +190 -0
- data/README.md +517 -0
- data/examples/agentic_workflows/llm_chat.rb +106 -0
- data/examples/dynamic_workflow.rb +177 -0
- data/examples/event_handler.rb +94 -0
- data/examples/event_listener_examples.rb +430 -0
- data/examples/helloworld/greetings_worker.rb +24 -0
- data/examples/helloworld/helloworld.rb +99 -0
- data/examples/kitchensink.rb +213 -0
- data/examples/metadata_journey.rb +189 -0
- data/examples/metrics_example.rb +284 -0
- data/examples/new_dsl_demo.rb +141 -0
- data/examples/orkes/http_poll.rb +83 -0
- data/examples/orkes/secrets_example.rb +69 -0
- data/examples/orkes/wait_for_webhook.rb +90 -0
- data/examples/prompt_journey.rb +245 -0
- data/examples/rag_workflow.rb +167 -0
- data/examples/schedule_journey.rb +244 -0
- data/examples/simple_worker.rb +125 -0
- data/examples/simple_workflow.rb +89 -0
- data/examples/task_context_example.rb +257 -0
- data/examples/task_listener_example.rb +192 -0
- data/examples/worker_configuration_example.rb +282 -0
- data/examples/workflow_dsl.rb +316 -0
- data/examples/workflow_ops.rb +305 -0
- data/lib/conductor/client/authorization_client.rb +238 -0
- data/lib/conductor/client/integration_client.rb +108 -0
- data/lib/conductor/client/metadata_client.rb +139 -0
- data/lib/conductor/client/prompt_client.rb +58 -0
- data/lib/conductor/client/scheduler_client.rb +132 -0
- data/lib/conductor/client/schema_client.rb +32 -0
- data/lib/conductor/client/secret_client.rb +48 -0
- data/lib/conductor/client/task_client.rb +168 -0
- data/lib/conductor/client/workflow_client.rb +242 -0
- data/lib/conductor/configuration/authentication_settings.rb +17 -0
- data/lib/conductor/configuration.rb +103 -0
- data/lib/conductor/exceptions.rb +86 -0
- data/lib/conductor/http/api/application_resource_api.rb +107 -0
- data/lib/conductor/http/api/authorization_resource_api.rb +56 -0
- data/lib/conductor/http/api/event_resource_api.rb +133 -0
- data/lib/conductor/http/api/gateway_auth_resource_api.rb +48 -0
- data/lib/conductor/http/api/group_resource_api.rb +76 -0
- data/lib/conductor/http/api/integration_resource_api.rb +145 -0
- data/lib/conductor/http/api/metadata_resource_api.rb +231 -0
- data/lib/conductor/http/api/prompt_resource_api.rb +81 -0
- data/lib/conductor/http/api/role_resource_api.rb +60 -0
- data/lib/conductor/http/api/scheduler_resource_api.rb +211 -0
- data/lib/conductor/http/api/schema_resource_api.rb +82 -0
- data/lib/conductor/http/api/secret_resource_api.rb +134 -0
- data/lib/conductor/http/api/task_resource_api.rb +321 -0
- data/lib/conductor/http/api/token_resource_api.rb +42 -0
- data/lib/conductor/http/api/user_resource_api.rb +59 -0
- data/lib/conductor/http/api/workflow_bulk_resource_api.rb +91 -0
- data/lib/conductor/http/api/workflow_resource_api.rb +451 -0
- data/lib/conductor/http/api_client.rb +437 -0
- data/lib/conductor/http/models/authentication_config.rb +67 -0
- data/lib/conductor/http/models/authorization_request.rb +39 -0
- data/lib/conductor/http/models/base_model.rb +162 -0
- data/lib/conductor/http/models/bulk_response.rb +39 -0
- data/lib/conductor/http/models/conductor_application.rb +39 -0
- data/lib/conductor/http/models/conductor_user.rb +53 -0
- data/lib/conductor/http/models/create_or_update_application_request.rb +24 -0
- data/lib/conductor/http/models/create_or_update_role_request.rb +27 -0
- data/lib/conductor/http/models/event_handler.rb +130 -0
- data/lib/conductor/http/models/generate_token_request.rb +27 -0
- data/lib/conductor/http/models/group.rb +36 -0
- data/lib/conductor/http/models/integration.rb +70 -0
- data/lib/conductor/http/models/integration_api.rb +53 -0
- data/lib/conductor/http/models/integration_api_update.rb +43 -0
- data/lib/conductor/http/models/integration_update.rb +36 -0
- data/lib/conductor/http/models/permission.rb +24 -0
- data/lib/conductor/http/models/poll_data.rb +33 -0
- data/lib/conductor/http/models/prompt_template.rb +59 -0
- data/lib/conductor/http/models/prompt_template_test_request.rb +43 -0
- data/lib/conductor/http/models/rerun_workflow_request.rb +37 -0
- data/lib/conductor/http/models/role.rb +27 -0
- data/lib/conductor/http/models/schema_def.rb +59 -0
- data/lib/conductor/http/models/search_result.rb +187 -0
- data/lib/conductor/http/models/skip_task_request.rb +27 -0
- data/lib/conductor/http/models/start_workflow_request.rb +68 -0
- data/lib/conductor/http/models/subject_ref.rb +35 -0
- data/lib/conductor/http/models/tag_object.rb +36 -0
- data/lib/conductor/http/models/target_ref.rb +39 -0
- data/lib/conductor/http/models/task.rb +156 -0
- data/lib/conductor/http/models/task_def.rb +95 -0
- data/lib/conductor/http/models/task_exec_log.rb +30 -0
- data/lib/conductor/http/models/task_result.rb +115 -0
- data/lib/conductor/http/models/task_result_status.rb +24 -0
- data/lib/conductor/http/models/token.rb +33 -0
- data/lib/conductor/http/models/upsert_group_request.rb +30 -0
- data/lib/conductor/http/models/upsert_user_request.rb +39 -0
- data/lib/conductor/http/models/workflow.rb +202 -0
- data/lib/conductor/http/models/workflow_def.rb +73 -0
- data/lib/conductor/http/models/workflow_schedule.rb +100 -0
- data/lib/conductor/http/models/workflow_state_update.rb +30 -0
- data/lib/conductor/http/models/workflow_status_constants.rb +57 -0
- data/lib/conductor/http/models/workflow_task.rb +169 -0
- data/lib/conductor/http/models/workflow_test_request.rb +67 -0
- data/lib/conductor/http/rest_client.rb +211 -0
- data/lib/conductor/orkes/models/access_key.rb +56 -0
- data/lib/conductor/orkes/models/granted_permission.rb +27 -0
- data/lib/conductor/orkes/models/metadata_tag.rb +15 -0
- data/lib/conductor/orkes/models/rate_limit_tag.rb +15 -0
- data/lib/conductor/orkes/orkes_clients.rb +69 -0
- data/lib/conductor/version.rb +5 -0
- data/lib/conductor/worker/events/conductor_event.rb +40 -0
- data/lib/conductor/worker/events/global_dispatcher.rb +37 -0
- data/lib/conductor/worker/events/http_events.rb +25 -0
- data/lib/conductor/worker/events/listener_registry.rb +40 -0
- data/lib/conductor/worker/events/listeners.rb +34 -0
- data/lib/conductor/worker/events/sync_event_dispatcher.rb +78 -0
- data/lib/conductor/worker/events/task_runner_events.rb +271 -0
- data/lib/conductor/worker/events/workflow_events.rb +49 -0
- data/lib/conductor/worker/fiber_executor.rb +532 -0
- data/lib/conductor/worker/ractor_task_runner.rb +501 -0
- data/lib/conductor/worker/task_context.rb +114 -0
- data/lib/conductor/worker/task_definition_registrar.rb +322 -0
- data/lib/conductor/worker/task_handler.rb +360 -0
- data/lib/conductor/worker/task_in_progress.rb +60 -0
- data/lib/conductor/worker/task_runner.rb +538 -0
- data/lib/conductor/worker/telemetry/metrics_collector.rb +196 -0
- data/lib/conductor/worker/telemetry/prometheus_backend.rb +224 -0
- data/lib/conductor/worker/worker.rb +355 -0
- data/lib/conductor/worker/worker_config.rb +154 -0
- data/lib/conductor/worker/worker_registry.rb +71 -0
- data/lib/conductor/workflow/dsl/input_ref.rb +37 -0
- data/lib/conductor/workflow/dsl/output_ref.rb +44 -0
- data/lib/conductor/workflow/dsl/parallel_builder.rb +49 -0
- data/lib/conductor/workflow/dsl/switch_builder.rb +74 -0
- data/lib/conductor/workflow/dsl/task_ref.rb +178 -0
- data/lib/conductor/workflow/dsl/workflow_builder.rb +1016 -0
- data/lib/conductor/workflow/dsl/workflow_definition.rb +150 -0
- data/lib/conductor/workflow/llm/chat_message.rb +47 -0
- data/lib/conductor/workflow/llm/embedding_model.rb +19 -0
- data/lib/conductor/workflow/llm/tool_call.rb +43 -0
- data/lib/conductor/workflow/llm/tool_spec.rb +46 -0
- data/lib/conductor/workflow/task_type.rb +68 -0
- data/lib/conductor/workflow/timeout_policy.rb +31 -0
- data/lib/conductor/workflow/workflow_executor.rb +373 -0
- data/lib/conductor.rb +192 -0
- metadata +359 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Simple example demonstrating worker functionality with Conductor Ruby SDK
|
|
5
|
+
#
|
|
6
|
+
# Prerequisites:
|
|
7
|
+
# 1. Conductor server running on localhost:7001 (OSS version)
|
|
8
|
+
# 2. A workflow that uses 'simple_task' task type
|
|
9
|
+
# 3. Bundle install completed
|
|
10
|
+
#
|
|
11
|
+
# Usage:
|
|
12
|
+
# bundle exec ruby examples/simple_worker.rb
|
|
13
|
+
|
|
14
|
+
require_relative '../lib/conductor'
|
|
15
|
+
|
|
16
|
+
puts '=' * 60
|
|
17
|
+
puts 'Conductor Ruby SDK - Simple Worker Example'
|
|
18
|
+
puts '=' * 60
|
|
19
|
+
|
|
20
|
+
# Configure Conductor client
|
|
21
|
+
config = Conductor::Configuration.new(
|
|
22
|
+
server_api_url: 'http://localhost:7001/api'
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
puts "\nConnecting to Conductor server at: #{config.server_url}"
|
|
26
|
+
|
|
27
|
+
# Example 1: Class-based worker
|
|
28
|
+
class SimpleWorker
|
|
29
|
+
include Conductor::Worker::WorkerModule
|
|
30
|
+
|
|
31
|
+
worker_task 'simple_task', poll_interval: 1
|
|
32
|
+
|
|
33
|
+
def execute(task)
|
|
34
|
+
puts "\n[SimpleWorker] Executing task: #{task.task_id}"
|
|
35
|
+
puts "[SimpleWorker] Input: #{task.input_data.inspect}"
|
|
36
|
+
|
|
37
|
+
# Get input
|
|
38
|
+
name = get_input(task, 'name', 'World')
|
|
39
|
+
|
|
40
|
+
# Do some work
|
|
41
|
+
sleep 0.5
|
|
42
|
+
result_message = "Hello, #{name}!"
|
|
43
|
+
|
|
44
|
+
# Create result
|
|
45
|
+
result = Conductor::Http::Models::TaskResult.complete
|
|
46
|
+
result.add_output_data('message', result_message)
|
|
47
|
+
result.add_output_data('processed_at', Time.now.to_s)
|
|
48
|
+
result.log("Processed greeting for #{name}")
|
|
49
|
+
|
|
50
|
+
puts "[SimpleWorker] Result: #{result_message}"
|
|
51
|
+
result
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Example 2: Block-based worker
|
|
56
|
+
math_worker = Conductor::Worker.define('math_task', poll_interval: 1) do |task|
|
|
57
|
+
puts "\n[MathWorker] Executing task: #{task.task_id}"
|
|
58
|
+
puts "[MathWorker] Input: #{task.input_data.inspect}"
|
|
59
|
+
|
|
60
|
+
a = task.input_data['a'] || 0
|
|
61
|
+
b = task.input_data['b'] || 0
|
|
62
|
+
operation = task.input_data['operation'] || 'add'
|
|
63
|
+
|
|
64
|
+
result = case operation
|
|
65
|
+
when 'add' then a + b
|
|
66
|
+
when 'subtract' then a - b
|
|
67
|
+
when 'multiply' then a * b
|
|
68
|
+
when 'divide' then b.zero? ? 'Error: Division by zero' : a / b
|
|
69
|
+
else 'Unknown operation'
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
puts "[MathWorker] Result: #{a} #{operation} #{b} = #{result}"
|
|
73
|
+
|
|
74
|
+
# Return hash - will be converted to TaskResult automatically
|
|
75
|
+
{ result: result, operation: operation }
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Example 3: Worker that can fail
|
|
79
|
+
failing_worker = Conductor::Worker.define('failing_task', poll_interval: 1) do |task|
|
|
80
|
+
puts "\n[FailingWorker] Executing task: #{task.task_id}"
|
|
81
|
+
|
|
82
|
+
should_fail = task.input_data['should_fail']
|
|
83
|
+
|
|
84
|
+
if should_fail
|
|
85
|
+
# Return a failed result
|
|
86
|
+
result = Conductor::Http::Models::TaskResult.failed('Task failed as requested')
|
|
87
|
+
result.log('This task was designed to fail')
|
|
88
|
+
result
|
|
89
|
+
else
|
|
90
|
+
# Return success
|
|
91
|
+
{ status: 'success', message: 'Task completed successfully' }
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Create and configure task runner
|
|
96
|
+
puts "\n[TaskRunner] Creating task runner..."
|
|
97
|
+
runner = Conductor::Worker::TaskRunner.new(config)
|
|
98
|
+
|
|
99
|
+
# Register workers
|
|
100
|
+
runner.register_worker(SimpleWorker.new)
|
|
101
|
+
runner.register_worker(math_worker)
|
|
102
|
+
runner.register_worker(failing_worker)
|
|
103
|
+
|
|
104
|
+
puts '[TaskRunner] Registered 3 workers:'
|
|
105
|
+
puts ' - simple_task (class-based)'
|
|
106
|
+
puts ' - math_task (block-based)'
|
|
107
|
+
puts ' - failing_task (block-based)'
|
|
108
|
+
|
|
109
|
+
# Start the runner
|
|
110
|
+
puts "\n[TaskRunner] Starting worker threads..."
|
|
111
|
+
runner.start(threads: 1)
|
|
112
|
+
|
|
113
|
+
puts "\nWorkers are now polling for tasks. Press Ctrl+C to stop."
|
|
114
|
+
puts '=' * 60
|
|
115
|
+
|
|
116
|
+
# Keep the main thread alive and handle Ctrl+C gracefully
|
|
117
|
+
trap('INT') do
|
|
118
|
+
puts "\n\nReceived interrupt signal..."
|
|
119
|
+
runner.stop
|
|
120
|
+
puts 'Workers stopped. Exiting.'
|
|
121
|
+
exit 0
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Keep running
|
|
125
|
+
sleep while runner.running?
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Simple example demonstrating basic workflow operations with Conductor Ruby SDK
|
|
5
|
+
#
|
|
6
|
+
# Prerequisites:
|
|
7
|
+
# 1. Conductor server running on localhost:7001 (OSS version)
|
|
8
|
+
# 2. Bundle install completed
|
|
9
|
+
#
|
|
10
|
+
# Usage:
|
|
11
|
+
# bundle exec ruby examples/simple_workflow.rb
|
|
12
|
+
|
|
13
|
+
require_relative '../lib/conductor'
|
|
14
|
+
|
|
15
|
+
puts '=' * 60
|
|
16
|
+
puts 'Conductor Ruby SDK - Simple Workflow Example'
|
|
17
|
+
puts '=' * 60
|
|
18
|
+
|
|
19
|
+
# Configure Conductor client
|
|
20
|
+
config = Conductor::Configuration.new(
|
|
21
|
+
server_api_url: 'http://localhost:7001/api'
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
puts "\nConnecting to Conductor server at: #{config.server_url}"
|
|
25
|
+
|
|
26
|
+
# Create workflow client
|
|
27
|
+
workflow_client = Conductor::Client::WorkflowClient.new(config)
|
|
28
|
+
|
|
29
|
+
puts 'Workflow client created successfully!'
|
|
30
|
+
|
|
31
|
+
# Example 1: Start a workflow
|
|
32
|
+
puts "\n[Example 1] Starting a workflow..."
|
|
33
|
+
begin
|
|
34
|
+
request = Conductor::Http::Models::StartWorkflowRequest.new(
|
|
35
|
+
name: 'my_workflow',
|
|
36
|
+
version: 1,
|
|
37
|
+
input: {
|
|
38
|
+
'param1' => 'value1',
|
|
39
|
+
'param2' => 42
|
|
40
|
+
}
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
workflow_id = workflow_client.start_workflow(request)
|
|
44
|
+
puts " ✓ Workflow started with ID: #{workflow_id}"
|
|
45
|
+
|
|
46
|
+
# Get workflow status
|
|
47
|
+
puts "\n[Example 2] Getting workflow status..."
|
|
48
|
+
workflow = workflow_client.get_workflow(workflow_id)
|
|
49
|
+
puts " ✓ Workflow status: #{begin
|
|
50
|
+
workflow['status']
|
|
51
|
+
rescue StandardError
|
|
52
|
+
'N/A'
|
|
53
|
+
end}"
|
|
54
|
+
rescue Conductor::ApiError => e
|
|
55
|
+
puts " ✗ Error: #{e.message}"
|
|
56
|
+
puts " Note: Make sure the workflow 'my_workflow' is registered in Conductor"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Example 3: Using convenience method
|
|
60
|
+
puts "\n[Example 3] Starting workflow with convenience method..."
|
|
61
|
+
begin
|
|
62
|
+
workflow_id = workflow_client.start(
|
|
63
|
+
'my_workflow',
|
|
64
|
+
input: { 'key' => 'value' },
|
|
65
|
+
correlation_id: 'example-123'
|
|
66
|
+
)
|
|
67
|
+
puts " ✓ Workflow started with ID: #{workflow_id}"
|
|
68
|
+
rescue Conductor::ApiError => e
|
|
69
|
+
puts " ✗ Error: #{e.message}"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Example 4: Task result creation
|
|
73
|
+
puts "\n[Example 4] Creating task results..."
|
|
74
|
+
result = Conductor::Http::Models::TaskResult.complete
|
|
75
|
+
result.add_output_data('result', 'success')
|
|
76
|
+
result.log('Task completed successfully')
|
|
77
|
+
puts " ✓ Task result created with status: #{result.status}"
|
|
78
|
+
puts " ✓ Output data: #{result.output_data.inspect}"
|
|
79
|
+
puts " ✓ Logs: #{result.logs.inspect}"
|
|
80
|
+
|
|
81
|
+
# Example 5: WorkflowStatus constants
|
|
82
|
+
puts "\n[Example 5] Workflow status constants..."
|
|
83
|
+
puts " All statuses: #{Conductor::Http::Models::WorkflowStatusConstants::ALL.join(', ')}"
|
|
84
|
+
puts " Running? #{Conductor::Http::Models::WorkflowStatusConstants.running?('RUNNING')}"
|
|
85
|
+
puts " Terminal? #{Conductor::Http::Models::WorkflowStatusConstants.terminal?('COMPLETED')}"
|
|
86
|
+
|
|
87
|
+
puts "\n#{'=' * 60}"
|
|
88
|
+
puts 'Example completed!'
|
|
89
|
+
puts '=' * 60
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# Task Context Example
|
|
5
|
+
#
|
|
6
|
+
# Demonstrates how to use TaskContext to access task information and modify
|
|
7
|
+
# task results during execution.
|
|
8
|
+
#
|
|
9
|
+
# The TaskContext provides:
|
|
10
|
+
# - Access to task metadata (task_id, workflow_id, retry_count, etc.)
|
|
11
|
+
# - Ability to add logs visible in Conductor UI
|
|
12
|
+
# - Ability to set callback delays for polling/retry patterns
|
|
13
|
+
# - Access to input parameters
|
|
14
|
+
#
|
|
15
|
+
# Usage:
|
|
16
|
+
# ruby examples/task_context_example.rb
|
|
17
|
+
#
|
|
18
|
+
# Environment variables:
|
|
19
|
+
# CONDUCTOR_SERVER_URL - Conductor server URL (default: http://localhost:8080/api)
|
|
20
|
+
# CONDUCTOR_AUTH_KEY - Authentication key (optional)
|
|
21
|
+
# CONDUCTOR_AUTH_SECRET - Authentication secret (optional)
|
|
22
|
+
#
|
|
23
|
+
|
|
24
|
+
require 'bundler/setup'
|
|
25
|
+
require 'conductor'
|
|
26
|
+
|
|
27
|
+
#
|
|
28
|
+
# Example 1: Basic TaskContext usage - accessing task info
|
|
29
|
+
#
|
|
30
|
+
Conductor::Worker.define('task_info_example', poll_interval: 100, thread_count: 5) do |task|
|
|
31
|
+
# Get the current task context
|
|
32
|
+
ctx = Conductor::Worker::TaskContext.current
|
|
33
|
+
|
|
34
|
+
# Access task information
|
|
35
|
+
task_id = ctx.task_id
|
|
36
|
+
workflow_id = ctx.workflow_instance_id
|
|
37
|
+
retry_count = ctx.retry_count
|
|
38
|
+
poll_count = task.poll_count || 0
|
|
39
|
+
|
|
40
|
+
puts "Task ID: #{task_id}"
|
|
41
|
+
puts "Workflow ID: #{workflow_id}"
|
|
42
|
+
puts "Retry Count: #{retry_count}"
|
|
43
|
+
puts "Poll Count: #{poll_count}"
|
|
44
|
+
|
|
45
|
+
{
|
|
46
|
+
task_id: task_id,
|
|
47
|
+
workflow_id: workflow_id,
|
|
48
|
+
retry_count: retry_count,
|
|
49
|
+
result: 'processed'
|
|
50
|
+
}
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
#
|
|
54
|
+
# Example 2: Adding logs via TaskContext
|
|
55
|
+
#
|
|
56
|
+
Conductor::Worker.define('logging_example', poll_interval: 100, thread_count: 5) do |task|
|
|
57
|
+
order_id = task.input_data['order_id'] || 'unknown'
|
|
58
|
+
items = task.input_data['items'] || []
|
|
59
|
+
|
|
60
|
+
ctx = Conductor::Worker::TaskContext.current
|
|
61
|
+
|
|
62
|
+
# Add logs as processing progresses
|
|
63
|
+
ctx.add_log("Starting to process order #{order_id}")
|
|
64
|
+
ctx.add_log("Order has #{items.size} items")
|
|
65
|
+
|
|
66
|
+
items.each_with_index do |item, i|
|
|
67
|
+
sleep(0.1) # Simulate processing
|
|
68
|
+
ctx.add_log("Processed item #{i + 1}/#{items.size}: #{item}")
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
ctx.add_log('Order processing completed')
|
|
72
|
+
|
|
73
|
+
{
|
|
74
|
+
order_id: order_id,
|
|
75
|
+
items_processed: items.size,
|
|
76
|
+
status: 'completed'
|
|
77
|
+
}
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
#
|
|
81
|
+
# Example 3: Callback pattern - polling external service
|
|
82
|
+
#
|
|
83
|
+
Conductor::Worker.define('polling_example', poll_interval: 100, thread_count: 10) do |task|
|
|
84
|
+
job_id = task.input_data['job_id'] || 'unknown'
|
|
85
|
+
|
|
86
|
+
ctx = Conductor::Worker::TaskContext.current
|
|
87
|
+
|
|
88
|
+
ctx.add_log("Checking status of job #{job_id}")
|
|
89
|
+
|
|
90
|
+
# Simulate checking external service
|
|
91
|
+
is_complete = rand > 0.7 # 30% chance of completion
|
|
92
|
+
|
|
93
|
+
if is_complete
|
|
94
|
+
ctx.add_log("Job #{job_id} is complete!")
|
|
95
|
+
{
|
|
96
|
+
job_id: job_id,
|
|
97
|
+
status: 'completed',
|
|
98
|
+
result: 'Job finished successfully'
|
|
99
|
+
}
|
|
100
|
+
else
|
|
101
|
+
# Job still running - poll again in 30 seconds
|
|
102
|
+
ctx.add_log("Job #{job_id} still running, will check again in 30s")
|
|
103
|
+
ctx.set_callback_after(30)
|
|
104
|
+
|
|
105
|
+
Conductor::Worker::TaskInProgress.new(
|
|
106
|
+
callback_after_seconds: 30,
|
|
107
|
+
output: {
|
|
108
|
+
job_id: job_id,
|
|
109
|
+
status: 'in_progress',
|
|
110
|
+
message: 'Job still running'
|
|
111
|
+
}
|
|
112
|
+
)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
#
|
|
117
|
+
# Example 4: Retry logic with context awareness
|
|
118
|
+
#
|
|
119
|
+
Conductor::Worker.define('retry_aware_example', poll_interval: 100, thread_count: 5) do |task|
|
|
120
|
+
operation = task.input_data['operation'] || 'default'
|
|
121
|
+
|
|
122
|
+
ctx = Conductor::Worker::TaskContext.current
|
|
123
|
+
|
|
124
|
+
retry_count = ctx.retry_count
|
|
125
|
+
|
|
126
|
+
if retry_count.positive?
|
|
127
|
+
ctx.add_log("This is retry attempt ##{retry_count}")
|
|
128
|
+
# Could implement exponential backoff, different logic, etc.
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
ctx.add_log("Executing operation: #{operation}")
|
|
132
|
+
|
|
133
|
+
# Simulate operation
|
|
134
|
+
success = rand > 0.3
|
|
135
|
+
|
|
136
|
+
if success
|
|
137
|
+
ctx.add_log('Operation succeeded')
|
|
138
|
+
{ status: 'success', operation: operation }
|
|
139
|
+
else
|
|
140
|
+
ctx.add_log('Operation failed, will retry')
|
|
141
|
+
raise StandardError, 'Operation failed'
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
#
|
|
146
|
+
# Example 5: Accessing input parameters via context
|
|
147
|
+
#
|
|
148
|
+
Conductor::Worker.define('input_access_example', poll_interval: 100, thread_count: 5) do |_task|
|
|
149
|
+
ctx = Conductor::Worker::TaskContext.current
|
|
150
|
+
|
|
151
|
+
# Get all input parameters
|
|
152
|
+
input_data = ctx.input
|
|
153
|
+
|
|
154
|
+
ctx.add_log("Received input parameters: #{input_data.keys}")
|
|
155
|
+
|
|
156
|
+
# Process based on input
|
|
157
|
+
input_data.each do |key, value|
|
|
158
|
+
ctx.add_log(" #{key} = #{value}")
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
{
|
|
162
|
+
processed_keys: input_data.keys,
|
|
163
|
+
input_count: input_data.size
|
|
164
|
+
}
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
#
|
|
168
|
+
# Example 6: Long-running task with progress tracking
|
|
169
|
+
#
|
|
170
|
+
Conductor::Worker.define('progress_tracking_example', poll_interval: 100, thread_count: 5) do |task|
|
|
171
|
+
total_steps = task.input_data['total_steps'] || 5
|
|
172
|
+
|
|
173
|
+
ctx = Conductor::Worker::TaskContext.current
|
|
174
|
+
poll_count = task.poll_count || 0
|
|
175
|
+
|
|
176
|
+
ctx.add_log("Progress: step #{poll_count + 1}/#{total_steps}")
|
|
177
|
+
|
|
178
|
+
if poll_count < total_steps - 1
|
|
179
|
+
# Still processing
|
|
180
|
+
progress = ((poll_count + 1).to_f / total_steps * 100).round
|
|
181
|
+
|
|
182
|
+
Conductor::Worker::TaskInProgress.new(
|
|
183
|
+
callback_after_seconds: 1,
|
|
184
|
+
output: {
|
|
185
|
+
current_step: poll_count + 1,
|
|
186
|
+
total_steps: total_steps,
|
|
187
|
+
progress_percent: progress,
|
|
188
|
+
status: 'in_progress'
|
|
189
|
+
}
|
|
190
|
+
)
|
|
191
|
+
else
|
|
192
|
+
# Complete
|
|
193
|
+
ctx.add_log('All steps completed!')
|
|
194
|
+
{
|
|
195
|
+
current_step: total_steps,
|
|
196
|
+
total_steps: total_steps,
|
|
197
|
+
progress_percent: 100,
|
|
198
|
+
status: 'completed'
|
|
199
|
+
}
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def main
|
|
204
|
+
config = Conductor::Configuration.new(
|
|
205
|
+
server_api_url: ENV.fetch('CONDUCTOR_SERVER_URL', 'http://localhost:8080/api'),
|
|
206
|
+
auth_key: ENV.fetch('CONDUCTOR_AUTH_KEY', nil),
|
|
207
|
+
auth_secret: ENV.fetch('CONDUCTOR_AUTH_SECRET', nil)
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
puts '=' * 60
|
|
211
|
+
puts 'Conductor TaskContext Examples'
|
|
212
|
+
puts '=' * 60
|
|
213
|
+
puts
|
|
214
|
+
puts 'Workers demonstrating TaskContext usage:'
|
|
215
|
+
puts ' - task_info_example - Access task metadata'
|
|
216
|
+
puts ' - logging_example - Add logs to task'
|
|
217
|
+
puts ' - polling_example - Use callback_after for polling'
|
|
218
|
+
puts ' - retry_aware_example - Handle retries intelligently'
|
|
219
|
+
puts ' - input_access_example - Access task input via context'
|
|
220
|
+
puts ' - progress_tracking_example - Track progress across polls'
|
|
221
|
+
puts
|
|
222
|
+
puts 'Key TaskContext Features:'
|
|
223
|
+
puts ' - Access task metadata (ID, workflow ID, retry count)'
|
|
224
|
+
puts ' - Add logs visible in Conductor UI'
|
|
225
|
+
puts ' - Set callback delays for polling patterns'
|
|
226
|
+
puts ' - Thread-safe via Thread.current'
|
|
227
|
+
puts '=' * 60
|
|
228
|
+
puts
|
|
229
|
+
puts 'Starting workers... Press Ctrl+C to stop'
|
|
230
|
+
puts
|
|
231
|
+
|
|
232
|
+
begin
|
|
233
|
+
handler = Conductor::Worker::TaskHandler.new(
|
|
234
|
+
configuration: config,
|
|
235
|
+
scan_for_annotated_workers: true
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
shutdown = false
|
|
239
|
+
Signal.trap('INT') do
|
|
240
|
+
puts "\nShutting down gracefully..."
|
|
241
|
+
shutdown = true
|
|
242
|
+
handler.stop
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
handler.start
|
|
246
|
+
handler.join unless shutdown
|
|
247
|
+
rescue Interrupt
|
|
248
|
+
puts 'Interrupted!'
|
|
249
|
+
rescue StandardError => e
|
|
250
|
+
puts "Error: #{e.message}"
|
|
251
|
+
puts e.backtrace.first(5).join("\n")
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
puts "\nWorkers stopped. Goodbye!"
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
main if __FILE__ == $PROGRAM_NAME
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# Example demonstrating TaskRunnerEventsListener for pre/post processing of worker tasks.
|
|
5
|
+
#
|
|
6
|
+
# This example shows how to implement a custom event listener to:
|
|
7
|
+
# - Log task execution events
|
|
8
|
+
# - Add custom headers or context before task execution
|
|
9
|
+
# - Process task results after execution
|
|
10
|
+
# - Track task timing and errors
|
|
11
|
+
# - Implement retry logic or custom error handling
|
|
12
|
+
#
|
|
13
|
+
# The listener pattern is useful for:
|
|
14
|
+
# - Request/response logging
|
|
15
|
+
# - Distributed tracing integration
|
|
16
|
+
# - Custom metrics collection
|
|
17
|
+
# - Authentication/authorization
|
|
18
|
+
# - Data enrichment
|
|
19
|
+
# - Error recovery
|
|
20
|
+
#
|
|
21
|
+
# Usage:
|
|
22
|
+
# ruby task_listener_example.rb
|
|
23
|
+
#
|
|
24
|
+
# Environment variables:
|
|
25
|
+
# CONDUCTOR_SERVER_URL - Conductor server URL (default: http://localhost:8080/api)
|
|
26
|
+
# CONDUCTOR_AUTH_KEY - Authentication key (optional)
|
|
27
|
+
# CONDUCTOR_AUTH_SECRET - Authentication secret (optional)
|
|
28
|
+
#
|
|
29
|
+
|
|
30
|
+
require 'bundler/setup'
|
|
31
|
+
require 'conductor'
|
|
32
|
+
require_relative 'event_listener_examples'
|
|
33
|
+
|
|
34
|
+
# Configure logging
|
|
35
|
+
logger = Logger.new($stdout)
|
|
36
|
+
logger.level = Logger::INFO
|
|
37
|
+
logger.formatter = proc do |severity, datetime, _progname, msg|
|
|
38
|
+
"[#{datetime.strftime('%Y-%m-%d %H:%M:%S')}] #{severity} -- #{msg}\n"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
#
|
|
42
|
+
# Example worker tasks
|
|
43
|
+
#
|
|
44
|
+
|
|
45
|
+
# Simple calculator worker
|
|
46
|
+
Conductor::Worker.define('calculate', poll_interval: 100, thread_count: 5) do |task|
|
|
47
|
+
n = task.input_data['n'] || 10
|
|
48
|
+
|
|
49
|
+
# Fibonacci calculation (demonstrates CPU-bound work)
|
|
50
|
+
fib = ->(x) { x <= 1 ? x : fib.call(x - 1) + fib.call(x - 2) }
|
|
51
|
+
result = fib.call(n)
|
|
52
|
+
|
|
53
|
+
{ fibonacci: result, input: n }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Long-running task with progress tracking
|
|
57
|
+
Conductor::Worker.define('long_running_task', poll_interval: 100, thread_count: 3) do |task|
|
|
58
|
+
job_id = task.input_data['job_id'] || 'unknown'
|
|
59
|
+
|
|
60
|
+
# Access task context for poll count
|
|
61
|
+
ctx = Conductor::Worker::TaskContext.current
|
|
62
|
+
poll_count = task.poll_count || 0
|
|
63
|
+
|
|
64
|
+
ctx.add_log("Processing job #{job_id}, poll #{poll_count}/5")
|
|
65
|
+
|
|
66
|
+
if poll_count < 5
|
|
67
|
+
# Still processing - return TaskInProgress
|
|
68
|
+
Conductor::Worker::TaskInProgress.new(
|
|
69
|
+
callback_after_seconds: 1,
|
|
70
|
+
output: {
|
|
71
|
+
job_id: job_id,
|
|
72
|
+
status: 'processing',
|
|
73
|
+
poll_count: poll_count,
|
|
74
|
+
progress: poll_count * 20,
|
|
75
|
+
message: "Working on job #{job_id}, poll #{poll_count}/5"
|
|
76
|
+
}
|
|
77
|
+
)
|
|
78
|
+
else
|
|
79
|
+
# Complete after 5 polls
|
|
80
|
+
ctx.add_log("Job #{job_id} completed")
|
|
81
|
+
{
|
|
82
|
+
job_id: job_id,
|
|
83
|
+
status: 'completed',
|
|
84
|
+
result: 'success',
|
|
85
|
+
total_time_seconds: 5,
|
|
86
|
+
total_polls: poll_count
|
|
87
|
+
}
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Worker that simulates failures for testing error handling
|
|
92
|
+
Conductor::Worker.define('flaky_worker', poll_interval: 100, thread_count: 2) do |task|
|
|
93
|
+
fail_rate = task.input_data['fail_rate'] || 0.3
|
|
94
|
+
|
|
95
|
+
raise StandardError, 'Random failure for testing' if rand < fail_rate
|
|
96
|
+
|
|
97
|
+
{ status: 'success', fail_rate: fail_rate }
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def main
|
|
101
|
+
# Configure Conductor connection
|
|
102
|
+
config = Conductor::Configuration.new(
|
|
103
|
+
server_api_url: ENV['CONDUCTOR_SERVER_URL'] || 'http://localhost:8080/api',
|
|
104
|
+
auth_key: ENV.fetch('CONDUCTOR_AUTH_KEY', nil),
|
|
105
|
+
auth_secret: ENV.fetch('CONDUCTOR_AUTH_SECRET', nil)
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# Create event listeners
|
|
109
|
+
logger_listener = TaskExecutionLogger.new
|
|
110
|
+
timing_tracker = TaskTimingTracker.new
|
|
111
|
+
tracing_listener = DistributedTracingListener.new
|
|
112
|
+
error_tracker = ErrorTrackingListener.new(
|
|
113
|
+
alert_threshold: 3,
|
|
114
|
+
alert_callback: ->(alert) { puts "[ALERT CALLBACK] #{alert.inspect}" }
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
puts '=' * 80
|
|
118
|
+
puts 'TaskRunnerEventsListener Example'
|
|
119
|
+
puts '=' * 80
|
|
120
|
+
puts
|
|
121
|
+
puts 'This example demonstrates event listeners for task pre/post processing:'
|
|
122
|
+
puts ' 1. TaskExecutionLogger - Logs all task lifecycle events'
|
|
123
|
+
puts ' 2. TaskTimingTracker - Tracks and reports execution statistics'
|
|
124
|
+
puts ' 3. DistributedTracingListener - Simulates distributed tracing'
|
|
125
|
+
puts ' 4. ErrorTrackingListener - Aggregates errors and alerts'
|
|
126
|
+
puts
|
|
127
|
+
puts 'Workers available:'
|
|
128
|
+
puts ' - calculate: Fibonacci calculator'
|
|
129
|
+
puts ' - long_running_task: Multi-poll task with progress tracking'
|
|
130
|
+
puts ' - flaky_worker: Random failure simulation'
|
|
131
|
+
puts
|
|
132
|
+
puts 'Press Ctrl+C to stop...'
|
|
133
|
+
puts '=' * 80
|
|
134
|
+
puts
|
|
135
|
+
|
|
136
|
+
begin
|
|
137
|
+
# Create task handler with multiple listeners
|
|
138
|
+
handler = Conductor::Worker::TaskHandler.new(
|
|
139
|
+
configuration: config,
|
|
140
|
+
scan_for_annotated_workers: true,
|
|
141
|
+
event_listeners: [
|
|
142
|
+
logger_listener,
|
|
143
|
+
timing_tracker,
|
|
144
|
+
tracing_listener,
|
|
145
|
+
error_tracker
|
|
146
|
+
]
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# Handle graceful shutdown
|
|
150
|
+
shutdown = false
|
|
151
|
+
Signal.trap('INT') do
|
|
152
|
+
puts "\nShutting down gracefully..."
|
|
153
|
+
shutdown = true
|
|
154
|
+
handler.stop
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
handler.start
|
|
158
|
+
|
|
159
|
+
# Print statistics periodically
|
|
160
|
+
Thread.new do
|
|
161
|
+
loop do
|
|
162
|
+
sleep 30
|
|
163
|
+
break if shutdown
|
|
164
|
+
|
|
165
|
+
puts "\n--- Statistics ---"
|
|
166
|
+
timing_tracker.all_stats.each do |task_type, stats|
|
|
167
|
+
puts "#{task_type}: #{stats.inspect}"
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
error_summary = error_tracker.error_summary
|
|
171
|
+
puts "Errors: #{error_summary.inspect}" unless error_summary.empty?
|
|
172
|
+
puts '------------------\n'
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
handler.join unless shutdown
|
|
177
|
+
rescue Interrupt
|
|
178
|
+
puts 'Interrupted!'
|
|
179
|
+
rescue StandardError => e
|
|
180
|
+
puts "Error: #{e.message}"
|
|
181
|
+
puts e.backtrace.first(5).join("\n")
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
puts "\nFinal Statistics:"
|
|
185
|
+
timing_tracker.all_stats.each do |task_type, stats|
|
|
186
|
+
puts " #{task_type}: #{stats.inspect}"
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
puts "\nWorkers stopped. Goodbye!"
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
main if __FILE__ == $PROGRAM_NAME
|