Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge upstream #5

Merged
merged 18 commits into from
Dec 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
9d88b87
Pass config to the error handler instead of using the global config
DeRauk Oct 5, 2021
f0fee2d
Fix example tests
DeRauk Oct 5, 2021
9c58a49
Make the config property in the workflow context publicly readable
DeRauk Oct 20, 2021
3b5a4dc
Merge pull request #107 from DeRauk/error-handler-config-refactor
DeRauk Oct 20, 2021
fba30df
[Fix] Retryer GRPC error lookup (#109)
antstorm Oct 22, 2021
be33f00
[Feature] Add id and domain to workflow context's metadata (#110)
DeRauk Nov 1, 2021
2c16ba1
Explicit docker-compose project name (#114)
antstorm Nov 3, 2021
64dca95
Add YARD documentation for Temporal::Client (#113)
antstorm Nov 8, 2021
9a581ce
Add signal arguments to start_workflow (support for signal_with_start…
nagl-stripe Nov 9, 2021
8c36d90
Extend #wait_for to take multiple futures and a condition block (#111)
jeffschoner-stripe Nov 12, 2021
388d9ed
Differentiate TARGET_WILDCARD and WILDCARD, allow comparison with Eve…
jeffschoner-stripe Nov 15, 2021
be42f00
Turn off schedule_to_start activity timeout by default (#119)
drewhoskins-stripe Nov 17, 2021
824652b
Separate options from keyword args in #start_workflow (#117)
antstorm Nov 17, 2021
5648972
Surface additional workflow metadata on workflow context (#120)
jeffschoner-stripe Nov 21, 2021
7d63692
Add memos (#121)
jeffschoner-stripe Nov 28, 2021
654cdad
Add describe_namespace (#122)
drewhoskins-stripe Nov 30, 2021
27ac014
Improve header serialization and propagation (#124)
jeffschoner-stripe Dec 5, 2021
9e367d1
[Fix] Non-started activity cancellation (#125)
antstorm Dec 9, 2021
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Ruby worker for Temporal
# Ruby SDK for Temporal

[![Coverage Status](https://coveralls.io/repos/github/coinbase/temporal-ruby/badge.svg?branch=master)](https://coveralls.io/github/coinbase/temporal-ruby?branch=master)

<img src="./assets/temporal_logo.png" width="250" align="right" alt="Temporal" />

A pure Ruby library for defining and running Temporal workflows and activities.

To find more about Temporal please visit <https://temporal.io/>.
To find more about Temporal itself please visit <https://temporal.io/>.


## Getting Started
Expand Down
1 change: 1 addition & 0 deletions examples/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
COMPOSE_PROJECT_NAME=temporal-ruby-examples
3 changes: 3 additions & 0 deletions examples/bin/worker
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ worker.register_workflow(HelloWorldWorkflow)
worker.register_workflow(LocalHelloWorldWorkflow)
worker.register_workflow(LongWorkflow)
worker.register_workflow(LoopWorkflow)
worker.register_workflow(MetadataWorkflow)
worker.register_workflow(ParentWorkflow)
worker.register_workflow(ProcessFileWorkflow)
worker.register_workflow(QuickTimeoutWorkflow)
Expand All @@ -39,9 +40,11 @@ worker.register_workflow(ReleaseWorkflow)
worker.register_workflow(ResultWorkflow)
worker.register_workflow(SerialHelloWorldWorkflow)
worker.register_workflow(SideEffectWorkflow)
worker.register_workflow(SignalWithStartWorkflow)
worker.register_workflow(SimpleTimerWorkflow)
worker.register_workflow(TimeoutWorkflow)
worker.register_workflow(TripBookingWorkflow)
worker.register_workflow(WaitForWorkflow)

worker.register_activity(AsyncActivity)
worker.register_activity(EchoActivity)
Expand Down
39 changes: 39 additions & 0 deletions examples/spec/integration/activity_cancellation_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
require 'workflows/long_workflow'

describe 'Activity cancellation' do
let(:workflow_id) { SecureRandom.uuid }

it 'cancels a running activity' do
run_id = Temporal.start_workflow(LongWorkflow, options: { workflow_id: workflow_id })

# Signal workflow after starting, allowing it to schedule the first activity
sleep 0.5
Temporal.signal_workflow(LongWorkflow, :CANCEL, workflow_id, run_id)

result = Temporal.await_workflow_result(
LongWorkflow,
workflow_id: workflow_id,
run_id: run_id,
)

expect(result).to be_a(LongRunningActivity::Canceled)
expect(result.message).to eq('cancel activity request received')
end

it 'cancels a non-started activity' do
# Workflow is started with a signal which will cancel an activity before it has started
run_id = Temporal.start_workflow(LongWorkflow, options: {
workflow_id: workflow_id,
signal_name: :CANCEL
})

result = Temporal.await_workflow_result(
LongWorkflow,
workflow_id: workflow_id,
run_id: run_id,
)

expect(result).to be_a(Temporal::ActivityCanceled)
expect(result.message).to eq('ACTIVITY_ID_NOT_STARTED')
end
end
4 changes: 3 additions & 1 deletion examples/spec/integration/await_workflow_result_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@
run_id = Temporal.start_workflow(
LoopWorkflow,
2, # it continues as new if this arg is > 1
{ options: { workflow_id: workflow_id } },
options: {
workflow_id: workflow_id,
},
)

expect do
Expand Down
49 changes: 49 additions & 0 deletions examples/spec/integration/continue_as_new_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
require 'workflows/loop_workflow'

describe LoopWorkflow do
it 'workflow continues as new into a new run' do
workflow_id = SecureRandom.uuid
memo = {
'my-memo' => 'foo',
}
headers = {
'my-header' => 'bar',
}
run_id = Temporal.start_workflow(
LoopWorkflow,
2, # it continues as new if this arg is > 1
options: {
workflow_id: workflow_id,
memo: memo,
headers: headers,
},
)

# First run will throw because it continued as new
next_run_id = nil
expect do
Temporal.await_workflow_result(
LoopWorkflow,
workflow_id: workflow_id,
run_id: run_id,
)
end.to raise_error(Temporal::WorkflowRunContinuedAsNew) do |error|
next_run_id = error.new_run_id
end

expect(next_run_id).to_not eq(nil)

# Second run will not throw because it returns rather than continues as new.
final_result = Temporal.await_workflow_result(
LoopWorkflow,
workflow_id: workflow_id,
run_id: next_run_id,
)

expect(final_result[:count]).to eq(1)

# memo and headers should be copied to the next run automatically
expect(final_result[:memo]).to eq(memo)
expect(final_result[:headers]).to eq(headers)
end
end
16 changes: 16 additions & 0 deletions examples/spec/integration/describe_namespace_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
require 'temporal/errors'

describe 'Temporal.describe_namespace' do
it 'returns a value' do
description = 'Namespace for temporal-ruby integration test'
begin
Temporal.register_namespace('a_test_namespace', description)
rescue Temporal::NamespaceAlreadyExistsFailure
end
result = Temporal.describe_namespace('a_test_namespace')
expect(result).to be_an_instance_of(Temporal::Api::WorkflowService::V1::DescribeNamespaceResponse)
expect(result.namespace_info.name).to eq('a_test_namespace')
expect(result.namespace_info.state).to eq(:NAMESPACE_STATE_REGISTERED)
expect(result.namespace_info.description).to eq(description)
end
end
91 changes: 91 additions & 0 deletions examples/spec/integration/metadata_workflow_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
require 'workflows/metadata_workflow'

describe MetadataWorkflow do
subject { described_class }

it 'gets task queue from running workflow' do
workflow_id = 'task-queue-' + SecureRandom.uuid
run_id = Temporal.start_workflow(
subject,
options: { workflow_id: workflow_id }
)

actual_result = Temporal.await_workflow_result(
subject,
workflow_id: workflow_id,
run_id: run_id,
)

expect(actual_result.task_queue).to eq(Temporal.configuration.task_queue)
end

it 'workflow can retrieve its headers' do
workflow_id = 'header_test_wf-' + SecureRandom.uuid

run_id = Temporal.start_workflow(
MetadataWorkflow,
options: {
workflow_id: workflow_id,
headers: { 'foo' => 'bar' },
}
)

actual_result = Temporal.await_workflow_result(
MetadataWorkflow,
workflow_id: workflow_id,
run_id: run_id,
)
expect(actual_result.headers).to eq({ 'foo' => 'bar' })
end

it 'workflow can retrieve its run started at' do
workflow_id = 'started_at_test_wf-' + SecureRandom.uuid

run_id = Temporal.start_workflow(
MetadataWorkflow,
options: { workflow_id: workflow_id }
)

actual_result = Temporal.await_workflow_result(
MetadataWorkflow,
workflow_id: workflow_id,
run_id: run_id,
)
expect(Time.now - actual_result.run_started_at).to be_between(0, 30)
end

it 'gets memo from workflow execution info' do
workflow_id = 'memo_execution_test_wf-' + SecureRandom.uuid
run_id = Temporal.start_workflow(subject, options: { workflow_id: workflow_id, memo: { 'foo' => 'bar' } })

actual_result = Temporal.await_workflow_result(
subject,
workflow_id: workflow_id,
run_id: run_id,
)
expect(actual_result.memo['foo']).to eq('bar')

expect(Temporal.fetch_workflow_execution_info(
'ruby-samples', workflow_id, nil
).memo).to eq({ 'foo' => 'bar' })
end

it 'gets memo from workflow context with no memo' do
workflow_id = 'memo_context_no_memo_test_wf-' + SecureRandom.uuid

run_id = Temporal.start_workflow(
subject,
options: { workflow_id: workflow_id }
)

actual_result = Temporal.await_workflow_result(
subject,
workflow_id: workflow_id,
run_id: run_id,
)
expect(actual_result.memo).to eq({})
expect(Temporal.fetch_workflow_execution_info(
'ruby-samples', workflow_id, nil
).memo).to eq({})
end
end
75 changes: 75 additions & 0 deletions examples/spec/integration/signal_with_start_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
require 'workflows/signal_with_start_workflow'

describe 'signal with start' do

it 'signals at workflow start time' do
workflow_id = SecureRandom.uuid
run_id = Temporal.start_workflow(
SignalWithStartWorkflow,
'signal_name',
0.1,
options: {
workflow_id: workflow_id,
signal_name: 'signal_name',
signal_input: 'expected value',
}
)

result = Temporal.await_workflow_result(
SignalWithStartWorkflow,
workflow_id: workflow_id,
run_id: run_id,
)

expect(result).to eq('expected value') # the workflow should return the signal value
end

it 'signals at workflow start time with name only' do
workflow_id = SecureRandom.uuid
run_id = Temporal.start_workflow(
SignalWithStartWorkflow,
'signal_name',
0.1,
options: {
workflow_id: workflow_id,
signal_name: 'signal_name',
}
)

result = Temporal.await_workflow_result(
SignalWithStartWorkflow,
workflow_id: workflow_id,
run_id: run_id,
)

expect(result).to eq(nil) # the workflow should return the signal value
end

it 'does not launch a new workflow when signaling a running workflow through signal_with_start' do
workflow_id = SecureRandom.uuid
run_id = Temporal.start_workflow(
SignalWithStartWorkflow,
'signal_name',
10,
options: {
workflow_id: workflow_id,
signal_name: 'signal_name',
signal_input: 'expected value',
}
)

second_run_id = Temporal.start_workflow(
SignalWithStartWorkflow,
'signal_name',
0.1,
options: {
workflow_id: workflow_id,
signal_name: 'signal_name',
signal_input: 'expected value',
}
)

# If the run ids are the same, then we didn't start a new workflow
expect(second_run_id).to eq(run_id)
end
end
28 changes: 28 additions & 0 deletions examples/spec/integration/wait_for_workflow_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
require 'workflows/wait_for_workflow'

describe WaitForWorkflow do

it 'signals at workflow start time' do
workflow_id = SecureRandom.uuid
run_id = Temporal.start_workflow(
WaitForWorkflow,
10, # number of echo activities to run
2, # max activity parallelism
'signal_name',
options: { workflow_id: workflow_id }
)

Temporal.signal_workflow(WaitForWorkflow, 'signal_name', workflow_id, run_id)

result = Temporal.await_workflow_result(
WaitForWorkflow,
workflow_id: workflow_id,
run_id: run_id,
)

expect(result.length).to eq(3)
expect(result[:signal]).to eq(true)
expect(result[:timer]).to eq(true)
expect(result[:activity]).to eq(true)
end
end
2 changes: 1 addition & 1 deletion examples/workflows/long_workflow.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ def execute(cycles = 10, interval = 1)
future.cancel
end

future.wait
future.get
end
end
6 changes: 5 additions & 1 deletion examples/workflows/loop_workflow.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ def execute(count)
return workflow.continue_as_new(count - 1)
end

return count
return {
count: count,
memo: workflow.metadata.memo,
headers: workflow.metadata.headers,
}
end
end
5 changes: 5 additions & 0 deletions examples/workflows/metadata_workflow.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class MetadataWorkflow < Temporal::Workflow
def execute
workflow.metadata
end
end
16 changes: 16 additions & 0 deletions examples/workflows/signal_with_start_workflow.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class SignalWithStartWorkflow < Temporal::Workflow

def execute(expected_signal, sleep_for)
received = 'no signal received'

workflow.on_signal do |signal, input|
if signal == expected_signal
received = input
end
end

# Do something to get descheduled so the signal handler has a chance to run
workflow.sleep(sleep_for)
received
end
end
Loading