Skip to content

Commit 9e367d1

Browse files
authored
[Fix] Non-started activity cancellation (#125)
* Fix event target map entry for ACTIVITY_CANCELED event * Fix cancellation of a non-started activity * fixup! Fix event target map entry for ACTIVITY_CANCELED event
1 parent 27ac014 commit 9e367d1

File tree

7 files changed

+87
-3
lines changed

7 files changed

+87
-3
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
require 'workflows/long_workflow'
2+
3+
describe 'Activity cancellation' do
4+
let(:workflow_id) { SecureRandom.uuid }
5+
6+
it 'cancels a running activity' do
7+
run_id = Temporal.start_workflow(LongWorkflow, options: { workflow_id: workflow_id })
8+
9+
# Signal workflow after starting, allowing it to schedule the first activity
10+
sleep 0.5
11+
Temporal.signal_workflow(LongWorkflow, :CANCEL, workflow_id, run_id)
12+
13+
result = Temporal.await_workflow_result(
14+
LongWorkflow,
15+
workflow_id: workflow_id,
16+
run_id: run_id,
17+
)
18+
19+
expect(result).to be_a(LongRunningActivity::Canceled)
20+
expect(result.message).to eq('cancel activity request received')
21+
end
22+
23+
it 'cancels a non-started activity' do
24+
# Workflow is started with a signal which will cancel an activity before it has started
25+
run_id = Temporal.start_workflow(LongWorkflow, options: {
26+
workflow_id: workflow_id,
27+
signal_name: :CANCEL
28+
})
29+
30+
result = Temporal.await_workflow_result(
31+
LongWorkflow,
32+
workflow_id: workflow_id,
33+
run_id: run_id,
34+
)
35+
36+
expect(result).to be_a(Temporal::ActivityCanceled)
37+
expect(result.message).to eq('ACTIVITY_ID_NOT_STARTED')
38+
end
39+
end

examples/workflows/long_workflow.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@ def execute(cycles = 10, interval = 1)
99
future.cancel
1010
end
1111

12-
future.wait
12+
future.get
1313
end
1414
end

lib/temporal/errors.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ class TimeoutError < ClientError; end
1919
# with the intent to propagate to a workflow
2020
class ActivityException < ClientError; end
2121

22+
# Represents cancellation of a non-started activity
23+
class ActivityCanceled < ActivityException; end
24+
2225
class ActivityNotRegistered < ClientError; end
2326
class WorkflowNotRegistered < ClientError; end
2427

lib/temporal/workflow/history/event_target.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class UnexpectedEventType < InternalError; end
1919

2020
# NOTE: The order is important, first prefix match wins (will be a longer match)
2121
TARGET_TYPES = {
22-
'ACTIVITY_TASK_CANCEL' => CANCEL_ACTIVITY_REQUEST_TYPE,
22+
'ACTIVITY_TASK_CANCEL_REQUESTED' => CANCEL_ACTIVITY_REQUEST_TYPE,
2323
'ACTIVITY_TASK' => ACTIVITY_TYPE,
2424
'REQUEST_CANCEL_ACTIVITY_TASK' => CANCEL_ACTIVITY_REQUEST_TYPE,
2525
'TIMER_CANCELED' => CANCEL_TIMER_REQUEST_TYPE,

lib/temporal/workflow/state_manager.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ def apply_event(event)
162162

163163
when 'ACTIVITY_TASK_CANCELED'
164164
state_machine.cancel
165-
dispatch(target, 'failed', Temporal::Workflow::Errors.generate_error(event.attributes.failure))
165+
dispatch(target, 'failed', Temporal::ActivityCanceled.new(from_details_payloads(event.attributes.details)))
166166

167167
when 'TIMER_STARTED'
168168
state_machine.start

spec/fabricators/grpc/history_event_fabricator.rb

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
require 'securerandom'
22

3+
class TestSerializer
4+
extend Temporal::Concerns::Payloads
5+
end
6+
37
Fabricator(:api_history_event, from: Temporal::Api::History::V1::HistoryEvent) do
48
event_id { 1 }
59
event_time { Time.now }
@@ -122,6 +126,28 @@
122126
end
123127
end
124128

129+
Fabricator(:api_activity_task_canceled_event, from: :api_history_event) do
130+
event_type { Temporal::Api::Enums::V1::EventType::EVENT_TYPE_ACTIVITY_TASK_CANCELED }
131+
activity_task_canceled_event_attributes do |attrs|
132+
Temporal::Api::History::V1::ActivityTaskCanceledEventAttributes.new(
133+
details: TestSerializer.to_details_payloads('ACTIVITY_ID_NOT_STARTED'),
134+
scheduled_event_id: attrs[:event_id] - 2,
135+
started_event_id: nil,
136+
identity: 'test-worker@test-host'
137+
)
138+
end
139+
end
140+
141+
Fabricator(:api_activity_task_cancel_requested_event, from: :api_history_event) do
142+
event_type { Temporal::Api::Enums::V1::EventType::EVENT_TYPE_ACTIVITY_TASK_CANCEL_REQUESTED }
143+
activity_task_cancel_requested_event_attributes do |attrs|
144+
Temporal::Api::History::V1::ActivityTaskCancelRequestedEventAttributes.new(
145+
scheduled_event_id: attrs[:event_id] - 1,
146+
workflow_task_completed_event_id: attrs[:event_id] - 2,
147+
)
148+
end
149+
end
150+
125151
Fabricator(:api_timer_started_event, from: :api_history_event) do
126152
event_type { Temporal::Api::Enums::V1::EventType::EVENT_TYPE_TIMER_STARTED }
127153
timer_started_event_attributes do |attrs|

spec/unit/lib/temporal/workflow/history/event_target_spec.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,21 @@
2121
expect(subject.type).to eq(described_class::CANCEL_TIMER_REQUEST_TYPE)
2222
end
2323
end
24+
25+
context 'when event is ACTIVITY_CANCELED' do
26+
let(:raw_event) { Fabricate(:api_activity_task_canceled_event) }
27+
28+
it 'sets type to activity' do
29+
expect(subject.type).to eq(described_class::ACTIVITY_TYPE)
30+
end
31+
end
32+
33+
context 'when event is ACTIVITY_TASK_CANCEL_REQUESTED' do
34+
let(:raw_event) { Fabricate(:api_activity_task_cancel_requested_event) }
35+
36+
it 'sets type to cancel_activity_request' do
37+
expect(subject.type).to eq(described_class::CANCEL_ACTIVITY_REQUEST_TYPE)
38+
end
39+
end
2440
end
2541
end

0 commit comments

Comments
 (0)