Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ Prerequisites:
* [env_config](env_config) - Load client configuration from TOML files with programmatic overrides.
* [message_passing_simple](message_passing_simple) - Simple workflow that accepts signals, queries, and updates.
* [patching](patching) - Demonstrates how to safely alter a workflow.
* [polling/frequent](polling/frequent) - Implement a frequent polling mechanism inside an Activity.
* [polling/infrequent](polling/infrequent) - Implement an infrequent polling mechanism using Temporal's automatic Activity Retry feature.
* [polling/periodic_sequence](polling/periodic_sequence) - Implement a periodic polling mechanism using a Child Workflow.
* [rails_app](rails_app) - Basic Rails API application using Temporal workflows and activities.
* [saga](saga) - Using undo/compensation using a very simplistic Saga pattern.
* [sorbet_generic](sorbet_generic) - Proof of concept of how to do _advanced_ Sorbet typing with the SDK.
Expand Down
35 changes: 35 additions & 0 deletions polling/frequent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Frequent Polling Activity

This sample demonstrates how to implement a frequent polling mechanism
(1 second or faster) inside an Activity.
The implementation is a loop that polls a service and then sleeps for the poll
interval (1 second in the sample).

## How to Run

1. **Start the Worker:**

Open a terminal and run the following command to start the worker process.
The worker will listen for tasks on the `frequent-polling-sample` task queue.

```bash
bundle exec ruby worker.rb
```

You will see the worker log messages indicating it is calling the service.
It will try several times, with a short delay between each attempt.

2. **Start the Workflow:**

In a separate terminal, run this command to start the workflow.
This script will start the workflow and wait for its completion,
printing the final result.

```bash
bundle exec ruby starter.rb
```

After a few seconds, the service will succeed.
You will see the final result printed in the starter's terminal,
and the worker will log the successful completion.

23 changes: 23 additions & 0 deletions polling/frequent/compose_greeting_activity.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# frozen_string_literal: true

require 'temporalio/activity'
require_relative '../test_service'

module Polling
module Frequent
class ComposeGreetingActivity < Temporalio::Activity::Definition
def execute(input)
loop do
activity_info = Temporalio::Activity::Context.current.info
begin
return TestService.get_service_result(input, activity_info)
rescue TestService::TestServiceError
Temporalio::Activity::Context.current.logger.info('Test service was down')
end
Temporalio::Activity::Context.current.heartbeat
sleep 1
end
end
end
end
end
19 changes: 19 additions & 0 deletions polling/frequent/greeting_workflow.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# frozen_string_literal: true

require 'temporalio/workflow'
require_relative 'compose_greeting_activity'

module Polling
module Frequent
class GreetingWorkflow < Temporalio::Workflow::Definition
def execute(name)
Temporalio::Workflow.execute_activity(
ComposeGreetingActivity,
{ greeting: 'Hello', name: name },
start_to_close_timeout: 60,
heartbeat_timeout: 2
)
end
end
end
end
17 changes: 17 additions & 0 deletions polling/frequent/starter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# frozen_string_literal: true

require 'temporalio/client'
require_relative 'greeting_workflow'

# Create a client
client = Temporalio::Client.connect('localhost:7233', 'default')

# Run workflow
puts 'Executing workflow'
result = client.execute_workflow(
Polling::Frequent::GreetingWorkflow,
'World',
id: "frequent-polling-sample-workflow-id-#{Time.now.to_i}",
task_queue: 'frequent-polling-sample'
)
puts "Workflow result: #{result}"
19 changes: 19 additions & 0 deletions polling/frequent/worker.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# frozen_string_literal: true

require_relative 'greeting_workflow'
require_relative 'compose_greeting_activity'
require 'temporalio/client'
require 'temporalio/worker'

# Create a client
client = Temporalio::Client.connect('localhost:7233', 'default')

worker = Temporalio::Worker.new(
client:,
task_queue: 'frequent-polling-sample',
workflows: [Polling::Frequent::GreetingWorkflow],
activities: [Polling::Frequent::ComposeGreetingActivity]
)

puts 'Starting worker (ctrl+c to exit)'
worker.run(shutdown_signals: ['SIGINT'])
2 changes: 1 addition & 1 deletion polling/infrequent/compose_greeting_activity.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

require 'temporalio/activity'
require_relative 'test_service'
require_relative '../test_service'

module Polling
module Infrequent
Expand Down
26 changes: 0 additions & 26 deletions polling/infrequent/test_service.rb

This file was deleted.

36 changes: 36 additions & 0 deletions polling/periodic_sequence/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Periodic Polling of a Sequence of Activities

This sample demonstrates how to use a Child Workflow for periodic Activity polling.

This is a rare scenario where polling requires execution of a Sequence of Activities, or Activity arguments need to change between polling retries. For this case we use a Child Workflow to call polling activities a set number of times in a loop and then periodically call Continue-As-New.

## How to Run

To run, first see [README.md](../README.md) for prerequisites.

1. **Start the Worker:**

Open a terminal and run the following command to start the worker process.
The worker will listen for tasks on the `frequent-polling-sample` task queue.

```bash
bundle exec ruby worker.rb
```

You will see the worker log messages indicating it is calling the service.
It will try several times, with a short delay between each attempt.

2. **Start the Workflow:**

In a separate terminal, run this command to start the workflow.
This script will start the workflow and wait for its completion,
printing the final result.

```bash
bundle exec ruby starter.rb
```

After a few seconds, the service will succeed.
You will see the final result printed in the starter's terminal,
and the worker will log the successful completion.

30 changes: 30 additions & 0 deletions polling/periodic_sequence/child_workflow.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# frozen_string_literal: true

require 'temporalio/workflow'
require_relative 'compose_greeting_activity'
require_relative '../test_service'

module Polling
module PeriodicSequence
class ChildWorkflow < Temporalio::Workflow::Definition
def execute(name)
4.times do
begin
return Temporalio::Workflow.execute_activity(
ComposeGreetingActivity,
{ greeting: 'Hello', name: name },
retry_policy: Temporalio::RetryPolicy.new(
max_attempts: 1
),
start_to_close_timeout: 4
)
rescue Temporalio::Error::ActivityError
Temporalio::Workflow.logger.info('Activity failed')
end
Temporalio::Workflow.sleep(1)
end
raise Temporalio::Workflow::ContinueAsNewError, name
end
end
end
end
15 changes: 15 additions & 0 deletions polling/periodic_sequence/compose_greeting_activity.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

require 'temporalio/activity'
require_relative '../test_service'

module Polling
module PeriodicSequence
class ComposeGreetingActivity < Temporalio::Activity::Definition
def execute(input)
activity_info = Temporalio::Activity::Context.current.info
TestService.get_service_result(input, activity_info)
end
end
end
end
14 changes: 14 additions & 0 deletions polling/periodic_sequence/greeting_workflow.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# frozen_string_literal: true

require 'temporalio/workflow'
require_relative 'child_workflow'

module Polling
module PeriodicSequence
class GreetingWorkflow < Temporalio::Workflow::Definition
def execute(name)
Temporalio::Workflow.execute_child_workflow(ChildWorkflow, name)
end
end
end
end
17 changes: 17 additions & 0 deletions polling/periodic_sequence/starter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# frozen_string_literal: true

require 'temporalio/client'
require_relative 'greeting_workflow'

# Create a client
client = Temporalio::Client.connect('localhost:7233', 'default')

# Run workflow
puts 'Executing workflow'
result = client.execute_workflow(
Polling::PeriodicSequence::GreetingWorkflow,
'World',
id: "periodic-sequence-polling-sample-workflow-id-#{Time.now.to_i}",
task_queue: 'periodic-sequence-polling-sample'
)
puts "Workflow result: #{result}"
20 changes: 20 additions & 0 deletions polling/periodic_sequence/worker.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# frozen_string_literal: true

require_relative 'greeting_workflow'
require_relative 'child_workflow'
require_relative 'compose_greeting_activity'
require 'temporalio/client'
require 'temporalio/worker'

# Create a client
client = Temporalio::Client.connect('localhost:7233', 'default')

worker = Temporalio::Worker.new(
client:,
task_queue: 'periodic-sequence-polling-sample',
workflows: [Polling::PeriodicSequence::GreetingWorkflow, Polling::PeriodicSequence::ChildWorkflow],
activities: [Polling::PeriodicSequence::ComposeGreetingActivity]
)

puts 'Starting worker (ctrl+c to exit)'
worker.run(shutdown_signals: ['SIGINT'])
24 changes: 24 additions & 0 deletions polling/test_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# frozen_string_literal: true

module Polling
# A mock external service with simulated errors.
module TestService
class TestServiceError < StandardError; end

@attempts = Hash.new(0)
ERROR_ATTEMPTS = 5

def get_service_result(input, activity_info)
workflow_id = activity_info.workflow_id
@attempts[workflow_id] ||= 0
@attempts[workflow_id] += 1

puts "Attempt #{@attempts[workflow_id]} of #{ERROR_ATTEMPTS} to invoke service"

raise TestServiceError, 'service is down' unless @attempts[workflow_id] == ERROR_ATTEMPTS

"#{input['greeting']}, #{input['name']}!"
end
module_function :get_service_result
end
end
41 changes: 41 additions & 0 deletions test/polling/frequent/greeting_workflow_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# frozen_string_literal: true

require 'test'
require 'securerandom'
require 'temporalio/testing'
require 'temporalio/worker'
require 'polling/frequent/greeting_workflow'
require 'polling/frequent/compose_greeting_activity'
require 'polling/test_service'

module Polling
module Frequent
class GreetingWorkflowTest < Test
def test_workflow_completes_after_polling
task_queue = "tq-#{SecureRandom.uuid}"

Temporalio::Testing::WorkflowEnvironment.start_local do |env|
worker = Temporalio::Worker.new(
client: env.client,
task_queue: task_queue,
activities: [Polling::Frequent::ComposeGreetingActivity],
workflows: [Polling::Frequent::GreetingWorkflow]
)

handle = env.client.start_workflow(
Polling::Frequent::GreetingWorkflow,
'Temporal',
id: "wf-#{SecureRandom.uuid}",
task_queue: task_queue
)

worker.run do
# Wait for the workflow to complete and assert its result
result = handle.result
assert_equal('Hello, Temporal!', result)
end
end
end
end
end
end
2 changes: 1 addition & 1 deletion test/polling/infrequent/greeting_workflow_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
require 'temporalio/worker'
require 'polling/infrequent/greeting_workflow'
require 'polling/infrequent/compose_greeting_activity'
require 'polling/infrequent/test_service'
require 'polling/test_service'

module Polling
module Infrequent
Expand Down
Loading