From fe855495eabab98ca09c3842acfe48ee1ded69c2 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Sun, 7 Mar 2021 18:38:11 -0800 Subject: [PATCH 01/45] Basic boilerplate and a failing test --- src/machines/activity_state_machine.rs | 80 ++++++++++++++++++++++- src/machines/test_help/workflow_driver.rs | 6 ++ src/test_help/canned_histories.rs | 52 ++++++++++++++- 3 files changed, 135 insertions(+), 3 deletions(-) diff --git a/src/machines/activity_state_machine.rs b/src/machines/activity_state_machine.rs index 23efd857d..7d5c1b714 100644 --- a/src/machines/activity_state_machine.rs +++ b/src/machines/activity_state_machine.rs @@ -1,3 +1,4 @@ +use crate::protos::temporal::api::command::v1::ScheduleActivityTaskCommandAttributes; use rustfsm::{fsm, TransitionResult}; // Schedule / cancel are "explicit events" (imperative rather than past events?) @@ -54,6 +55,36 @@ pub(super) enum ActivityMachineError {} pub(super) enum ActivityCommand {} +#[derive(Debug, Clone, derive_more::Display)] +pub(super) enum ActivityCancellationType { + /** + * Wait for activity cancellation completion. Note that activity must heartbeat to receive a + * cancellation notification. This can block the cancellation for a long time if activity doesn't + * heartbeat or chooses to ignore the cancellation request. + */ + WaitCancellationCompleted, + + /** Initiate a cancellation request and immediately report cancellation to the workflow. */ + TryCancel, + + /** + * Do not request cancellation of the activity and immediately report cancellation to the workflow + */ + Abandon, +} + +impl Default for ActivityCancellationType { + fn default() -> Self { + ActivityCancellationType::TryCancel + } +} + +#[derive(Default, Clone)] +pub(super) struct SharedState { + attrs: ScheduleActivityTaskCommandAttributes, + cancellation_type: ActivityCancellationType, +} + #[derive(Default, Clone)] pub(super) struct Created {} @@ -201,6 +232,51 @@ pub(super) struct Canceled {} #[cfg(test)] mod activity_machine_tests { - #[test] - fn test() {} + use crate::machines::test_help::{CommandSender, TestHistoryBuilder, TestWorkflowDriver}; + use crate::machines::WorkflowMachines; + use crate::protos::temporal::api::command::v1::CompleteWorkflowExecutionCommandAttributes; + use crate::protos::temporal::api::command::v1::ScheduleActivityTaskCommandAttributes; + use crate::protos::temporal::api::enums::v1::CommandType; + use crate::test_help::canned_histories; + use rstest::{fixture, rstest}; + use tracing::Level; + + #[fixture] + fn activity_happy_hist() -> (TestHistoryBuilder, WorkflowMachines) { + let twd = TestWorkflowDriver::new(|mut command_sink: CommandSender| async move { + let activity = ScheduleActivityTaskCommandAttributes { + ..Default::default() + }; + command_sink.activity(activity); + + let complete = CompleteWorkflowExecutionCommandAttributes::default(); + command_sink.send(complete.into()); + }); + + let t = canned_histories::single_activity("activity1"); + let state_machines = WorkflowMachines::new( + "wfid".to_string(), + "runid".to_string(), + Box::new(twd).into(), + ); + + assert_eq!(2, t.as_history().get_workflow_task_count(None).unwrap()); + (t, state_machines) + } + + #[rstest] + fn test_activity_happy_path(activity_happy_hist: (TestHistoryBuilder, WorkflowMachines)) { + let s = span!(Level::DEBUG, "Test start", t = "activity_happy_path"); + let _enter = s.enter(); + let (t, mut state_machines) = activity_happy_hist; + let commands = t + .handle_workflow_task_take_cmds(&mut state_machines, Some(1)) + .unwrap(); + state_machines.get_wf_activation(); + assert_eq!(commands.len(), 1); + assert_eq!( + commands[0].command_type, + CommandType::ScheduleActivityTask as i32 + ); + } } diff --git a/src/machines/test_help/workflow_driver.rs b/src/machines/test_help/workflow_driver.rs index 443b66d06..8c50e5b6b 100644 --- a/src/machines/test_help/workflow_driver.rs +++ b/src/machines/test_help/workflow_driver.rs @@ -1,3 +1,4 @@ +use crate::protos::temporal::api::command::v1::ScheduleActivityTaskCommandAttributes; use crate::{ machines::WFCommand, protos::{ @@ -139,6 +140,11 @@ impl CommandSender { (Self { chan, twd_cache }, rx) } + pub fn activity(&mut self, a: ScheduleActivityTaskCommandAttributes) -> bool { + // TODO implement + false + } + /// Request to create a timer. Returns true if the timer has fired, false if it hasn't yet. /// /// If `do_wait` is true, issue a waiting command if the timer is not finished. diff --git a/src/test_help/canned_histories.rs b/src/test_help/canned_histories.rs index 9ac08c3ec..ed8df16c1 100644 --- a/src/test_help/canned_histories.rs +++ b/src/test_help/canned_histories.rs @@ -3,7 +3,8 @@ use crate::protos::temporal::api::common::v1::Payload; use crate::protos::temporal::api::enums::v1::{EventType, WorkflowTaskFailedCause}; use crate::protos::temporal::api::failure::v1::Failure; use crate::protos::temporal::api::history::v1::{ - history_event, TimerCanceledEventAttributes, TimerFiredEventAttributes, + history_event, ActivityTaskCompletedEventAttributes, ActivityTaskScheduledEventAttributes, + ActivityTaskStartedEventAttributes, TimerCanceledEventAttributes, TimerFiredEventAttributes, }; /// 1: EVENT_TYPE_WORKFLOW_EXECUTION_STARTED @@ -152,6 +153,55 @@ pub fn workflow_fails_with_failure_after_timer(timer_id: &str) -> TestHistoryBui t } +/// 1: EVENT_TYPE_WORKFLOW_EXECUTION_STARTED +/// 2: EVENT_TYPE_WORKFLOW_TASK_SCHEDULED +/// 3: EVENT_TYPE_WORKFLOW_TASK_STARTED +/// 4: EVENT_TYPE_WORKFLOW_TASK_COMPLETED +/// 5: EVENT_TYPE_ACTIVITY_TASK_SCHEDULED +/// 6: EVENT_TYPE_ACTIVITY_TASK_STARTED +/// 7: EVENT_TYPE_ACTIVITY_TASK_COMPLETED +/// 8: EVENT_TYPE_WORKFLOW_TASK_SCHEDULED +/// 9: EVENT_TYPE_WORKFLOW_TASK_STARTED +pub fn single_activity(activity_id: &str) -> TestHistoryBuilder { + let mut t = TestHistoryBuilder::default(); + t.add_by_type(EventType::WorkflowExecutionStarted); + t.add_full_wf_task(); + let scheduled_event_id = t.add_get_event_id( + EventType::ActivityTaskScheduled, + Some( + history_event::Attributes::ActivityTaskScheduledEventAttributes( + ActivityTaskScheduledEventAttributes { + activity_id: activity_id.to_string(), + ..Default::default() + }, + ), + ), + ); + let started_event_id = t.add_get_event_id( + EventType::ActivityTaskStarted, + Some( + history_event::Attributes::ActivityTaskStartedEventAttributes( + ActivityTaskStartedEventAttributes { + scheduled_event_id, + ..Default::default() + }, + ), + ), + ); + t.add( + EventType::ActivityTaskCompleted, + history_event::Attributes::ActivityTaskCompletedEventAttributes( + ActivityTaskCompletedEventAttributes { + scheduled_event_id, + started_event_id, + ..Default::default() + }, + ), + ); + t.add_workflow_task_scheduled_and_started(); + t +} + /// First signal's payload is "hello " and second is "world" (no metadata for either) /// 1: EVENT_TYPE_WORKFLOW_EXECUTION_STARTED /// 2: EVENT_TYPE_WORKFLOW_TASK_SCHEDULED From 933039ba42d2211f9dac7a0584d4e0a56978f0fc Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 8 Mar 2021 11:58:59 -0800 Subject: [PATCH 02/45] recent progress --- src/machines/activity_state_machine.rs | 82 +++++++++++++++++++++-- src/machines/mod.rs | 5 +- src/machines/test_help/workflow_driver.rs | 3 +- src/machines/workflow_machines.rs | 10 +++ 4 files changed, 91 insertions(+), 9 deletions(-) diff --git a/src/machines/activity_state_machine.rs b/src/machines/activity_state_machine.rs index 7d5c1b714..cbe98be01 100644 --- a/src/machines/activity_state_machine.rs +++ b/src/machines/activity_state_machine.rs @@ -1,10 +1,15 @@ -use crate::protos::temporal::api::command::v1::ScheduleActivityTaskCommandAttributes; -use rustfsm::{fsm, TransitionResult}; +use crate::machines::workflow_machines::MachineResponse; +use crate::machines::{Cancellable, NewMachineWithCommand, WFMachinesAdapter, WFMachinesError}; +use crate::protos::temporal::api::command::v1::{Command, ScheduleActivityTaskCommandAttributes}; +use crate::protos::temporal::api::enums::v1::CommandType; +use crate::protos::temporal::api::history::v1::HistoryEvent; +use rustfsm::{fsm, MachineError, StateMachine, TransitionResult}; +use std::convert::TryFrom; // Schedule / cancel are "explicit events" (imperative rather than past events?) fsm! { - pub(super) name ActivityMachine; command ActivityCommand; error ActivityMachineError; + pub(super) name ActivityMachine; command ActivityCommand; error WFMachinesError; Created --(Schedule, on_schedule)--> ScheduleCommandCreated; @@ -50,9 +55,6 @@ fsm! { --(ActivityTaskCanceled, on_activity_task_canceled) --> Canceled; } -#[derive(thiserror::Error, Debug)] -pub(super) enum ActivityMachineError {} - pub(super) enum ActivityCommand {} #[derive(Debug, Clone, derive_more::Display)] @@ -79,6 +81,74 @@ impl Default for ActivityCancellationType { } } +/// Creates a new, scheduled, activity as a [CancellableCommand] +pub(super) fn new_activity( + attribs: ScheduleActivityTaskCommandAttributes, +) -> NewMachineWithCommand { + let (activity, add_cmd) = ActivityMachine::new_scheduled(attribs); + NewMachineWithCommand { + command: add_cmd, + machine: activity, + } +} + +impl ActivityMachine { + pub(crate) fn new_scheduled(attribs: ScheduleActivityTaskCommandAttributes) -> (Self, Command) { + let mut s = Self::new(attribs); + s.on_event_mut(ActivityMachine::Schedule) + .expect("Scheduling activities doesn't fail"); + let cmd = Command { + command_type: CommandType::ScheduleActivityTask as i32, + attributes: Some(s.shared_state().attrs.clone().into()), + }; + (s, cmd) + } +} + +impl TryFrom for ActivityMachine { + type Error = WFMachinesError; + + fn try_from(value: HistoryEvent) -> Result { + unimplemented!() + } +} + +impl TryFrom for ActivityMachine { + type Error = (); + + fn try_from(c: CommandType) -> Result { + unimplemented!() + } +} + +impl WFMachinesAdapter for ActivityMachine { + fn adapt_response( + &self, + event: &HistoryEvent, + has_next_event: bool, + my_command: ActivityMachineCommand, + ) -> Result, WFMachinesError> { + Ok(!vec![]) + } +} + +impl Cancellable for ActivityMachine { + fn cancel(&mut self) -> Result> { + unimplemented!() + } + + fn was_cancelled_before_sent_to_server(&self) -> bool { + unimplemented!() + } +} + +#[derive(Debug, derive_more::Display)] +pub(super) enum ActivityMachineCommand { + Complete, + Canceled, + IssueCancelCmd(Command), +} + #[derive(Default, Clone)] pub(super) struct SharedState { attrs: ScheduleActivityTaskCommandAttributes, diff --git a/src/machines/mod.rs b/src/machines/mod.rs index 9ef95a3c4..f371c33ea 100644 --- a/src/machines/mod.rs +++ b/src/machines/mod.rs @@ -33,7 +33,9 @@ pub(crate) mod test_help; pub(crate) use workflow_machines::{WFMachinesError, WorkflowMachines}; -use crate::protos::temporal::api::command::v1::FailWorkflowExecutionCommandAttributes; +use crate::protos::temporal::api::command::v1::{ + FailWorkflowExecutionCommandAttributes, ScheduleActivityTaskCommandAttributes, +}; use crate::{ core_tracing::VecDisplayer, machines::workflow_machines::MachineResponse, @@ -64,6 +66,7 @@ pub(crate) type ProtoCommand = Command; pub enum WFCommand { /// Returned when we need to wait for the lang sdk to send us something NoCommandsFromLang, + AddActivity(ScheduleActivityTaskCommandAttributes), AddTimer(StartTimerCommandAttributes), CancelTimer(CancelTimerCommandAttributes), CompleteWorkflow(CompleteWorkflowExecutionCommandAttributes), diff --git a/src/machines/test_help/workflow_driver.rs b/src/machines/test_help/workflow_driver.rs index 8c50e5b6b..078cb1132 100644 --- a/src/machines/test_help/workflow_driver.rs +++ b/src/machines/test_help/workflow_driver.rs @@ -141,8 +141,7 @@ impl CommandSender { } pub fn activity(&mut self, a: ScheduleActivityTaskCommandAttributes) -> bool { - // TODO implement - false + unimplemented!() } /// Request to create a timer. Returns true if the timer has fired, false if it hasn't yet. diff --git a/src/machines/workflow_machines.rs b/src/machines/workflow_machines.rs index 22bcb484a..616bc5885 100644 --- a/src/machines/workflow_machines.rs +++ b/src/machines/workflow_machines.rs @@ -1,3 +1,4 @@ +use crate::machines::activity_state_machine::new_activity; use crate::workflow::{DrivenWorkflow, WorkflowFetcher}; use crate::{ core_tracing::VecDisplayer, @@ -57,6 +58,9 @@ pub(crate) struct WorkflowMachines { /// TODO: Make this apply to *all* cancellable things, once we've added more. Key can be enum. timer_id_to_machine: HashMap, + /// TODO document + activity_id_to_machine: HashMap, + /// Queued commands which have been produced by machines and await processing / being sent to /// the server. commands: VecDeque, @@ -551,6 +555,12 @@ impl WorkflowMachines { } } } + WFCommand::AddActivity(attrs) => { + let aid = attrs.activity_id.clone(); + let activity = self.add_new_machine(new_activity(attrs)); + self.activity_id_to_machine.insert(aid, activity.machine); + self.current_wf_task_commands.push_back(activity); + } WFCommand::CompleteWorkflow(attrs) => { let cwfm = self.add_new_machine(complete_workflow(attrs)); self.current_wf_task_commands.push_back(cwfm); From af6229958aad5d1cc4a4e43a0f9b08663c36d65b Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 8 Mar 2021 17:44:53 -0800 Subject: [PATCH 03/45] fix compilation issues and add proto messages --- src/machines/activity_state_machine.rs | 24 +++++++++++++++++------- src/machines/workflow_machines.rs | 1 + 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/machines/activity_state_machine.rs b/src/machines/activity_state_machine.rs index cbe98be01..1b5ecde45 100644 --- a/src/machines/activity_state_machine.rs +++ b/src/machines/activity_state_machine.rs @@ -9,7 +9,10 @@ use std::convert::TryFrom; // Schedule / cancel are "explicit events" (imperative rather than past events?) fsm! { - pub(super) name ActivityMachine; command ActivityCommand; error WFMachinesError; + pub(super) name ActivityMachine; + command ActivityCommand; + error WFMachinesError; + shared_state SharedState; Created --(Schedule, on_schedule)--> ScheduleCommandCreated; @@ -55,6 +58,7 @@ fsm! { --(ActivityTaskCanceled, on_activity_task_canceled) --> Canceled; } +#[derive(Debug, derive_more::Display)] pub(super) enum ActivityCommand {} #[derive(Debug, Clone, derive_more::Display)] @@ -94,8 +98,14 @@ pub(super) fn new_activity( impl ActivityMachine { pub(crate) fn new_scheduled(attribs: ScheduleActivityTaskCommandAttributes) -> (Self, Command) { - let mut s = Self::new(attribs); - s.on_event_mut(ActivityMachine::Schedule) + let mut s = Self { + state: Created {}.into(), + shared_state: SharedState { + attrs: attribs, + cancellation_type: ActivityCancellationType::TryCancel, + }, + }; + s.on_event_mut(ActivityMachineEvents::Schedule) .expect("Scheduling activities doesn't fail"); let cmd = Command { command_type: CommandType::ScheduleActivityTask as i32, @@ -105,7 +115,7 @@ impl ActivityMachine { } } -impl TryFrom for ActivityMachine { +impl TryFrom for ActivityMachineEvents { type Error = WFMachinesError; fn try_from(value: HistoryEvent) -> Result { @@ -113,7 +123,7 @@ impl TryFrom for ActivityMachine { } } -impl TryFrom for ActivityMachine { +impl TryFrom for ActivityMachineEvents { type Error = (); fn try_from(c: CommandType) -> Result { @@ -126,9 +136,9 @@ impl WFMachinesAdapter for ActivityMachine { &self, event: &HistoryEvent, has_next_event: bool, - my_command: ActivityMachineCommand, + my_command: ActivityCommand, ) -> Result, WFMachinesError> { - Ok(!vec![]) + Ok(match my_command {}) } } diff --git a/src/machines/workflow_machines.rs b/src/machines/workflow_machines.rs index 616bc5885..4629a2ee7 100644 --- a/src/machines/workflow_machines.rs +++ b/src/machines/workflow_machines.rs @@ -146,6 +146,7 @@ impl WorkflowMachines { all_machines: Default::default(), machines_by_event_id: Default::default(), timer_id_to_machine: Default::default(), + activity_id_to_machine: Default::default(), commands: Default::default(), current_wf_task_commands: Default::default(), } From dfde0bf1a21bbbbe752b5b10f1dc3e7ad3b7bf48 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 8 Mar 2021 20:01:01 -0800 Subject: [PATCH 04/45] Convert ActivityCommand in WFMachinesAdapter --- src/machines/activity_state_machine.rs | 22 ++++++++++++++++++++-- src/machines/workflow_machines.rs | 8 ++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/machines/activity_state_machine.rs b/src/machines/activity_state_machine.rs index 1b5ecde45..ddacd7a3e 100644 --- a/src/machines/activity_state_machine.rs +++ b/src/machines/activity_state_machine.rs @@ -1,5 +1,7 @@ use crate::machines::workflow_machines::MachineResponse; +use crate::machines::workflow_machines::MachineResponse::PushActivityJob; use crate::machines::{Cancellable, NewMachineWithCommand, WFMachinesAdapter, WFMachinesError}; +use crate::protos::coresdk::{activity_task, CancelActivity, StartActivity}; use crate::protos::temporal::api::command::v1::{Command, ScheduleActivityTaskCommandAttributes}; use crate::protos::temporal::api::enums::v1::CommandType; use crate::protos::temporal::api::history::v1::HistoryEvent; @@ -59,7 +61,10 @@ fsm! { } #[derive(Debug, derive_more::Display)] -pub(super) enum ActivityCommand {} +pub(super) enum ActivityCommand { + StartActivity, + CancelActivity, +} #[derive(Debug, Clone, derive_more::Display)] pub(super) enum ActivityCancellationType { @@ -138,7 +143,20 @@ impl WFMachinesAdapter for ActivityMachine { has_next_event: bool, my_command: ActivityCommand, ) -> Result, WFMachinesError> { - Ok(match my_command {}) + Ok(match my_command { + ActivityCommand::StartActivity => { + vec![PushActivityJob(activity_task::Job::Start(StartActivity { + // TODO pass activity details + }))] + } + ActivityCommand::CancelActivity => { + vec![PushActivityJob(activity_task::Job::Cancel( + CancelActivity { + // TODO pass activity cancellation details + }, + ))] + } + }) } } diff --git a/src/machines/workflow_machines.rs b/src/machines/workflow_machines.rs index 4629a2ee7..c9d397e86 100644 --- a/src/machines/workflow_machines.rs +++ b/src/machines/workflow_machines.rs @@ -1,4 +1,5 @@ use crate::machines::activity_state_machine::new_activity; +use crate::protos::coresdk::activity_task; use crate::workflow::{DrivenWorkflow, WorkflowFetcher}; use crate::{ core_tracing::VecDisplayer, @@ -90,6 +91,10 @@ struct CommandAndMachine { pub enum MachineResponse { #[display(fmt = "PushWFJob")] PushWFJob(#[from(forward)] wf_activation_job::Variant), + + #[display(fmt = "PushActivityJob")] + PushActivityJob(activity_task::Job), + IssueNewCommand(ProtoCommand), #[display(fmt = "TriggerWFTaskStarted")] TriggerWFTaskStarted { @@ -515,6 +520,9 @@ impl WorkflowMachines { MachineResponse::IssueNewCommand(_) => { panic!("Issue new command machine response not expected here") } + MachineResponse::PushActivityJob(a) => { + unimplemented!() + } } } Ok(()) From 72fa5f7e399322466dc0d85b0052cb4094fc94f4 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 8 Mar 2021 20:36:15 -0800 Subject: [PATCH 05/45] Add CommandSender for activity in the workflow driver --- src/machines/activity_state_machine.rs | 3 ++- src/machines/test_help/workflow_driver.rs | 23 +++++++++++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/machines/activity_state_machine.rs b/src/machines/activity_state_machine.rs index ddacd7a3e..ccb348c34 100644 --- a/src/machines/activity_state_machine.rs +++ b/src/machines/activity_state_machine.rs @@ -343,9 +343,10 @@ mod activity_machine_tests { fn activity_happy_hist() -> (TestHistoryBuilder, WorkflowMachines) { let twd = TestWorkflowDriver::new(|mut command_sink: CommandSender| async move { let activity = ScheduleActivityTaskCommandAttributes { + activity_id: "activity A".to_string(), ..Default::default() }; - command_sink.activity(activity); + command_sink.activity(activity, true); let complete = CompleteWorkflowExecutionCommandAttributes::default(); command_sink.send(complete.into()); diff --git a/src/machines/test_help/workflow_driver.rs b/src/machines/test_help/workflow_driver.rs index 078cb1132..2ad551876 100644 --- a/src/machines/test_help/workflow_driver.rs +++ b/src/machines/test_help/workflow_driver.rs @@ -38,6 +38,9 @@ pub(in crate::machines) struct TestWfDriverCache { /// means keeping the workflow suspended in a future somewhere. I tried this and it was hard, /// but ultimately it's how real workflows will need to work. unblocked_timers: DashMap, + + // Holds a mapping of activity id -> is completed + completed_activities: DashMap, } impl TestWorkflowDriver @@ -140,8 +143,24 @@ impl CommandSender { (Self { chan, twd_cache }, rx) } - pub fn activity(&mut self, a: ScheduleActivityTaskCommandAttributes) -> bool { - unimplemented!() + pub fn activity(&mut self, a: ScheduleActivityTaskCommandAttributes, do_wait: bool) -> bool { + let finished = match self + .twd_cache + .completed_activities + .entry(a.activity_id.clone()) + { + dashmap::mapref::entry::Entry::Occupied(existing) => *existing.get(), + dashmap::mapref::entry::Entry::Vacant(v) => { + let c = WFCommand::AddActivity(a); + self.chan.send(c.into()).unwrap(); + v.insert(false); + false + } + }; + if !finished && do_wait { + self.chan.send(TestWFCommand::Waiting).unwrap(); + } + finished } /// Request to create a timer. Returns true if the timer has fired, false if it hasn't yet. From 1d33414540bdc49e8ecf06c46f859ea5025d430d Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 8 Mar 2021 21:00:58 -0800 Subject: [PATCH 06/45] Add TryFrom for CommandType and make the test pass --- src/machines/activity_state_machine.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/machines/activity_state_machine.rs b/src/machines/activity_state_machine.rs index ccb348c34..84e346ebf 100644 --- a/src/machines/activity_state_machine.rs +++ b/src/machines/activity_state_machine.rs @@ -132,7 +132,11 @@ impl TryFrom for ActivityMachineEvents { type Error = (); fn try_from(c: CommandType) -> Result { - unimplemented!() + Ok(match c { + CommandType::ScheduleActivityTask => Self::CommandScheduleActivityTask, + CommandType::RequestCancelActivityTask => Self::CommandRequestCancelActivityTask, + _ => return Err(()), + }) } } @@ -166,7 +170,7 @@ impl Cancellable for ActivityMachine { } fn was_cancelled_before_sent_to_server(&self) -> bool { - unimplemented!() + false // TODO return cancellation flag from the shared state } } From 7aead98e6c45317c53fa3926255a9458d6e61d94 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Wed, 10 Mar 2021 22:52:53 -0800 Subject: [PATCH 07/45] Add core API for polling activities --- protos/local/core_interface.proto | 2 +- src/lib.rs | 76 ++++++++++++++++++-------- src/machines/activity_state_machine.rs | 28 ++++++++-- src/machines/test_help/mod.rs | 4 +- src/machines/workflow_machines.rs | 5 +- src/pollers/mod.rs | 71 +++++++++++++++++++----- src/workflow/driven_workflow.rs | 2 +- 7 files changed, 140 insertions(+), 48 deletions(-) diff --git a/protos/local/core_interface.proto b/protos/local/core_interface.proto index 0880ad626..a3bd97734 100644 --- a/protos/local/core_interface.proto +++ b/protos/local/core_interface.proto @@ -155,7 +155,7 @@ message CancelActivity { message ActivityTask { string activity_id = 1; - oneof job { + oneof variant { // Start activity execution. StartActivity start = 2; // Attempt to cancel activity execution. diff --git a/src/lib.rs b/src/lib.rs index 49a752131..1dadabca2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,16 +22,20 @@ mod workflow; mod test_help; pub use core_tracing::tracing_init; -pub use pollers::{ServerGateway, ServerGatewayApis, ServerGatewayOptions}; +pub use pollers::{ + PollTaskRequest, PollTaskResponse, ServerGateway, ServerGatewayApis, ServerGatewayOptions, +}; pub use url::Url; +use crate::protos::coresdk::ActivityTask; +use crate::protos::temporal::api::workflowservice::v1::PollActivityTaskQueueResponse; use crate::{ machines::{InconvertibleCommandError, WFCommand, WFMachinesError}, pending_activations::{PendingActivation, PendingActivations}, protos::{ coresdk::{ - task_completion, wf_activation_completion::Status, Task, TaskCompletion, - WfActivationCompletion, WfActivationSuccess, + task_completion, wf_activation_completion::Status, ActivityResult, Task, + TaskCompletion, WfActivationCompletion, WfActivationSuccess, }, temporal::api::{ enums::v1::WorkflowTaskFailedCause, workflowservice::v1::PollWorkflowTaskQueueResponse, @@ -62,13 +66,17 @@ pub type Result = std::result::Result; /// expected that only one instance of an implementation will exist for the lifetime of the /// worker(s) using it. pub trait Core: Send + Sync { - /// Ask the core for some work, returning a [Task], which will eventually contain either a - /// [protos::coresdk::WfActivation] or an [protos::coresdk::ActivityTask]. It is then the - /// language SDK's responsibility to call the appropriate code with the provided inputs. + /// Ask the core for some work, returning a [Task], which will contain a [protos::coresdk::WfActivation]. + /// It is then the language SDK's responsibility to call the appropriate code with the provided inputs. /// /// TODO: Examples + /// TODO: rename to poll_workflow_task and change result type to WfActivation fn poll_task(&self, task_queue: &str) -> Result; + /// Ask the core for some work, returning a [protos::coresdk::Task], which will contain a [protos::coresdk::ActivityTask]. + /// It is then the language SDK's responsibility to call the completion API. + fn poll_activity_task(&self, task_queue: &str) -> Result; + /// Tell the core that some work has been completed - whether as a result of running workflow /// code or executing an activity. fn complete_task(&self, req: TaskCompletion) -> Result<()>; @@ -158,8 +166,8 @@ where // This will block forever (unless interrupted by shutdown) in the event there is no work // from the server - match self.poll_server(task_queue) { - Ok(work) => { + match self.poll_server(PollTaskRequest::Workflow(task_queue.to_owned())) { + Ok(PollTaskResponse::WorkflowTask(work)) => { let task_token = work.task_token.clone(); debug!( task_token = %fmt_task_token(&task_token), @@ -180,8 +188,29 @@ where variant: next_activation.activation.map(Into::into), }) } + // Drain pending activations in case of shutdown. Err(CoreError::ShuttingDown) => self.poll_task(task_queue), Err(e) => Err(e), + Ok(PollTaskResponse::ActivityTask(_)) => Err(CoreError::UnexpectedResult), + } + } + + #[instrument(skip(self))] + fn poll_activity_task(&self, task_queue: &str) -> Result { + if self.shutdown_requested.load(Ordering::SeqCst) { + return Err(CoreError::ShuttingDown); + } + + match self.poll_server(PollTaskRequest::Activity(task_queue.to_owned())) { + Ok(PollTaskResponse::ActivityTask(work)) => { + let task_token = work.task_token.clone(); + Ok(Task { + task_token, + variant: None, + }) + } + Err(e) => Err(e), + Ok(PollTaskResponse::WorkflowTask(_)) => Err(CoreError::UnexpectedResult), } } @@ -233,8 +262,11 @@ where Ok(()) } TaskCompletion { - variant: Some(task_completion::Variant::Activity(_)), - .. + task_token, + variant: + Some(task_completion::Variant::Activity(ActivityResult { + status: Some(activity_status), + })), } => unimplemented!(), _ => Err(CoreError::MalformedCompletion(req)), } @@ -301,7 +333,7 @@ impl CoreSDK { /// Blocks polling the server until it responds, or until the shutdown flag is set (aborting /// the poll) - fn poll_server(&self, task_queue: &str) -> Result { + fn poll_server(&self, req: PollTaskRequest) -> Result { self.runtime.block_on(async { let shutdownfut = async { loop { @@ -311,14 +343,12 @@ impl CoreSDK { tokio::time::sleep(Duration::from_millis(100)).await; } }; - let pollfut = self - .server_gateway - .poll_workflow_task(task_queue.to_owned()); + let poll_result_future = self.server_gateway.poll_task(req); tokio::select! { _ = shutdownfut => { Err(CoreError::ShuttingDown) } - r = pollfut => r + r = poll_result_future => r } }) } @@ -380,6 +410,9 @@ pub enum CoreError { /// When thrown from complete_task, it means you should poll for a new task, receive a new /// task token, and complete that task. UnhandledCommandWhenCompleting, + /// Indicates that underlying function returned Ok, but result type was incorrect. + /// This is likely a result of a bug and should never happen. + UnexpectedResult, } #[cfg(test)] @@ -813,14 +846,11 @@ mod test { let res = core.poll_task(TASK_Q).unwrap(); assert_matches!( res.get_wf_jobs().as_slice(), - [ - WfActivationJob { - variant: Some(wf_activation_job::Variant::SignalWorkflow(_)), - }, - WfActivationJob { - variant: Some(wf_activation_job::Variant::SignalWorkflow(_)), - } - ] + [WfActivationJob { + variant: Some(wf_activation_job::Variant::SignalWorkflow(_)), + }, WfActivationJob { + variant: Some(wf_activation_job::Variant::SignalWorkflow(_)), + }] ); } } diff --git a/src/machines/activity_state_machine.rs b/src/machines/activity_state_machine.rs index 84e346ebf..9f2220b84 100644 --- a/src/machines/activity_state_machine.rs +++ b/src/machines/activity_state_machine.rs @@ -3,7 +3,7 @@ use crate::machines::workflow_machines::MachineResponse::PushActivityJob; use crate::machines::{Cancellable, NewMachineWithCommand, WFMachinesAdapter, WFMachinesError}; use crate::protos::coresdk::{activity_task, CancelActivity, StartActivity}; use crate::protos::temporal::api::command::v1::{Command, ScheduleActivityTaskCommandAttributes}; -use crate::protos::temporal::api::enums::v1::CommandType; +use crate::protos::temporal::api::enums::v1::{CommandType, EventType}; use crate::protos::temporal::api::history::v1::HistoryEvent; use rustfsm::{fsm, MachineError, StateMachine, TransitionResult}; use std::convert::TryFrom; @@ -123,8 +123,22 @@ impl ActivityMachine { impl TryFrom for ActivityMachineEvents { type Error = WFMachinesError; - fn try_from(value: HistoryEvent) -> Result { - unimplemented!() + fn try_from(e: HistoryEvent) -> Result { + Ok(match EventType::from_i32(e.event_type) { + Some(EventType::ActivityTaskScheduled) => Self::ActivityTaskScheduled, + Some(EventType::ActivityTaskStarted) => Self::ActivityTaskStarted, + Some(EventType::ActivityTaskCompleted) => Self::ActivityTaskCompleted, + Some(EventType::ActivityTaskFailed) => Self::ActivityTaskFailed, + Some(EventType::ActivityTaskTimedOut) => Self::ActivityTaskTimedOut, + Some(EventType::ActivityTaskCancelRequested) => Self::ActivityTaskCancelRequested, + Some(EventType::ActivityTaskCanceled) => Self::ActivityTaskCanceled, + _ => { + return Err(WFMachinesError::UnexpectedEvent( + e, + "Activity machine does not handle this event", + )) + } + }) } } @@ -149,12 +163,14 @@ impl WFMachinesAdapter for ActivityMachine { ) -> Result, WFMachinesError> { Ok(match my_command { ActivityCommand::StartActivity => { - vec![PushActivityJob(activity_task::Job::Start(StartActivity { + vec![PushActivityJob(activity_task::Variant::Start( + StartActivity { // TODO pass activity details - }))] + }, + ))] } ActivityCommand::CancelActivity => { - vec![PushActivityJob(activity_task::Job::Cancel( + vec![PushActivityJob(activity_task::Variant::Cancel( CancelActivity { // TODO pass activity cancellation details }, diff --git a/src/machines/test_help/mod.rs b/src/machines/test_help/mod.rs index dc5aaec8d..e0dedb3fd 100644 --- a/src/machines/test_help/mod.rs +++ b/src/machines/test_help/mod.rs @@ -14,7 +14,7 @@ use crate::{ protos::temporal::api::workflowservice::v1::{ PollWorkflowTaskQueueResponse, RespondWorkflowTaskCompletedResponse, }, - CoreSDK, ServerGatewayApis, + CoreSDK, PollTaskResponse, ServerGatewayApis, }; use rand::{thread_rng, Rng}; use std::sync::atomic::AtomicBool; @@ -59,7 +59,7 @@ pub(crate) fn build_fake_core( mock_gateway .expect_poll_workflow_task() .times(response_batches.len()) - .returning(move |_| Ok(tasks.pop_front().unwrap())); + .returning(move |_| Ok(PollTaskResponse::WorkflowTask(tasks.pop_front().unwrap()))); // Response not really important here mock_gateway .expect_complete_workflow_task() diff --git a/src/machines/workflow_machines.rs b/src/machines/workflow_machines.rs index c9d397e86..f50d870e0 100644 --- a/src/machines/workflow_machines.rs +++ b/src/machines/workflow_machines.rs @@ -10,7 +10,7 @@ use crate::{ TemporalStateMachine, WFCommand, }, protos::{ - coresdk::{wf_activation_job, StartWorkflow, UpdateRandomSeed, WfActivation}, + coresdk::{wf_activation_job, ActivityTask, StartWorkflow, UpdateRandomSeed, WfActivation}, temporal::api::{ enums::v1::{CommandType, EventType}, history::v1::{history_event, HistoryEvent}, @@ -92,8 +92,9 @@ pub enum MachineResponse { #[display(fmt = "PushWFJob")] PushWFJob(#[from(forward)] wf_activation_job::Variant), + // TODO remove and use IssueNewCommand #[display(fmt = "PushActivityJob")] - PushActivityJob(activity_task::Job), + PushActivityJob(activity_task::Variant), IssueNewCommand(ProtoCommand), #[display(fmt = "TriggerWFTaskStarted")] diff --git a/src/pollers/mod.rs b/src/pollers/mod.rs index 6563033bc..4b29fb470 100644 --- a/src/pollers/mod.rs +++ b/src/pollers/mod.rs @@ -2,7 +2,8 @@ use std::time::Duration; use crate::protos::temporal::api::common::v1::{Payloads, WorkflowExecution}; use crate::protos::temporal::api::workflowservice::v1::{ - SignalWorkflowExecutionRequest, SignalWorkflowExecutionResponse, + PollActivityTaskQueueRequest, PollActivityTaskQueueResponse, SignalWorkflowExecutionRequest, + SignalWorkflowExecutionResponse, }; use crate::{ machines::ProtoCommand, @@ -100,8 +101,13 @@ pub trait ServerGatewayApis { ) -> Result; /// Fetch new work. Should block indefinitely if there is no work. - async fn poll_workflow_task(&self, task_queue: String) - -> Result; + async fn poll_task(&self, req: PollTaskRequest) -> Result; + + /// Fetch new work. Should block indefinitely if there is no work. + async fn poll_workflow_task(&self, task_queue: String) -> Result; + + /// Fetch new work. Should block indefinitely if there is no work. + async fn poll_activity_task(&self, task_queue: String) -> Result; /// Complete a task by sending it to the server. `task_token` is the task token that would've /// been received from [PollWorkflowTaskQueueApi::poll]. `commands` is a list of new commands @@ -131,6 +137,16 @@ pub trait ServerGatewayApis { ) -> Result; } +pub enum PollTaskRequest { + Workflow(String), + Activity(String), +} + +pub enum PollTaskResponse { + WorkflowTask(PollWorkflowTaskQueueResponse), + ActivityTask(PollActivityTaskQueueResponse), +} + #[async_trait::async_trait] impl ServerGatewayApis for ServerGateway { async fn start_workflow( @@ -162,10 +178,18 @@ impl ServerGatewayApis for ServerGateway { .into_inner()) } - async fn poll_workflow_task( - &self, - task_queue: String, - ) -> Result { + async fn poll_task(&self, req: PollTaskRequest) -> Result { + match req { + PollTaskRequest::Workflow(task_queue) => { + Ok(Self::poll_workflow_task(self, task_queue).await?) + } + PollTaskRequest::Activity(task_queue) => { + Ok(Self::poll_activity_task(self, task_queue).await?) + } + } + } + + async fn poll_workflow_task(&self, task_queue: String) -> Result { let request = PollWorkflowTaskQueueRequest { namespace: self.opts.namespace.clone(), task_queue: Some(TaskQueue { @@ -176,12 +200,33 @@ impl ServerGatewayApis for ServerGateway { binary_checksum: self.opts.worker_binary_id.clone(), }; - Ok(self - .service - .clone() - .poll_workflow_task_queue(request) - .await? - .into_inner()) + Ok(PollTaskResponse::WorkflowTask( + self.service + .clone() + .poll_workflow_task_queue(request) + .await? + .into_inner(), + )) + } + + async fn poll_activity_task(&self, task_queue: String) -> Result { + let request = PollActivityTaskQueueRequest { + namespace: self.opts.namespace.clone(), + task_queue: Some(TaskQueue { + name: task_queue, + kind: TaskQueueKind::Unspecified as i32, + }), + identity: self.opts.identity.clone(), + task_queue_metadata: None, + }; + + Ok(PollTaskResponse::ActivityTask( + self.service + .clone() + .poll_activity_task_queue(request) + .await? + .into_inner(), + )) } async fn complete_workflow_task( diff --git a/src/workflow/driven_workflow.rs b/src/workflow/driven_workflow.rs index 4decbee1e..a541910a8 100644 --- a/src/workflow/driven_workflow.rs +++ b/src/workflow/driven_workflow.rs @@ -1,4 +1,4 @@ -use crate::protos::coresdk::SignalWorkflow; +use crate::protos::coresdk::{activity_task, SignalWorkflow}; use crate::{ machines::WFCommand, protos::coresdk::wf_activation_job, From 45b5176a8c60762f96842fafbe070c969f1a1f76 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Sun, 14 Mar 2021 23:07:54 -0700 Subject: [PATCH 08/45] Add complete activity task API to the core --- protos/local/core_interface.proto | 3 +-- src/lib.rs | 35 ++++++++++++++++++++++++------- src/pollers/mod.rs | 29 +++++++++++++++++++++++-- 3 files changed, 56 insertions(+), 11 deletions(-) diff --git a/protos/local/core_interface.proto b/protos/local/core_interface.proto index a3bd97734..259de62ad 100644 --- a/protos/local/core_interface.proto +++ b/protos/local/core_interface.proto @@ -187,8 +187,7 @@ message WFActivationCompletion { message ActivityResult { oneof status { ActivityTaskSuccess completed = 1; - ActivityTaskCancelation canceled = 2; - ActivityTaskFailure failed = 3; + ActivityTaskFailure failed = 2; } } diff --git a/src/lib.rs b/src/lib.rs index 1dadabca2..38ec4356e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,15 +27,15 @@ pub use pollers::{ }; pub use url::Url; -use crate::protos::coresdk::ActivityTask; +use crate::protos::coresdk::{activity_result, ActivityTask}; use crate::protos::temporal::api::workflowservice::v1::PollActivityTaskQueueResponse; use crate::{ machines::{InconvertibleCommandError, WFCommand, WFMachinesError}, pending_activations::{PendingActivation, PendingActivations}, protos::{ coresdk::{ - task_completion, wf_activation_completion::Status, ActivityResult, Task, - TaskCompletion, WfActivationCompletion, WfActivationSuccess, + task_completion, wf_activation_completion, ActivityResult, Task, TaskCompletion, + WfActivationCompletion, WfActivationSuccess, }, temporal::api::{ enums::v1::WorkflowTaskFailedCause, workflowservice::v1::PollWorkflowTaskQueueResponse, @@ -81,6 +81,9 @@ pub trait Core: Send + Sync { /// code or executing an activity. fn complete_task(&self, req: TaskCompletion) -> Result<()>; + /// Tell the core that activity has completed. This will result in core calling the server and completing activity synchronously. + fn complete_activity_task(&self, req: TaskCompletion) -> Result<()>; + /// Returns an instance of ServerGateway. fn server_gateway(&self) -> Result>; @@ -230,7 +233,7 @@ where .map(|x| x.value().clone()) .ok_or_else(|| CoreError::NothingFoundForTaskToken(task_token.clone()))?; match wfstatus { - Status::Successful(success) => { + wf_activation_completion::Status::Successful(success) => { self.push_lang_commands(&run_id, success)?; let commands = self.access_wf_machine(&run_id, move |mgr| { Ok(mgr.machines.get_commands()) @@ -245,7 +248,7 @@ where )?; } } - Status::Failed(failure) => { + wf_activation_completion::Status::Failed(failure) => { // Blow up any cached data associated with the workflow self.evict_run(&run_id); @@ -261,13 +264,31 @@ where } Ok(()) } + _ => Err(CoreError::MalformedCompletion(req)), + } + } + + #[instrument(skip(self))] + fn complete_activity_task(&self, req: TaskCompletion) -> Result<()> { + match req { TaskCompletion { task_token, variant: Some(task_completion::Variant::Activity(ActivityResult { - status: Some(activity_status), + status: Some(status), })), - } => unimplemented!(), + } => { + match status { + activity_result::Status::Completed(success) => { + self.runtime.block_on( + self.server_gateway + .complete_activity_task(task_token, success.result), + )?; + } + activity_result::Status::Failed(_) => unimplemented!(), + } + Ok(()) + } _ => Err(CoreError::MalformedCompletion(req)), } } diff --git a/src/pollers/mod.rs b/src/pollers/mod.rs index 4b29fb470..0efb1d25a 100644 --- a/src/pollers/mod.rs +++ b/src/pollers/mod.rs @@ -2,8 +2,9 @@ use std::time::Duration; use crate::protos::temporal::api::common::v1::{Payloads, WorkflowExecution}; use crate::protos::temporal::api::workflowservice::v1::{ - PollActivityTaskQueueRequest, PollActivityTaskQueueResponse, SignalWorkflowExecutionRequest, - SignalWorkflowExecutionResponse, + PollActivityTaskQueueRequest, PollActivityTaskQueueResponse, + RespondActivityTaskCompletedRequest, RespondActivityTaskCompletedResponse, + SignalWorkflowExecutionRequest, SignalWorkflowExecutionResponse, }; use crate::{ machines::ProtoCommand, @@ -118,6 +119,12 @@ pub trait ServerGatewayApis { commands: Vec, ) -> Result; + async fn complete_activity_task( + &self, + task_token: Vec, + result: Option, + ) -> Result; + /// Fail task by sending the failure to the server. `task_token` is the task token that would've /// been received from [PollWorkflowTaskQueueApi::poll]. async fn fail_workflow_task( @@ -305,4 +312,22 @@ impl ServerGatewayApis for ServerGateway { .await? .into_inner()) } + + async fn complete_activity_task( + &self, + task_token: Vec, + result: Option, + ) -> Result { + Ok(self + .service + .clone() + .respond_activity_task_completed(RespondActivityTaskCompletedRequest { + task_token, + result, + identity: self.opts.identity.clone(), + namespace: self.opts.namespace.clone(), + }) + .await? + .into_inner()) + } } From dd1ecbfa86769b098f1938194531c7469d8cdd5c Mon Sep 17 00:00:00 2001 From: Vitaly Date: Sun, 14 Mar 2021 23:20:52 -0700 Subject: [PATCH 09/45] Return cancellation result back --- protos/local/core_interface.proto | 1 + 1 file changed, 1 insertion(+) diff --git a/protos/local/core_interface.proto b/protos/local/core_interface.proto index 259de62ad..8a266a795 100644 --- a/protos/local/core_interface.proto +++ b/protos/local/core_interface.proto @@ -188,6 +188,7 @@ message ActivityResult { oneof status { ActivityTaskSuccess completed = 1; ActivityTaskFailure failed = 2; + ActivityTaskCancelation canceled = 3; } } From 6afa486a0e1d46592f24eb1b854dbce70120aeef Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 15 Mar 2021 01:20:01 -0700 Subject: [PATCH 10/45] State machine changes for activity completion --- protos/local/core_interface.proto | 2 +- src/machines/activity_state_machine.rs | 82 +++++++++++++---------- src/machines/test_help/workflow_driver.rs | 1 + src/machines/workflow_machines.rs | 7 -- 4 files changed, 50 insertions(+), 42 deletions(-) diff --git a/protos/local/core_interface.proto b/protos/local/core_interface.proto index 8a266a795..85d7b4a0e 100644 --- a/protos/local/core_interface.proto +++ b/protos/local/core_interface.proto @@ -183,7 +183,7 @@ message WFActivationCompletion { } } -/// Used to report activity completion to core and to resolve the activity in a workflow activtion +/// Used to report activity completion to core and to resolve the activity in a workflow activation message ActivityResult { oneof status { ActivityTaskSuccess completed = 1; diff --git a/src/machines/activity_state_machine.rs b/src/machines/activity_state_machine.rs index 9f2220b84..62100cf9a 100644 --- a/src/machines/activity_state_machine.rs +++ b/src/machines/activity_state_machine.rs @@ -1,10 +1,15 @@ use crate::machines::workflow_machines::MachineResponse; -use crate::machines::workflow_machines::MachineResponse::PushActivityJob; use crate::machines::{Cancellable, NewMachineWithCommand, WFMachinesAdapter, WFMachinesError}; -use crate::protos::coresdk::{activity_task, CancelActivity, StartActivity}; +use crate::protos::coresdk::{ + activity_result, activity_task, ActivityResult, ActivityTaskSuccess, CancelActivity, + ResolveActivity, StartActivity, +}; use crate::protos::temporal::api::command::v1::{Command, ScheduleActivityTaskCommandAttributes}; +use crate::protos::temporal::api::common::v1::Payloads; use crate::protos::temporal::api::enums::v1::{CommandType, EventType}; -use crate::protos::temporal::api::history::v1::HistoryEvent; +use crate::protos::temporal::api::history::v1::{ + history_event, ActivityTaskCompletedEventAttributes, HistoryEvent, +}; use rustfsm::{fsm, MachineError, StateMachine, TransitionResult}; use std::convert::TryFrom; @@ -12,7 +17,7 @@ use std::convert::TryFrom; fsm! { pub(super) name ActivityMachine; - command ActivityCommand; + command ActivityMachineCommand; error WFMachinesError; shared_state SharedState; @@ -27,7 +32,7 @@ fsm! { ScheduledEventRecorded --(ActivityTaskTimedOut, on_task_timed_out) --> TimedOut; ScheduledEventRecorded --(Cancel, on_canceled) --> ScheduledActivityCancelCommandCreated; - Started --(ActivityTaskCompleted, on_activity_task_completed) --> Completed; + Started --(ActivityTaskCompleted(ActivityTaskCompletedEventAttributes), on_activity_task_completed) --> Completed; Started --(ActivityTaskFailed, on_activity_task_failed) --> Failed; Started --(ActivityTaskTimedOut, on_activity_task_timed_out) --> TimedOut; Started --(Cancel, on_canceled) --> StartedActivityCancelCommandCreated; @@ -53,7 +58,7 @@ fsm! { StartedActivityCancelEventRecorded --(ActivityTaskFailed, on_activity_task_failed) --> Failed; StartedActivityCancelEventRecorded - --(ActivityTaskCompleted, on_activity_task_completed) --> Completed; + --(ActivityTaskCompleted(ActivityTaskCompletedEventAttributes), on_activity_task_completed) --> Completed; StartedActivityCancelEventRecorded --(ActivityTaskTimedOut, on_activity_task_timed_out) --> TimedOut; StartedActivityCancelEventRecorded @@ -61,9 +66,9 @@ fsm! { } #[derive(Debug, derive_more::Display)] -pub(super) enum ActivityCommand { - StartActivity, - CancelActivity, +pub(super) enum ActivityMachineCommand { + #[display(fmt = "Complete")] + Complete(Option), } #[derive(Debug, Clone, derive_more::Display)] @@ -127,7 +132,19 @@ impl TryFrom for ActivityMachineEvents { Ok(match EventType::from_i32(e.event_type) { Some(EventType::ActivityTaskScheduled) => Self::ActivityTaskScheduled, Some(EventType::ActivityTaskStarted) => Self::ActivityTaskStarted, - Some(EventType::ActivityTaskCompleted) => Self::ActivityTaskCompleted, + Some(EventType::ActivityTaskCompleted) => { + if let Some(history_event::Attributes::ActivityTaskCompletedEventAttributes( + attrs, + )) = e.attributes + { + Self::ActivityTaskCompleted(attrs) + } else { + return Err(WFMachinesError::MalformedEvent( + e, + "Activity completion attributes were unset".to_string(), + )); + } + } Some(EventType::ActivityTaskFailed) => Self::ActivityTaskFailed, Some(EventType::ActivityTaskTimedOut) => Self::ActivityTaskTimedOut, Some(EventType::ActivityTaskCancelRequested) => Self::ActivityTaskCancelRequested, @@ -159,23 +176,18 @@ impl WFMachinesAdapter for ActivityMachine { &self, event: &HistoryEvent, has_next_event: bool, - my_command: ActivityCommand, + my_command: ActivityMachineCommand, ) -> Result, WFMachinesError> { Ok(match my_command { - ActivityCommand::StartActivity => { - vec![PushActivityJob(activity_task::Variant::Start( - StartActivity { - // TODO pass activity details - }, - ))] - } - ActivityCommand::CancelActivity => { - vec![PushActivityJob(activity_task::Variant::Cancel( - CancelActivity { - // TODO pass activity cancellation details - }, - ))] + ActivityMachineCommand::Complete(result) => vec![ResolveActivity { + activity_id: self.shared_state.attrs.activity_id.clone(), + result: Some(ActivityResult { + status: Some(activity_result::Status::Completed(ActivityTaskSuccess { + result, + })), + }), } + .into()], }) } } @@ -190,13 +202,6 @@ impl Cancellable for ActivityMachine { } } -#[derive(Debug, derive_more::Display)] -pub(super) enum ActivityMachineCommand { - Complete, - Canceled, - IssueCancelCmd(Command), -} - #[derive(Default, Clone)] pub(super) struct SharedState { attrs: ScheduleActivityTaskCommandAttributes, @@ -250,9 +255,15 @@ impl ScheduledEventRecorded { pub(super) struct Started {} impl Started { - pub(super) fn on_activity_task_completed(self) -> ActivityMachineTransition { + pub(super) fn on_activity_task_completed( + self, + attrs: ActivityTaskCompletedEventAttributes, + ) -> ActivityMachineTransition { // notify_completed - ActivityMachineTransition::default::() + ActivityMachineTransition::ok( + vec![ActivityMachineCommand::Complete(attrs.result)], + Completed::default(), + ) } pub(super) fn on_activity_task_failed(self) -> ActivityMachineTransition { // notify_failed @@ -312,7 +323,10 @@ impl StartedActivityCancelCommandCreated { pub(super) struct StartedActivityCancelEventRecorded {} impl StartedActivityCancelEventRecorded { - pub(super) fn on_activity_task_completed(self) -> ActivityMachineTransition { + pub(super) fn on_activity_task_completed( + self, + attrs: ActivityTaskCompletedEventAttributes, + ) -> ActivityMachineTransition { // notify_completed ActivityMachineTransition::default::() } diff --git a/src/machines/test_help/workflow_driver.rs b/src/machines/test_help/workflow_driver.rs index 2ad551876..0e1db1d77 100644 --- a/src/machines/test_help/workflow_driver.rs +++ b/src/machines/test_help/workflow_driver.rs @@ -1,4 +1,5 @@ use crate::protos::temporal::api::command::v1::ScheduleActivityTaskCommandAttributes; +use crate::protos::temporal::api::common::v1::Payloads; use crate::{ machines::WFCommand, protos::{ diff --git a/src/machines/workflow_machines.rs b/src/machines/workflow_machines.rs index f50d870e0..4f0d8abd8 100644 --- a/src/machines/workflow_machines.rs +++ b/src/machines/workflow_machines.rs @@ -92,10 +92,6 @@ pub enum MachineResponse { #[display(fmt = "PushWFJob")] PushWFJob(#[from(forward)] wf_activation_job::Variant), - // TODO remove and use IssueNewCommand - #[display(fmt = "PushActivityJob")] - PushActivityJob(activity_task::Variant), - IssueNewCommand(ProtoCommand), #[display(fmt = "TriggerWFTaskStarted")] TriggerWFTaskStarted { @@ -521,9 +517,6 @@ impl WorkflowMachines { MachineResponse::IssueNewCommand(_) => { panic!("Issue new command machine response not expected here") } - MachineResponse::PushActivityJob(a) => { - unimplemented!() - } } } Ok(()) From 164fbac7f78a1f4be4083e183ccbef7a0e7c6aea Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 15 Mar 2021 01:20:19 -0700 Subject: [PATCH 11/45] update pattern --- src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 38ec4356e..a165e602b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,6 +27,7 @@ pub use pollers::{ }; pub use url::Url; +use crate::protos::coresdk::activity_result::Status; use crate::protos::coresdk::{activity_result, ActivityTask}; use crate::protos::temporal::api::workflowservice::v1::PollActivityTaskQueueResponse; use crate::{ @@ -285,7 +286,7 @@ where .complete_activity_task(task_token, success.result), )?; } - activity_result::Status::Failed(_) => unimplemented!(), + _ => unimplemented!(), } Ok(()) } From a561436947a6479d4a681bdec9ad97040c1064c0 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 15 Mar 2021 01:38:26 -0700 Subject: [PATCH 12/45] Add handling for failure/cancelation in completion API --- protos/local/core_interface.proto | 1 + src/lib.rs | 21 ++++++++++--- src/pollers/mod.rs | 50 +++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 4 deletions(-) diff --git a/protos/local/core_interface.proto b/protos/local/core_interface.proto index 85d7b4a0e..5f9f82792 100644 --- a/protos/local/core_interface.proto +++ b/protos/local/core_interface.proto @@ -229,6 +229,7 @@ message WFActivationFailure { /// Used in ActivityResult to report cancellation message ActivityTaskCancelation { + temporal.api.common.v1.Payloads details = 1; } /// Used in ActivityResult to report successful completion diff --git a/src/lib.rs b/src/lib.rs index a165e602b..654595560 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,7 +28,10 @@ pub use pollers::{ pub use url::Url; use crate::protos::coresdk::activity_result::Status; -use crate::protos::coresdk::{activity_result, ActivityTask}; +use crate::protos::coresdk::{ + activity_result, ActivityTask, ActivityTaskCancelation, ActivityTaskFailure, + ActivityTaskSuccess, +}; use crate::protos::temporal::api::workflowservice::v1::PollActivityTaskQueueResponse; use crate::{ machines::{InconvertibleCommandError, WFCommand, WFMachinesError}, @@ -280,13 +283,23 @@ where })), } => { match status { - activity_result::Status::Completed(success) => { + activity_result::Status::Completed(ActivityTaskSuccess { result }) => { + self.runtime.block_on( + self.server_gateway + .complete_activity_task(task_token, result), + )?; + } + activity_result::Status::Failed(ActivityTaskFailure { failure }) => { + self.runtime.block_on( + self.server_gateway.fail_activity_task(task_token, failure), + )?; + } + activity_result::Status::Canceled(ActivityTaskCancelation { details }) => { self.runtime.block_on( self.server_gateway - .complete_activity_task(task_token, success.result), + .cancel_activity_task(task_token, details), )?; } - _ => unimplemented!(), } Ok(()) } diff --git a/src/pollers/mod.rs b/src/pollers/mod.rs index 0efb1d25a..41cd99106 100644 --- a/src/pollers/mod.rs +++ b/src/pollers/mod.rs @@ -3,7 +3,9 @@ use std::time::Duration; use crate::protos::temporal::api::common::v1::{Payloads, WorkflowExecution}; use crate::protos::temporal::api::workflowservice::v1::{ PollActivityTaskQueueRequest, PollActivityTaskQueueResponse, + RespondActivityTaskCanceledRequest, RespondActivityTaskCanceledResponse, RespondActivityTaskCompletedRequest, RespondActivityTaskCompletedResponse, + RespondActivityTaskFailedRequest, RespondActivityTaskFailedResponse, SignalWorkflowExecutionRequest, SignalWorkflowExecutionResponse, }; use crate::{ @@ -125,6 +127,18 @@ pub trait ServerGatewayApis { result: Option, ) -> Result; + async fn cancel_activity_task( + &self, + task_token: Vec, + details: Option, + ) -> Result; + + async fn fail_activity_task( + &self, + task_token: Vec, + failure: Option, + ) -> Result; + /// Fail task by sending the failure to the server. `task_token` is the task token that would've /// been received from [PollWorkflowTaskQueueApi::poll]. async fn fail_workflow_task( @@ -330,4 +344,40 @@ impl ServerGatewayApis for ServerGateway { .await? .into_inner()) } + + async fn cancel_activity_task( + &self, + task_token: Vec, + details: Option, + ) -> Result { + Ok(self + .service + .clone() + .respond_activity_task_canceled(RespondActivityTaskCanceledRequest { + task_token, + details, + identity: self.opts.identity.clone(), + namespace: self.opts.namespace.clone(), + }) + .await? + .into_inner()) + } + + async fn fail_activity_task( + &self, + task_token: Vec, + failure: Option, + ) -> Result { + Ok(self + .service + .clone() + .respond_activity_task_failed(RespondActivityTaskFailedRequest { + task_token, + failure, + identity: self.opts.identity.clone(), + namespace: self.opts.namespace.clone(), + }) + .await? + .into_inner()) + } } From 1b9416e0de4c9992b5cd7acee55b617e46e1da72 Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Thu, 18 Mar 2021 08:32:03 -0700 Subject: [PATCH 13/45] Pushing to show what I have so far to Roey --- protos/local/common.proto | 61 ++++++++++++++++ protos/local/core_interface.proto | 111 +++++++++++++++--------------- 2 files changed, 117 insertions(+), 55 deletions(-) create mode 100644 protos/local/common.proto diff --git a/protos/local/common.proto b/protos/local/common.proto new file mode 100644 index 000000000..71a7f7e10 --- /dev/null +++ b/protos/local/common.proto @@ -0,0 +1,61 @@ +syntax = "proto3"; + +package coresdk.common; + +import "google/protobuf/duration.proto"; + +// Many of the messages in here are exact or near duplicates of the protobufs defined by the +// Temporal API. We dupe them here to introduce better ergonomics wherever possible, and to +// decouple ourselves from upstream changes. Additionally, we have no need for wire compatibility +// between core and lang sdks, since the lang SDK chooses which version of core it wants to use. + +// Used as arguments to activities, signals, queries, etc. +message Payload { + map metadata = 1; + bytes data = 2; +} + +// Identifying information about a particular workflow execution +message WorkflowExecution { + string workflow_id = 1; + string run_id = 2; +} + +// Defines how an activity or workflow should be retried in the event of failure, timeout, etc. +message RetryPolicy { + // Interval of the first retry. If backoff_coefficient is 1.0 then it is used for all + // retries. + google.protobuf.Duration initial_interval = 1; + // Coefficient used to calculate the next retry interval. The next retry interval is previous + // interval multiplied by the coefficient. Must be 1 or larger. + double backoff_coefficient = 2; + // Maximum interval between retries. Exponential backoff leads to interval increase. This value + // caps that interval. Default is 100x of the initial interval. + google.protobuf.Duration maximum_interval = 3; + // Maximum number of attempts. When exceeded, retrying will stop. 1 disables retries. 0 means + // unlimited retries (until the activity or workflow's total timeout is reached). + int32 maximum_attempts = 4; + // If a stringified error matches something in this list, retries will cease. + repeated string non_retryable_error_types = 5; +} + +// Represents a failure in user code, workflow or activity, which could've been triggered by +// an exception or similar error mechanism like the error half of a Result type. +// +// This eventually needs to be converted into an upstream `Failure` which needs to handle a lot +// more cases that the lang sdk does not care about. By default any lang sdk failure is an upstream +// `ApplicationFailureInfo`. +message UserCodeFailure { + // Human-specified or otherwise most-human-readable representation of the error. + string message = 1; + // A type identifier for the error, if the error is well-typed. + string type = 2; + // If known, the location the error was issued at. + string source = 3; + // If collected, a stack trace for the error. + string stack_trace = 4; + // Explicitly thrown user errors are able to indicate that retries should be prevented + bool non_retryable = 5; + + UserCodeFailure cause = 6; +} diff --git a/protos/local/core_interface.proto b/protos/local/core_interface.proto index 5f9f82792..3e3e864a5 100644 --- a/protos/local/core_interface.proto +++ b/protos/local/core_interface.proto @@ -6,18 +6,11 @@ package coresdk; // the include paths. You can make it work by going to the "Protobuf Support" settings section // and adding the "api_upstream" subdir as an include path. +import "common.proto"; + import "google/protobuf/timestamp.proto"; import "google/protobuf/duration.proto"; import "google/protobuf/empty.proto"; -import "dependencies/gogoproto/gogo.proto"; -import "temporal/api/workflowservice/v1/request_response.proto"; -import "temporal/api/taskqueue/v1/message.proto"; -import "temporal/api/enums/v1/failed_cause.proto"; -import "temporal/api/failure/v1/message.proto"; -import "temporal/api/history/v1/message.proto"; -import "temporal/api/common/v1/message.proto"; -import "temporal/api/command/v1/message.proto"; -import "temporal/api/query/v1/message.proto"; // A request as given to [crate::Core::poll_task] message PollTaskReq { @@ -50,7 +43,7 @@ message Task { // a cached state. message WFActivation { // The current time as understood by the workflow, which is set by workflow task started events - google.protobuf.Timestamp timestamp = 1 [(gogoproto.stdtime) = true]; + google.protobuf.Timestamp timestamp = 1; // The id of the currently active run of the workflow string run_id = 2; // The things to do upon activating the workflow @@ -83,8 +76,8 @@ message StartWorkflow { string workflow_type = 1; // The workflow id used on the temporal server string workflow_id = 2; - // Input to the workflow code - temporal.api.common.v1.Payloads arguments = 3; + // Inputs to the workflow code + repeated common.Payload arguments = 3; // The seed must be used to initialize the random generator used by SDK. // RandomSeedUpdatedAttributes are used to deliver seed updates. uint64 randomness_seed = 4; @@ -111,7 +104,8 @@ message UpdateRandomSeed { } message QueryWorkflow { - temporal.api.query.v1.WorkflowQuery query = 1; + string query_type = 1; + repeated common.Payload arguments = 2; } message CancelWorkflow { @@ -119,38 +113,9 @@ message CancelWorkflow { } message SignalWorkflow { - // The signal information from the workflow's history - temporal.api.history.v1.WorkflowExecutionSignaledEventAttributes signal = 1; -} - -message StartActivity { - string workflow_namespace = 1; - temporal.api.common.v1.WorkflowType workflow_type = 2; - temporal.api.common.v1.WorkflowExecution workflow_execution = 3; - temporal.api.common.v1.ActivityType activity_type = 4; - temporal.api.common.v1.Header header = 5; - temporal.api.common.v1.Payloads input = 6; - temporal.api.common.v1.Payloads heartbeat_details = 7; - google.protobuf.Timestamp scheduled_time = 8 [(gogoproto.stdtime) = true]; - google.protobuf.Timestamp current_attempt_scheduled_time = 9 [(gogoproto.stdtime) = true]; - google.protobuf.Timestamp started_time = 10 [(gogoproto.stdtime) = true]; - int32 attempt = 11; - // (-- api-linter: core::0140::prepositions=disabled - // aip.dev/not-precedent: "to" is used to indicate interval. --) - google.protobuf.Duration schedule_to_close_timeout = 12 [(gogoproto.stdduration) = true]; - // (-- api-linter: core::0140::prepositions=disabled - // aip.dev/not-precedent: "to" is used to indicate interval. --) - google.protobuf.Duration start_to_close_timeout = 13 [(gogoproto.stdduration) = true]; - google.protobuf.Duration heartbeat_timeout = 14 [(gogoproto.stdduration) = true]; - // This is an actual retry policy the service uses. - // It can be different from the one provided (or not) during activity scheduling - // as the service can override the provided one in case its values are not specified - // or exceed configured system limits. - temporal.api.common.v1.RetryPolicy retry_policy = 15; -} - -message CancelActivity { - // TODO: add attributes + string signal_name = 1; + repeated common.Payload input = 2; + string identity = 3; } message ActivityTask { @@ -163,6 +128,36 @@ message ActivityTask { } } +message StartActivity { + string workflow_namespace = 1; + // The workflow's type name or function identifier + string workflow_type = 2; + common.WorkflowExecution workflow_execution = 3; + // The activity's type name or function identifier + string activity_type = 4; + map header_fields = 5; + // Arguments to the activity + repeated common.Payload input = 6; + repeated common.Payload heartbeat_details = 7; + + google.protobuf.Timestamp scheduled_time = 8; + google.protobuf.Timestamp current_attempt_scheduled_time = 9; + google.protobuf.Timestamp started_time = 10; + int32 attempt = 11; + + google.protobuf.Duration schedule_to_close_timeout = 12; + google.protobuf.Duration start_to_close_timeout = 13; + google.protobuf.Duration heartbeat_timeout = 14; + // This is an actual retry policy the service uses. It can be different from the one provided + // (or not) during activity scheduling as the service can override the provided one in case its + // values are not specified or exceed configured system limits. + common.RetryPolicy retry_policy = 15; +} + +message CancelActivity { + // TODO: add attributes +} + // Sent from lang side to core when calling [crate::Core::complete_task] message TaskCompletion { @@ -200,7 +195,7 @@ message RequestActivityCancellation { message CoreCommand { oneof variant { - temporal.api.query.v1.WorkflowQueryResult respond_to_query = 1; + WFQueryResult respond_to_query = 1; RequestActivityCancellation request_activity_cancellation = 2; } } @@ -216,36 +211,42 @@ message Command { message WFActivationSuccess { // A list of commands to send back to the temporal server repeated Command commands = 1; - - // Other bits from RespondWorkflowTaskCompletedRequest as needed } message WFActivationFailure { - temporal.api.enums.v1.WorkflowTaskFailedCause cause = 1; - temporal.api.failure.v1.Failure failure = 2; + common.UserCodeFailure failure = 1; +} + +message WFQueryResult { + oneof variant { + WFQuerySuccess succeeded = 1; + string failed_with_message = 2; + } +} - // Other bits from RespondWorkflowTaskFailedRequest as needed +message WFQuerySuccess { + repeated common.Payload responses = 1; } /// Used in ActivityResult to report cancellation message ActivityTaskCancelation { - temporal.api.common.v1.Payloads details = 1; + repeated common.Payload details = 1; } /// Used in ActivityResult to report successful completion message ActivityTaskSuccess { - temporal.api.common.v1.Payloads result = 1; + repeated common.Payload result = 1; // Other bits from RespondActivityTaskCompletedRequest as needed } /// Used in ActivityResult to report failure message ActivityTaskFailure { - temporal.api.failure.v1.Failure failure = 1; + repeated common.Payload failure = 1; // Other bits from RespondActivityTaskFailedRequest as needed } // A request as given to [crate::Core::send_activity_heartbeat] message ActivityHeartbeat { string activity_id = 1; - temporal.api.common.v1.Payloads details = 2; + repeated common.Payload details = 2; } From 4a395ab7fdb28d73219c50bc34e4fb5380d3c3f7 Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Thu, 18 Mar 2021 11:17:30 -0700 Subject: [PATCH 14/45] Push in progress commands for Roey --- protos/local/core_interface.proto | 36 +----------- protos/local/workflow_commands.proto | 87 ++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 34 deletions(-) create mode 100644 protos/local/workflow_commands.proto diff --git a/protos/local/core_interface.proto b/protos/local/core_interface.proto index 3e3e864a5..bbe95f63b 100644 --- a/protos/local/core_interface.proto +++ b/protos/local/core_interface.proto @@ -7,6 +7,7 @@ package coresdk; // and adding the "api_upstream" subdir as an include path. import "common.proto"; +import "workflow_commands.proto"; import "google/protobuf/timestamp.proto"; import "google/protobuf/duration.proto"; @@ -56,8 +57,6 @@ message WFActivationJob { StartWorkflow start_workflow = 1; // A timer has fired, allowing whatever was waiting on it (if anything) to proceed FireTimer fire_timer = 2; - // A timer was canceled, and needs to be unblocked on the lang side. - CancelTimer cancel_timer = 3; // Workflow was reset. The randomness seed must be updated. UpdateRandomSeed update_random_seed = 4; // A request to query the workflow was received. @@ -95,10 +94,6 @@ message ResolveActivity { ActivityResult result = 2; } -message CancelTimer { - string timer_id = 1; -} - message UpdateRandomSeed { uint64 randomness_seed = 1; } @@ -187,47 +182,20 @@ message ActivityResult { } } -/// Request cancellation of an activity from a workflow -message RequestActivityCancellation { - string activity_id = 1; - string reason = 2; -} - message CoreCommand { oneof variant { - WFQueryResult respond_to_query = 1; - RequestActivityCancellation request_activity_cancellation = 2; - } -} - -// Included in successful [WfActivationCompletion]s, indicates what the workflow wishes to do next -message Command { - oneof variant { - temporal.api.command.v1.Command api = 1; - CoreCommand core = 2; } } message WFActivationSuccess { // A list of commands to send back to the temporal server - repeated Command commands = 1; + repeated workflow_commands.WorkflowCommand commands = 1; } message WFActivationFailure { common.UserCodeFailure failure = 1; } -message WFQueryResult { - oneof variant { - WFQuerySuccess succeeded = 1; - string failed_with_message = 2; - } -} - -message WFQuerySuccess { - repeated common.Payload responses = 1; -} - /// Used in ActivityResult to report cancellation message ActivityTaskCancelation { repeated common.Payload details = 1; diff --git a/protos/local/workflow_commands.proto b/protos/local/workflow_commands.proto new file mode 100644 index 000000000..031c81d46 --- /dev/null +++ b/protos/local/workflow_commands.proto @@ -0,0 +1,87 @@ +syntax = "proto3"; + +package coresdk.workflow_commands; + +import "common.proto"; + +message WorkflowCommand { + oneof variant { + StartTimer start_timer = 1; + ScheduleActivity schedule_activity = 2; + QueryResult respond_to_query = 3; + RequestActivityCancellation request_activity_cancellation = 4; + CompleteWorkflowExecution complete_workflow_execution = 5; + FailWorkflowExecution fail_workflow_execution = 6; + + // To be added as/if needed: + // RequestCancelActivityTask request_cancel_activity_task_command_attributes = 6; + // CancelWorkflowExecution cancel_workflow_execution_command_attributes = 8; + // RequestCancelExternalWorkflowExecution request_cancel_external_workflow_execution_command_attributes = 9; + // RecordMarker record_marker_command_attributes = 10; + // ContinueAsNewWorkflowExecution continue_as_new_workflow_execution_command_attributes = 11; + // StartChildWorkflowExecution start_child_workflow_execution_command_attributes = 12; + // SignalExternalWorkflowExecution signal_external_workflow_execution_command_attributes = 13; + // UpsertWorkflowSearchAttributes upsert_workflow_search_attributes_command_attributes = 14; + } +} + +message StartTimer { + string timer_id = 1; + google.protobuf.Duration start_to_fire_timeout = 2; +} + +message ScheduleActivity { + string activity_id = 1; + string activity_type = 2; + string namespace = 3; + // The name of the task queue to place this activity request in + // TODO: Do we care about sticky/not sticky? + string task_queue = 4; + map header_fields = 5; + repeated common.Payload input = 6; + // Indicates how long the caller is willing to wait for an activity completion. Limits how long + // retries will be attempted. Either this or start_to_close_timeout_seconds must be specified. + // When not specified defaults to the workflow execution timeout. + google.protobuf.Duration schedule_to_close_timeout = 7; + // Limits time an activity task can stay in a task queue before a worker picks it up. This + // timeout is always non retryable as all a retry would achieve is to put it back into the same + // queue. Defaults to schedule_to_close_timeout or workflow execution timeout if not specified. + google.protobuf.Duration schedule_to_start_timeout = 8; + // Maximum time an activity is allowed to execute after a pick up by a worker. This timeout is + // always retryable. Either this or schedule_to_close_timeout must be specified. + // TODO: Is this really either or can you do both? Make oneof if mutually exclusive + google.protobuf.Duration start_to_close_timeout = 9; + // Maximum time allowed between successful worker heartbeats. + google.protobuf.Duration heartbeat_timeout = 10; + // Activities are provided by a default retry policy controlled through the service dynamic + // configuration. Retries are happening up to schedule_to_close_timeout. To disable retries set + // retry_policy.maximum_attempts to 1. + common.RetryPolicy retry_policy = 11; +} + +message QueryResult { + oneof variant { + QuerySuccess succeeded = 1; + string failed_with_message = 2; + } +} + +message QuerySuccess { + repeated common.Payload responses = 1; +} + +/// Request cancellation of an activity from a workflow +message RequestActivityCancellation { + string activity_id = 1; + string reason = 2; +} + +/// Issued when the workflow completes successfully +message CompleteWorkflowExecution { + repeated common.Payload result = 1; +} + +/// Issued when the workflow errors out +message FailWorkflowExecution { + +} \ No newline at end of file From f33dce750500795396e9617707b77baf0f7a5abb Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Thu, 18 Mar 2021 13:41:32 -0700 Subject: [PATCH 15/45] PR feedback -- still need to now fix all the Rust code. --- protos/local/activity_result.proto | 30 +++++ protos/local/activity_task.proto | 55 ++++++++ protos/local/core_interface.proto | 180 ++----------------------- protos/local/workflow_activation.proto | 93 +++++++++++++ protos/local/workflow_commands.proto | 40 +++--- protos/local/workflow_completion.proto | 26 ++++ 6 files changed, 239 insertions(+), 185 deletions(-) create mode 100644 protos/local/activity_result.proto create mode 100644 protos/local/activity_task.proto create mode 100644 protos/local/workflow_activation.proto create mode 100644 protos/local/workflow_completion.proto diff --git a/protos/local/activity_result.proto b/protos/local/activity_result.proto new file mode 100644 index 000000000..59aa21d15 --- /dev/null +++ b/protos/local/activity_result.proto @@ -0,0 +1,30 @@ +syntax = "proto3"; + +package coresdk.activity_result; + +import "common.proto"; + +/// Used to report activity completion to core and to resolve the activity in a workflow activation +message ActivityResult { + oneof status { + Success completed = 1; + Failure failed = 2; + Cancelation canceled = 3; + } +} + +/// Used in ActivityResult to report cancellation +message Cancelation { + repeated common.Payload details = 1; +} + +/// Used in ActivityResult to report successful completion +message Success { + repeated common.Payload result = 1; +} + +/// Used in ActivityResult to report failure +message Failure { + common.UserCodeFailure failure = 1; +} + diff --git a/protos/local/activity_task.proto b/protos/local/activity_task.proto new file mode 100644 index 000000000..73625455f --- /dev/null +++ b/protos/local/activity_task.proto @@ -0,0 +1,55 @@ +syntax = "proto3"; + +/** + * Definitions of the different activity tasks returned from [crate::Core::poll_task]. + */ +package coresdk.activity_task; + +import "common.proto"; + +import "google/protobuf/timestamp.proto"; +import "google/protobuf/duration.proto"; + +message ActivityTask { + string activity_id = 1; + oneof variant { + // Start activity execution. + Start start = 2; + // Attempt to cancel activity execution. + Cancel cancel = 3; + } +} + +/// Begin executing an activity +message Start { + string workflow_namespace = 1; + /// The workflow's type name or function identifier + string workflow_type = 2; + common.WorkflowExecution workflow_execution = 3; + /// The activity's type name or function identifier + string activity_type = 4; + map header_fields = 5; + /// Arguments to the activity + repeated common.Payload input = 6; + repeated common.Payload heartbeat_details = 7; + + google.protobuf.Timestamp scheduled_time = 8; + google.protobuf.Timestamp current_attempt_scheduled_time = 9; + google.protobuf.Timestamp started_time = 10; + int32 attempt = 11; + + google.protobuf.Duration schedule_to_close_timeout = 12; + google.protobuf.Duration start_to_close_timeout = 13; + google.protobuf.Duration heartbeat_timeout = 14; + /// This is an actual retry policy the service uses. It can be different from the one provided + /// (or not) during activity scheduling as the service can override the provided one in case its + /// values are not specified or exceed configured system limits. + common.RetryPolicy retry_policy = 15; +} + +/// Attempt to cancel a running activity +message Cancel { + // TODO: add attributes +} + + diff --git a/protos/local/core_interface.proto b/protos/local/core_interface.proto index bbe95f63b..c80c3ae26 100644 --- a/protos/local/core_interface.proto +++ b/protos/local/core_interface.proto @@ -2,13 +2,16 @@ syntax = "proto3"; package coresdk; -// Note: Intellij will think these imports don't work because of the slightly odd nature of -// the include paths. You can make it work by going to the "Protobuf Support" settings section -// and adding the "api_upstream" subdir as an include path. - +import "activity_result.proto"; +import "activity_task.proto"; import "common.proto"; +import "workflow_activation.proto"; import "workflow_commands.proto"; +import "workflow_completion.proto"; +// Note: Intellij will think these imports don't work because of the slightly odd nature of +// the include paths. You can make it work by going to the "Protobuf Support" settings section +// and adding the "api_upstream" subdir as an include path. import "google/protobuf/timestamp.proto"; import "google/protobuf/duration.proto"; import "google/protobuf/empty.proto"; @@ -34,185 +37,24 @@ message Task { // The type of task to be performed oneof variant { // Wake up a workflow - WFActivation workflow = 2; + workflow_activation.WFActivation workflow = 2; // Run an activity - ActivityTask activity = 3; - } -} - -// An instruction to the lang sdk to run some workflow code, whether for the first time or from -// a cached state. -message WFActivation { - // The current time as understood by the workflow, which is set by workflow task started events - google.protobuf.Timestamp timestamp = 1; - // The id of the currently active run of the workflow - string run_id = 2; - // The things to do upon activating the workflow - repeated WFActivationJob jobs = 3; -} - -message WFActivationJob { - oneof variant { - // Begin a workflow for the first time - StartWorkflow start_workflow = 1; - // A timer has fired, allowing whatever was waiting on it (if anything) to proceed - FireTimer fire_timer = 2; - // Workflow was reset. The randomness seed must be updated. - UpdateRandomSeed update_random_seed = 4; - // A request to query the workflow was received. - QueryWorkflow query_workflow = 5; - // A request to cancel the workflow was received. - CancelWorkflow cancel_workflow = 6; - // A request to signal the workflow was received. - SignalWorkflow signal_workflow = 7; - // An activity was resolved with, result could be completed, failed or cancelled - ResolveActivity resolve_activity = 8; + activity_task.ActivityTask activity = 3; } } -message StartWorkflow { - // The identifier the lang-specific sdk uses to execute workflow code - string workflow_type = 1; - // The workflow id used on the temporal server - string workflow_id = 2; - // Inputs to the workflow code - repeated common.Payload arguments = 3; - // The seed must be used to initialize the random generator used by SDK. - // RandomSeedUpdatedAttributes are used to deliver seed updates. - uint64 randomness_seed = 4; - - // TODO: Do we need namespace here, or should that just be fetchable easily? - // will be others - workflow exe started attrs, etc -} - -message FireTimer { - string timer_id = 1; -} - -message ResolveActivity { - string activity_id = 1; - ActivityResult result = 2; -} - -message UpdateRandomSeed { - uint64 randomness_seed = 1; -} - -message QueryWorkflow { - string query_type = 1; - repeated common.Payload arguments = 2; -} - -message CancelWorkflow { - // TODO: add attributes here -} - -message SignalWorkflow { - string signal_name = 1; - repeated common.Payload input = 2; - string identity = 3; -} - -message ActivityTask { - string activity_id = 1; - oneof variant { - // Start activity execution. - StartActivity start = 2; - // Attempt to cancel activity execution. - CancelActivity cancel = 3; - } -} - -message StartActivity { - string workflow_namespace = 1; - // The workflow's type name or function identifier - string workflow_type = 2; - common.WorkflowExecution workflow_execution = 3; - // The activity's type name or function identifier - string activity_type = 4; - map header_fields = 5; - // Arguments to the activity - repeated common.Payload input = 6; - repeated common.Payload heartbeat_details = 7; - - google.protobuf.Timestamp scheduled_time = 8; - google.protobuf.Timestamp current_attempt_scheduled_time = 9; - google.protobuf.Timestamp started_time = 10; - int32 attempt = 11; - - google.protobuf.Duration schedule_to_close_timeout = 12; - google.protobuf.Duration start_to_close_timeout = 13; - google.protobuf.Duration heartbeat_timeout = 14; - // This is an actual retry policy the service uses. It can be different from the one provided - // (or not) during activity scheduling as the service can override the provided one in case its - // values are not specified or exceed configured system limits. - common.RetryPolicy retry_policy = 15; -} - -message CancelActivity { - // TODO: add attributes -} - - // Sent from lang side to core when calling [crate::Core::complete_task] message TaskCompletion { // The id from the [Task] being completed bytes task_token = 1; oneof variant { // Complete a workflow task - WFActivationCompletion workflow = 2; + workflow_completion.WFActivationCompletion workflow = 2; // Complete an activity task - ActivityResult activity = 3; - } -} - -message WFActivationCompletion { - oneof status { - WFActivationSuccess successful = 1; - WFActivationFailure failed = 2; - } -} - -/// Used to report activity completion to core and to resolve the activity in a workflow activation -message ActivityResult { - oneof status { - ActivityTaskSuccess completed = 1; - ActivityTaskFailure failed = 2; - ActivityTaskCancelation canceled = 3; - } -} - -message CoreCommand { - oneof variant { + activity_result.ActivityResult activity = 3; } } -message WFActivationSuccess { - // A list of commands to send back to the temporal server - repeated workflow_commands.WorkflowCommand commands = 1; -} - -message WFActivationFailure { - common.UserCodeFailure failure = 1; -} - -/// Used in ActivityResult to report cancellation -message ActivityTaskCancelation { - repeated common.Payload details = 1; -} - -/// Used in ActivityResult to report successful completion -message ActivityTaskSuccess { - repeated common.Payload result = 1; - // Other bits from RespondActivityTaskCompletedRequest as needed -} - -/// Used in ActivityResult to report failure -message ActivityTaskFailure { - repeated common.Payload failure = 1; - // Other bits from RespondActivityTaskFailedRequest as needed -} - // A request as given to [crate::Core::send_activity_heartbeat] message ActivityHeartbeat { string activity_id = 1; diff --git a/protos/local/workflow_activation.proto b/protos/local/workflow_activation.proto new file mode 100644 index 000000000..97fbb4d1a --- /dev/null +++ b/protos/local/workflow_activation.proto @@ -0,0 +1,93 @@ +syntax = "proto3"; + +/** + * Definitions of the different workflow activation jobs returned from [crate::Core::poll_task]. The + * lang SDK applies these activation jobs to drive workflows. + */ +package coresdk.workflow_activation; + +import "common.proto"; +import "activity_result.proto"; + +import "google/protobuf/timestamp.proto"; + +/// An instruction to the lang sdk to run some workflow code, whether for the first time or from +/// a cached state. +message WFActivation { + /// The current time as understood by the workflow, which is set by workflow task started events + google.protobuf.Timestamp timestamp = 1; + /// The id of the currently active run of the workflow + string run_id = 2; + /// The things to do upon activating the workflow + repeated WFActivationJob jobs = 3; +} + +message WFActivationJob { + oneof variant { + /// Begin a workflow for the first time + StartWorkflow start_workflow = 1; + /// A timer has fired, allowing whatever was waiting on it (if anything) to proceed + FireTimer fire_timer = 2; + /// Workflow was reset. The randomness seed must be updated. + UpdateRandomSeed update_random_seed = 4; + /// A request to query the workflow was received. + QueryWorkflow query_workflow = 5; + /// A request to cancel the workflow was received. + CancelWorkflow cancel_workflow = 6; + /// A request to signal the workflow was received. + SignalWorkflow signal_workflow = 7; + /// An activity was resolved with, result could be completed, failed or cancelled + ResolveActivity resolve_activity = 8; + } +} + +/// Start a new workflow +message StartWorkflow { + /// The identifier the lang-specific sdk uses to execute workflow code + string workflow_type = 1; + /// The workflow id used on the temporal server + string workflow_id = 2; + /// Inputs to the workflow code + repeated common.Payload arguments = 3; + /// The seed must be used to initialize the random generator used by SDK. + /// RandomSeedUpdatedAttributes are used to deliver seed updates. + uint64 randomness_seed = 4; + + // TODO: Do we need namespace here, or should that just be fetchable easily? + // will be others - workflow exe started attrs, etc +} + +/// Notify a workflow that a timer has fired +message FireTimer { + string timer_id = 1; +} + +/// Notify a workflow that an activity has been resolved +message ResolveActivity { + string activity_id = 1; + activity_result.ActivityResult result = 2; +} + +/// Update the workflow's random seed +message UpdateRandomSeed { + uint64 randomness_seed = 1; +} + +/// Query a workflow +message QueryWorkflow { + string query_type = 1; + repeated common.Payload arguments = 2; +} + +/// Cancel a running workflow +message CancelWorkflow { + // TODO: add attributes here +} + +/// Send a signal to a workflow +message SignalWorkflow { + string signal_name = 1; + repeated common.Payload input = 2; + string identity = 3; +} + diff --git a/protos/local/workflow_commands.proto b/protos/local/workflow_commands.proto index 031c81d46..596f9921a 100644 --- a/protos/local/workflow_commands.proto +++ b/protos/local/workflow_commands.proto @@ -1,9 +1,16 @@ syntax = "proto3"; +/** + * Definitions for commands from a workflow in lang SDK to core. While a workflow processes a batch + * of activation jobs, it accumulates these commands to be sent back to core to conclude that + * activation. + */ package coresdk.workflow_commands; import "common.proto"; +import "google/protobuf/duration.proto"; + message WorkflowCommand { oneof variant { StartTimer start_timer = 1; @@ -38,24 +45,25 @@ message ScheduleActivity { // TODO: Do we care about sticky/not sticky? string task_queue = 4; map header_fields = 5; - repeated common.Payload input = 6; - // Indicates how long the caller is willing to wait for an activity completion. Limits how long - // retries will be attempted. Either this or start_to_close_timeout_seconds must be specified. - // When not specified defaults to the workflow execution timeout. + /// Arguments/input to the activity. Called "input" upstream. + repeated common.Payload arguments = 6; + /// Indicates how long the caller is willing to wait for an activity completion. Limits how long + /// retries will be attempted. Either this or start_to_close_timeout_seconds must be specified. + /// When not specified defaults to the workflow execution timeout. google.protobuf.Duration schedule_to_close_timeout = 7; - // Limits time an activity task can stay in a task queue before a worker picks it up. This - // timeout is always non retryable as all a retry would achieve is to put it back into the same - // queue. Defaults to schedule_to_close_timeout or workflow execution timeout if not specified. + /// Limits time an activity task can stay in a task queue before a worker picks it up. This + /// timeout is always non retryable as all a retry would achieve is to put it back into the same + /// queue. Defaults to schedule_to_close_timeout or workflow execution timeout if not specified. google.protobuf.Duration schedule_to_start_timeout = 8; - // Maximum time an activity is allowed to execute after a pick up by a worker. This timeout is - // always retryable. Either this or schedule_to_close_timeout must be specified. - // TODO: Is this really either or can you do both? Make oneof if mutually exclusive + /// Maximum time an activity is allowed to execute after a pick up by a worker. This timeout is + /// always retryable. Either this or schedule_to_close_timeout must be specified. + /// TODO: Is this really either or can you do both? Make oneof if mutually exclusive google.protobuf.Duration start_to_close_timeout = 9; - // Maximum time allowed between successful worker heartbeats. + /// Maximum time allowed between successful worker heartbeats. google.protobuf.Duration heartbeat_timeout = 10; - // Activities are provided by a default retry policy controlled through the service dynamic - // configuration. Retries are happening up to schedule_to_close_timeout. To disable retries set - // retry_policy.maximum_attempts to 1. + /// Activities are provided by a default retry policy controlled through the service dynamic + /// configuration. Retries are happening up to schedule_to_close_timeout. To disable retries set + /// retry_policy.maximum_attempts to 1. common.RetryPolicy retry_policy = 11; } @@ -67,7 +75,7 @@ message QueryResult { } message QuerySuccess { - repeated common.Payload responses = 1; + common.Payload response = 1; } /// Request cancellation of an activity from a workflow @@ -83,5 +91,5 @@ message CompleteWorkflowExecution { /// Issued when the workflow errors out message FailWorkflowExecution { - + common.UserCodeFailure failure = 1; } \ No newline at end of file diff --git a/protos/local/workflow_completion.proto b/protos/local/workflow_completion.proto new file mode 100644 index 000000000..02aefee6c --- /dev/null +++ b/protos/local/workflow_completion.proto @@ -0,0 +1,26 @@ +syntax = "proto3"; + +package coresdk.workflow_completion; + +import "common.proto"; +import "workflow_commands.proto"; + +/// Result of a single workflow activation, reported from lang to core +message WFActivationCompletion { + oneof status { + Success successful = 1; + Failure failed = 2; + } +} + +/// Successful workflow activation with a list of commands generated by the workflow execution +message Success { + // A list of commands to send back to the temporal server + repeated workflow_commands.WorkflowCommand commands = 1; +} + +/// Failure to activate or execute a workflow +message Failure { + common.UserCodeFailure failure = 1; +} + From de0b9b06641c3f3a5848a93a9e9113e391eb393d Mon Sep 17 00:00:00 2001 From: Vitaly Date: Sun, 7 Mar 2021 18:38:11 -0800 Subject: [PATCH 16/45] Basic boilerplate and a failing test --- src/machines/activity_state_machine.rs | 80 +++++++++++++++++++++++++- src/test_help/canned_histories.rs | 52 ++++++++++++++++- 2 files changed, 129 insertions(+), 3 deletions(-) diff --git a/src/machines/activity_state_machine.rs b/src/machines/activity_state_machine.rs index 23efd857d..7d5c1b714 100644 --- a/src/machines/activity_state_machine.rs +++ b/src/machines/activity_state_machine.rs @@ -1,3 +1,4 @@ +use crate::protos::temporal::api::command::v1::ScheduleActivityTaskCommandAttributes; use rustfsm::{fsm, TransitionResult}; // Schedule / cancel are "explicit events" (imperative rather than past events?) @@ -54,6 +55,36 @@ pub(super) enum ActivityMachineError {} pub(super) enum ActivityCommand {} +#[derive(Debug, Clone, derive_more::Display)] +pub(super) enum ActivityCancellationType { + /** + * Wait for activity cancellation completion. Note that activity must heartbeat to receive a + * cancellation notification. This can block the cancellation for a long time if activity doesn't + * heartbeat or chooses to ignore the cancellation request. + */ + WaitCancellationCompleted, + + /** Initiate a cancellation request and immediately report cancellation to the workflow. */ + TryCancel, + + /** + * Do not request cancellation of the activity and immediately report cancellation to the workflow + */ + Abandon, +} + +impl Default for ActivityCancellationType { + fn default() -> Self { + ActivityCancellationType::TryCancel + } +} + +#[derive(Default, Clone)] +pub(super) struct SharedState { + attrs: ScheduleActivityTaskCommandAttributes, + cancellation_type: ActivityCancellationType, +} + #[derive(Default, Clone)] pub(super) struct Created {} @@ -201,6 +232,51 @@ pub(super) struct Canceled {} #[cfg(test)] mod activity_machine_tests { - #[test] - fn test() {} + use crate::machines::test_help::{CommandSender, TestHistoryBuilder, TestWorkflowDriver}; + use crate::machines::WorkflowMachines; + use crate::protos::temporal::api::command::v1::CompleteWorkflowExecutionCommandAttributes; + use crate::protos::temporal::api::command::v1::ScheduleActivityTaskCommandAttributes; + use crate::protos::temporal::api::enums::v1::CommandType; + use crate::test_help::canned_histories; + use rstest::{fixture, rstest}; + use tracing::Level; + + #[fixture] + fn activity_happy_hist() -> (TestHistoryBuilder, WorkflowMachines) { + let twd = TestWorkflowDriver::new(|mut command_sink: CommandSender| async move { + let activity = ScheduleActivityTaskCommandAttributes { + ..Default::default() + }; + command_sink.activity(activity); + + let complete = CompleteWorkflowExecutionCommandAttributes::default(); + command_sink.send(complete.into()); + }); + + let t = canned_histories::single_activity("activity1"); + let state_machines = WorkflowMachines::new( + "wfid".to_string(), + "runid".to_string(), + Box::new(twd).into(), + ); + + assert_eq!(2, t.as_history().get_workflow_task_count(None).unwrap()); + (t, state_machines) + } + + #[rstest] + fn test_activity_happy_path(activity_happy_hist: (TestHistoryBuilder, WorkflowMachines)) { + let s = span!(Level::DEBUG, "Test start", t = "activity_happy_path"); + let _enter = s.enter(); + let (t, mut state_machines) = activity_happy_hist; + let commands = t + .handle_workflow_task_take_cmds(&mut state_machines, Some(1)) + .unwrap(); + state_machines.get_wf_activation(); + assert_eq!(commands.len(), 1); + assert_eq!( + commands[0].command_type, + CommandType::ScheduleActivityTask as i32 + ); + } } diff --git a/src/test_help/canned_histories.rs b/src/test_help/canned_histories.rs index 9ac08c3ec..ed8df16c1 100644 --- a/src/test_help/canned_histories.rs +++ b/src/test_help/canned_histories.rs @@ -3,7 +3,8 @@ use crate::protos::temporal::api::common::v1::Payload; use crate::protos::temporal::api::enums::v1::{EventType, WorkflowTaskFailedCause}; use crate::protos::temporal::api::failure::v1::Failure; use crate::protos::temporal::api::history::v1::{ - history_event, TimerCanceledEventAttributes, TimerFiredEventAttributes, + history_event, ActivityTaskCompletedEventAttributes, ActivityTaskScheduledEventAttributes, + ActivityTaskStartedEventAttributes, TimerCanceledEventAttributes, TimerFiredEventAttributes, }; /// 1: EVENT_TYPE_WORKFLOW_EXECUTION_STARTED @@ -152,6 +153,55 @@ pub fn workflow_fails_with_failure_after_timer(timer_id: &str) -> TestHistoryBui t } +/// 1: EVENT_TYPE_WORKFLOW_EXECUTION_STARTED +/// 2: EVENT_TYPE_WORKFLOW_TASK_SCHEDULED +/// 3: EVENT_TYPE_WORKFLOW_TASK_STARTED +/// 4: EVENT_TYPE_WORKFLOW_TASK_COMPLETED +/// 5: EVENT_TYPE_ACTIVITY_TASK_SCHEDULED +/// 6: EVENT_TYPE_ACTIVITY_TASK_STARTED +/// 7: EVENT_TYPE_ACTIVITY_TASK_COMPLETED +/// 8: EVENT_TYPE_WORKFLOW_TASK_SCHEDULED +/// 9: EVENT_TYPE_WORKFLOW_TASK_STARTED +pub fn single_activity(activity_id: &str) -> TestHistoryBuilder { + let mut t = TestHistoryBuilder::default(); + t.add_by_type(EventType::WorkflowExecutionStarted); + t.add_full_wf_task(); + let scheduled_event_id = t.add_get_event_id( + EventType::ActivityTaskScheduled, + Some( + history_event::Attributes::ActivityTaskScheduledEventAttributes( + ActivityTaskScheduledEventAttributes { + activity_id: activity_id.to_string(), + ..Default::default() + }, + ), + ), + ); + let started_event_id = t.add_get_event_id( + EventType::ActivityTaskStarted, + Some( + history_event::Attributes::ActivityTaskStartedEventAttributes( + ActivityTaskStartedEventAttributes { + scheduled_event_id, + ..Default::default() + }, + ), + ), + ); + t.add( + EventType::ActivityTaskCompleted, + history_event::Attributes::ActivityTaskCompletedEventAttributes( + ActivityTaskCompletedEventAttributes { + scheduled_event_id, + started_event_id, + ..Default::default() + }, + ), + ); + t.add_workflow_task_scheduled_and_started(); + t +} + /// First signal's payload is "hello " and second is "world" (no metadata for either) /// 1: EVENT_TYPE_WORKFLOW_EXECUTION_STARTED /// 2: EVENT_TYPE_WORKFLOW_TASK_SCHEDULED From 2baa39e96f4a3377a3b0321fd61722e4b578207b Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 8 Mar 2021 11:58:59 -0800 Subject: [PATCH 17/45] recent progress --- src/machines/activity_state_machine.rs | 82 ++++++++++++++++++++++++-- src/machines/mod.rs | 5 +- src/machines/workflow_machines.rs | 10 ++++ 3 files changed, 90 insertions(+), 7 deletions(-) diff --git a/src/machines/activity_state_machine.rs b/src/machines/activity_state_machine.rs index 7d5c1b714..cbe98be01 100644 --- a/src/machines/activity_state_machine.rs +++ b/src/machines/activity_state_machine.rs @@ -1,10 +1,15 @@ -use crate::protos::temporal::api::command::v1::ScheduleActivityTaskCommandAttributes; -use rustfsm::{fsm, TransitionResult}; +use crate::machines::workflow_machines::MachineResponse; +use crate::machines::{Cancellable, NewMachineWithCommand, WFMachinesAdapter, WFMachinesError}; +use crate::protos::temporal::api::command::v1::{Command, ScheduleActivityTaskCommandAttributes}; +use crate::protos::temporal::api::enums::v1::CommandType; +use crate::protos::temporal::api::history::v1::HistoryEvent; +use rustfsm::{fsm, MachineError, StateMachine, TransitionResult}; +use std::convert::TryFrom; // Schedule / cancel are "explicit events" (imperative rather than past events?) fsm! { - pub(super) name ActivityMachine; command ActivityCommand; error ActivityMachineError; + pub(super) name ActivityMachine; command ActivityCommand; error WFMachinesError; Created --(Schedule, on_schedule)--> ScheduleCommandCreated; @@ -50,9 +55,6 @@ fsm! { --(ActivityTaskCanceled, on_activity_task_canceled) --> Canceled; } -#[derive(thiserror::Error, Debug)] -pub(super) enum ActivityMachineError {} - pub(super) enum ActivityCommand {} #[derive(Debug, Clone, derive_more::Display)] @@ -79,6 +81,74 @@ impl Default for ActivityCancellationType { } } +/// Creates a new, scheduled, activity as a [CancellableCommand] +pub(super) fn new_activity( + attribs: ScheduleActivityTaskCommandAttributes, +) -> NewMachineWithCommand { + let (activity, add_cmd) = ActivityMachine::new_scheduled(attribs); + NewMachineWithCommand { + command: add_cmd, + machine: activity, + } +} + +impl ActivityMachine { + pub(crate) fn new_scheduled(attribs: ScheduleActivityTaskCommandAttributes) -> (Self, Command) { + let mut s = Self::new(attribs); + s.on_event_mut(ActivityMachine::Schedule) + .expect("Scheduling activities doesn't fail"); + let cmd = Command { + command_type: CommandType::ScheduleActivityTask as i32, + attributes: Some(s.shared_state().attrs.clone().into()), + }; + (s, cmd) + } +} + +impl TryFrom for ActivityMachine { + type Error = WFMachinesError; + + fn try_from(value: HistoryEvent) -> Result { + unimplemented!() + } +} + +impl TryFrom for ActivityMachine { + type Error = (); + + fn try_from(c: CommandType) -> Result { + unimplemented!() + } +} + +impl WFMachinesAdapter for ActivityMachine { + fn adapt_response( + &self, + event: &HistoryEvent, + has_next_event: bool, + my_command: ActivityMachineCommand, + ) -> Result, WFMachinesError> { + Ok(!vec![]) + } +} + +impl Cancellable for ActivityMachine { + fn cancel(&mut self) -> Result> { + unimplemented!() + } + + fn was_cancelled_before_sent_to_server(&self) -> bool { + unimplemented!() + } +} + +#[derive(Debug, derive_more::Display)] +pub(super) enum ActivityMachineCommand { + Complete, + Canceled, + IssueCancelCmd(Command), +} + #[derive(Default, Clone)] pub(super) struct SharedState { attrs: ScheduleActivityTaskCommandAttributes, diff --git a/src/machines/mod.rs b/src/machines/mod.rs index 9ef95a3c4..f371c33ea 100644 --- a/src/machines/mod.rs +++ b/src/machines/mod.rs @@ -33,7 +33,9 @@ pub(crate) mod test_help; pub(crate) use workflow_machines::{WFMachinesError, WorkflowMachines}; -use crate::protos::temporal::api::command::v1::FailWorkflowExecutionCommandAttributes; +use crate::protos::temporal::api::command::v1::{ + FailWorkflowExecutionCommandAttributes, ScheduleActivityTaskCommandAttributes, +}; use crate::{ core_tracing::VecDisplayer, machines::workflow_machines::MachineResponse, @@ -64,6 +66,7 @@ pub(crate) type ProtoCommand = Command; pub enum WFCommand { /// Returned when we need to wait for the lang sdk to send us something NoCommandsFromLang, + AddActivity(ScheduleActivityTaskCommandAttributes), AddTimer(StartTimerCommandAttributes), CancelTimer(CancelTimerCommandAttributes), CompleteWorkflow(CompleteWorkflowExecutionCommandAttributes), diff --git a/src/machines/workflow_machines.rs b/src/machines/workflow_machines.rs index 47131db3f..a5b6dcbbc 100644 --- a/src/machines/workflow_machines.rs +++ b/src/machines/workflow_machines.rs @@ -1,3 +1,4 @@ +use crate::machines::activity_state_machine::new_activity; use crate::workflow::{DrivenWorkflow, WorkflowFetcher}; use crate::{ core_tracing::VecDisplayer, @@ -57,6 +58,9 @@ pub(crate) struct WorkflowMachines { /// TODO: Make this apply to *all* cancellable things, once we've added more. Key can be enum. timer_id_to_machine: HashMap, + /// TODO document + activity_id_to_machine: HashMap, + /// Queued commands which have been produced by machines and await processing / being sent to /// the server. commands: VecDeque, @@ -554,6 +558,12 @@ impl WorkflowMachines { } } } + WFCommand::AddActivity(attrs) => { + let aid = attrs.activity_id.clone(); + let activity = self.add_new_machine(new_activity(attrs)); + self.activity_id_to_machine.insert(aid, activity.machine); + self.current_wf_task_commands.push_back(activity); + } WFCommand::CompleteWorkflow(attrs) => { let cwfm = self.add_new_machine(complete_workflow(attrs)); self.current_wf_task_commands.push_back(cwfm); From e7e3de2ef3d4430734b48808f734d06033515db6 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 8 Mar 2021 17:44:53 -0800 Subject: [PATCH 18/45] fix compilation issues and add proto messages --- src/machines/activity_state_machine.rs | 24 +++++++++++++++++------- src/machines/workflow_machines.rs | 1 + 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/machines/activity_state_machine.rs b/src/machines/activity_state_machine.rs index cbe98be01..1b5ecde45 100644 --- a/src/machines/activity_state_machine.rs +++ b/src/machines/activity_state_machine.rs @@ -9,7 +9,10 @@ use std::convert::TryFrom; // Schedule / cancel are "explicit events" (imperative rather than past events?) fsm! { - pub(super) name ActivityMachine; command ActivityCommand; error WFMachinesError; + pub(super) name ActivityMachine; + command ActivityCommand; + error WFMachinesError; + shared_state SharedState; Created --(Schedule, on_schedule)--> ScheduleCommandCreated; @@ -55,6 +58,7 @@ fsm! { --(ActivityTaskCanceled, on_activity_task_canceled) --> Canceled; } +#[derive(Debug, derive_more::Display)] pub(super) enum ActivityCommand {} #[derive(Debug, Clone, derive_more::Display)] @@ -94,8 +98,14 @@ pub(super) fn new_activity( impl ActivityMachine { pub(crate) fn new_scheduled(attribs: ScheduleActivityTaskCommandAttributes) -> (Self, Command) { - let mut s = Self::new(attribs); - s.on_event_mut(ActivityMachine::Schedule) + let mut s = Self { + state: Created {}.into(), + shared_state: SharedState { + attrs: attribs, + cancellation_type: ActivityCancellationType::TryCancel, + }, + }; + s.on_event_mut(ActivityMachineEvents::Schedule) .expect("Scheduling activities doesn't fail"); let cmd = Command { command_type: CommandType::ScheduleActivityTask as i32, @@ -105,7 +115,7 @@ impl ActivityMachine { } } -impl TryFrom for ActivityMachine { +impl TryFrom for ActivityMachineEvents { type Error = WFMachinesError; fn try_from(value: HistoryEvent) -> Result { @@ -113,7 +123,7 @@ impl TryFrom for ActivityMachine { } } -impl TryFrom for ActivityMachine { +impl TryFrom for ActivityMachineEvents { type Error = (); fn try_from(c: CommandType) -> Result { @@ -126,9 +136,9 @@ impl WFMachinesAdapter for ActivityMachine { &self, event: &HistoryEvent, has_next_event: bool, - my_command: ActivityMachineCommand, + my_command: ActivityCommand, ) -> Result, WFMachinesError> { - Ok(!vec![]) + Ok(match my_command {}) } } diff --git a/src/machines/workflow_machines.rs b/src/machines/workflow_machines.rs index a5b6dcbbc..59a4ffcee 100644 --- a/src/machines/workflow_machines.rs +++ b/src/machines/workflow_machines.rs @@ -146,6 +146,7 @@ impl WorkflowMachines { all_machines: Default::default(), machines_by_event_id: Default::default(), timer_id_to_machine: Default::default(), + activity_id_to_machine: Default::default(), commands: Default::default(), current_wf_task_commands: Default::default(), } From 95084c4b1e50080e795e05f4bc4fe3b99a692851 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 8 Mar 2021 20:01:01 -0800 Subject: [PATCH 19/45] Convert ActivityCommand in WFMachinesAdapter --- src/machines/activity_state_machine.rs | 22 ++++++++++++++++++++-- src/machines/workflow_machines.rs | 8 ++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/machines/activity_state_machine.rs b/src/machines/activity_state_machine.rs index 1b5ecde45..ddacd7a3e 100644 --- a/src/machines/activity_state_machine.rs +++ b/src/machines/activity_state_machine.rs @@ -1,5 +1,7 @@ use crate::machines::workflow_machines::MachineResponse; +use crate::machines::workflow_machines::MachineResponse::PushActivityJob; use crate::machines::{Cancellable, NewMachineWithCommand, WFMachinesAdapter, WFMachinesError}; +use crate::protos::coresdk::{activity_task, CancelActivity, StartActivity}; use crate::protos::temporal::api::command::v1::{Command, ScheduleActivityTaskCommandAttributes}; use crate::protos::temporal::api::enums::v1::CommandType; use crate::protos::temporal::api::history::v1::HistoryEvent; @@ -59,7 +61,10 @@ fsm! { } #[derive(Debug, derive_more::Display)] -pub(super) enum ActivityCommand {} +pub(super) enum ActivityCommand { + StartActivity, + CancelActivity, +} #[derive(Debug, Clone, derive_more::Display)] pub(super) enum ActivityCancellationType { @@ -138,7 +143,20 @@ impl WFMachinesAdapter for ActivityMachine { has_next_event: bool, my_command: ActivityCommand, ) -> Result, WFMachinesError> { - Ok(match my_command {}) + Ok(match my_command { + ActivityCommand::StartActivity => { + vec![PushActivityJob(activity_task::Job::Start(StartActivity { + // TODO pass activity details + }))] + } + ActivityCommand::CancelActivity => { + vec![PushActivityJob(activity_task::Job::Cancel( + CancelActivity { + // TODO pass activity cancellation details + }, + ))] + } + }) } } diff --git a/src/machines/workflow_machines.rs b/src/machines/workflow_machines.rs index 59a4ffcee..d7a920b52 100644 --- a/src/machines/workflow_machines.rs +++ b/src/machines/workflow_machines.rs @@ -1,4 +1,5 @@ use crate::machines::activity_state_machine::new_activity; +use crate::protos::coresdk::activity_task; use crate::workflow::{DrivenWorkflow, WorkflowFetcher}; use crate::{ core_tracing::VecDisplayer, @@ -90,6 +91,10 @@ struct CommandAndMachine { pub enum MachineResponse { #[display(fmt = "PushWFJob")] PushWFJob(#[from(forward)] wf_activation_job::Variant), + + #[display(fmt = "PushActivityJob")] + PushActivityJob(activity_task::Job), + IssueNewCommand(ProtoCommand), #[display(fmt = "TriggerWFTaskStarted")] TriggerWFTaskStarted { @@ -518,6 +523,9 @@ impl WorkflowMachines { MachineResponse::IssueNewCommand(_) => { panic!("Issue new command machine response not expected here") } + MachineResponse::PushActivityJob(a) => { + unimplemented!() + } } } Ok(()) From d066943b7e2eb37979830f10b48c8eaf27012c21 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 8 Mar 2021 20:36:15 -0800 Subject: [PATCH 20/45] Add CommandSender for activity in the workflow driver --- src/machines/activity_state_machine.rs | 3 ++- src/machines/test_help/workflow_driver.rs | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 src/machines/test_help/workflow_driver.rs diff --git a/src/machines/activity_state_machine.rs b/src/machines/activity_state_machine.rs index ddacd7a3e..ccb348c34 100644 --- a/src/machines/activity_state_machine.rs +++ b/src/machines/activity_state_machine.rs @@ -343,9 +343,10 @@ mod activity_machine_tests { fn activity_happy_hist() -> (TestHistoryBuilder, WorkflowMachines) { let twd = TestWorkflowDriver::new(|mut command_sink: CommandSender| async move { let activity = ScheduleActivityTaskCommandAttributes { + activity_id: "activity A".to_string(), ..Default::default() }; - command_sink.activity(activity); + command_sink.activity(activity, true); let complete = CompleteWorkflowExecutionCommandAttributes::default(); command_sink.send(complete.into()); diff --git a/src/machines/test_help/workflow_driver.rs b/src/machines/test_help/workflow_driver.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/src/machines/test_help/workflow_driver.rs @@ -0,0 +1 @@ + From 8dec63a9c26bdd49db6822c9ddd36665fe1ebb1d Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 8 Mar 2021 21:00:58 -0800 Subject: [PATCH 21/45] Add TryFrom for CommandType and make the test pass --- src/machines/activity_state_machine.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/machines/activity_state_machine.rs b/src/machines/activity_state_machine.rs index ccb348c34..84e346ebf 100644 --- a/src/machines/activity_state_machine.rs +++ b/src/machines/activity_state_machine.rs @@ -132,7 +132,11 @@ impl TryFrom for ActivityMachineEvents { type Error = (); fn try_from(c: CommandType) -> Result { - unimplemented!() + Ok(match c { + CommandType::ScheduleActivityTask => Self::CommandScheduleActivityTask, + CommandType::RequestCancelActivityTask => Self::CommandRequestCancelActivityTask, + _ => return Err(()), + }) } } @@ -166,7 +170,7 @@ impl Cancellable for ActivityMachine { } fn was_cancelled_before_sent_to_server(&self) -> bool { - unimplemented!() + false // TODO return cancellation flag from the shared state } } From abdbb6b9cdd3b937338a5838419a0c4bc9d51a34 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Wed, 10 Mar 2021 22:52:53 -0800 Subject: [PATCH 22/45] Add core API for polling activities --- protos/local/core_interface.proto | 2 +- src/lib.rs | 76 ++++++++++++++++++-------- src/machines/activity_state_machine.rs | 28 ++++++++-- src/machines/test_help/mod.rs | 4 +- src/machines/workflow_machines.rs | 5 +- src/pollers/mod.rs | 71 +++++++++++++++++++----- src/workflow/driven_workflow.rs | 2 +- 7 files changed, 140 insertions(+), 48 deletions(-) diff --git a/protos/local/core_interface.proto b/protos/local/core_interface.proto index e2a0bf38b..f54e8679d 100644 --- a/protos/local/core_interface.proto +++ b/protos/local/core_interface.proto @@ -157,7 +157,7 @@ message CancelActivity { message ActivityTask { string activity_id = 1; - oneof job { + oneof variant { // Start activity execution. StartActivity start = 2; // Attempt to cancel activity execution. diff --git a/src/lib.rs b/src/lib.rs index 04b549fe7..f777a5baf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,16 +22,20 @@ mod workflow; mod test_help; pub use core_tracing::tracing_init; -pub use pollers::{ServerGateway, ServerGatewayApis, ServerGatewayOptions}; +pub use pollers::{ + PollTaskRequest, PollTaskResponse, ServerGateway, ServerGatewayApis, ServerGatewayOptions, +}; pub use url::Url; +use crate::protos::coresdk::ActivityTask; +use crate::protos::temporal::api::workflowservice::v1::PollActivityTaskQueueResponse; use crate::{ machines::{InconvertibleCommandError, ProtoCommand, WFCommand, WFMachinesError}, pending_activations::{PendingActivation, PendingActivations}, protos::{ coresdk::{ - task_completion, wf_activation_completion::Status, Task, TaskCompletion, - WfActivationCompletion, WfActivationSuccess, + task_completion, wf_activation_completion::Status, ActivityResult, Task, + TaskCompletion, WfActivationCompletion, WfActivationSuccess, }, temporal::api::{ enums::v1::WorkflowTaskFailedCause, workflowservice::v1::PollWorkflowTaskQueueResponse, @@ -62,13 +66,17 @@ pub type Result = std::result::Result; /// expected that only one instance of an implementation will exist for the lifetime of the /// worker(s) using it. pub trait Core: Send + Sync { - /// Ask the core for some work, returning a [Task], which will eventually contain either a - /// [protos::coresdk::WfActivation] or an [protos::coresdk::ActivityTask]. It is then the - /// language SDK's responsibility to call the appropriate code with the provided inputs. + /// Ask the core for some work, returning a [Task], which will contain a [protos::coresdk::WfActivation]. + /// It is then the language SDK's responsibility to call the appropriate code with the provided inputs. /// /// TODO: Examples + /// TODO: rename to poll_workflow_task and change result type to WfActivation fn poll_task(&self, task_queue: &str) -> Result; + /// Ask the core for some work, returning a [protos::coresdk::Task], which will contain a [protos::coresdk::ActivityTask]. + /// It is then the language SDK's responsibility to call the completion API. + fn poll_activity_task(&self, task_queue: &str) -> Result; + /// Tell the core that some work has been completed - whether as a result of running workflow /// code or executing an activity. fn complete_task(&self, req: TaskCompletion) -> Result<()>; @@ -158,8 +166,8 @@ where // This will block forever (unless interrupted by shutdown) in the event there is no work // from the server - match self.poll_server(task_queue) { - Ok(work) => { + match self.poll_server(PollTaskRequest::Workflow(task_queue.to_owned())) { + Ok(PollTaskResponse::WorkflowTask(work)) => { let task_token = work.task_token.clone(); debug!( task_token = %fmt_task_token(&task_token), @@ -180,8 +188,29 @@ where variant: next_activation.activation.map(Into::into), }) } + // Drain pending activations in case of shutdown. Err(CoreError::ShuttingDown) => self.poll_task(task_queue), Err(e) => Err(e), + Ok(PollTaskResponse::ActivityTask(_)) => Err(CoreError::UnexpectedResult), + } + } + + #[instrument(skip(self))] + fn poll_activity_task(&self, task_queue: &str) -> Result { + if self.shutdown_requested.load(Ordering::SeqCst) { + return Err(CoreError::ShuttingDown); + } + + match self.poll_server(PollTaskRequest::Activity(task_queue.to_owned())) { + Ok(PollTaskResponse::ActivityTask(work)) => { + let task_token = work.task_token.clone(); + Ok(Task { + task_token, + variant: None, + }) + } + Err(e) => Err(e), + Ok(PollTaskResponse::WorkflowTask(_)) => Err(CoreError::UnexpectedResult), } } @@ -230,8 +259,11 @@ where Ok(()) } TaskCompletion { - variant: Some(task_completion::Variant::Activity(_)), - .. + task_token, + variant: + Some(task_completion::Variant::Activity(ActivityResult { + status: Some(activity_status), + })), } => unimplemented!(), _ => Err(CoreError::MalformedCompletion(req)), } @@ -298,7 +330,7 @@ impl CoreSDK { /// Blocks polling the server until it responds, or until the shutdown flag is set (aborting /// the poll) - fn poll_server(&self, task_queue: &str) -> Result { + fn poll_server(&self, req: PollTaskRequest) -> Result { self.runtime.block_on(async { let shutdownfut = async { loop { @@ -308,14 +340,12 @@ impl CoreSDK { tokio::time::sleep(Duration::from_millis(100)).await; } }; - let pollfut = self - .server_gateway - .poll_workflow_task(task_queue.to_owned()); + let poll_result_future = self.server_gateway.poll_task(req); tokio::select! { _ = shutdownfut => { Err(CoreError::ShuttingDown) } - r = pollfut => r + r = poll_result_future => r } }) } @@ -377,6 +407,9 @@ pub enum CoreError { /// When thrown from complete_task, it means you should poll for a new task, receive a new /// task token, and complete that task. UnhandledCommandWhenCompleting, + /// Indicates that underlying function returned Ok, but result type was incorrect. + /// This is likely a result of a bug and should never happen. + UnexpectedResult, } #[cfg(test)] @@ -810,14 +843,11 @@ mod test { let res = core.poll_task(TASK_Q).unwrap(); assert_matches!( res.get_wf_jobs().as_slice(), - [ - WfActivationJob { - variant: Some(wf_activation_job::Variant::SignalWorkflow(_)), - }, - WfActivationJob { - variant: Some(wf_activation_job::Variant::SignalWorkflow(_)), - } - ] + [WfActivationJob { + variant: Some(wf_activation_job::Variant::SignalWorkflow(_)), + }, WfActivationJob { + variant: Some(wf_activation_job::Variant::SignalWorkflow(_)), + }] ); } } diff --git a/src/machines/activity_state_machine.rs b/src/machines/activity_state_machine.rs index 84e346ebf..9f2220b84 100644 --- a/src/machines/activity_state_machine.rs +++ b/src/machines/activity_state_machine.rs @@ -3,7 +3,7 @@ use crate::machines::workflow_machines::MachineResponse::PushActivityJob; use crate::machines::{Cancellable, NewMachineWithCommand, WFMachinesAdapter, WFMachinesError}; use crate::protos::coresdk::{activity_task, CancelActivity, StartActivity}; use crate::protos::temporal::api::command::v1::{Command, ScheduleActivityTaskCommandAttributes}; -use crate::protos::temporal::api::enums::v1::CommandType; +use crate::protos::temporal::api::enums::v1::{CommandType, EventType}; use crate::protos::temporal::api::history::v1::HistoryEvent; use rustfsm::{fsm, MachineError, StateMachine, TransitionResult}; use std::convert::TryFrom; @@ -123,8 +123,22 @@ impl ActivityMachine { impl TryFrom for ActivityMachineEvents { type Error = WFMachinesError; - fn try_from(value: HistoryEvent) -> Result { - unimplemented!() + fn try_from(e: HistoryEvent) -> Result { + Ok(match EventType::from_i32(e.event_type) { + Some(EventType::ActivityTaskScheduled) => Self::ActivityTaskScheduled, + Some(EventType::ActivityTaskStarted) => Self::ActivityTaskStarted, + Some(EventType::ActivityTaskCompleted) => Self::ActivityTaskCompleted, + Some(EventType::ActivityTaskFailed) => Self::ActivityTaskFailed, + Some(EventType::ActivityTaskTimedOut) => Self::ActivityTaskTimedOut, + Some(EventType::ActivityTaskCancelRequested) => Self::ActivityTaskCancelRequested, + Some(EventType::ActivityTaskCanceled) => Self::ActivityTaskCanceled, + _ => { + return Err(WFMachinesError::UnexpectedEvent( + e, + "Activity machine does not handle this event", + )) + } + }) } } @@ -149,12 +163,14 @@ impl WFMachinesAdapter for ActivityMachine { ) -> Result, WFMachinesError> { Ok(match my_command { ActivityCommand::StartActivity => { - vec![PushActivityJob(activity_task::Job::Start(StartActivity { + vec![PushActivityJob(activity_task::Variant::Start( + StartActivity { // TODO pass activity details - }))] + }, + ))] } ActivityCommand::CancelActivity => { - vec![PushActivityJob(activity_task::Job::Cancel( + vec![PushActivityJob(activity_task::Variant::Cancel( CancelActivity { // TODO pass activity cancellation details }, diff --git a/src/machines/test_help/mod.rs b/src/machines/test_help/mod.rs index 011da2805..d0ba92241 100644 --- a/src/machines/test_help/mod.rs +++ b/src/machines/test_help/mod.rs @@ -14,7 +14,7 @@ use crate::{ protos::temporal::api::workflowservice::v1::{ PollWorkflowTaskQueueResponse, RespondWorkflowTaskCompletedResponse, }, - CoreSDK, ServerGatewayApis, + CoreSDK, PollTaskResponse, ServerGatewayApis, }; use rand::{thread_rng, Rng}; use std::sync::atomic::AtomicBool; @@ -59,7 +59,7 @@ pub(crate) fn build_fake_core( mock_gateway .expect_poll_workflow_task() .times(response_batches.len()) - .returning(move |_| Ok(tasks.pop_front().unwrap())); + .returning(move |_| Ok(PollTaskResponse::WorkflowTask(tasks.pop_front().unwrap()))); // Response not really important here mock_gateway .expect_complete_workflow_task() diff --git a/src/machines/workflow_machines.rs b/src/machines/workflow_machines.rs index d7a920b52..83dfbb872 100644 --- a/src/machines/workflow_machines.rs +++ b/src/machines/workflow_machines.rs @@ -10,7 +10,7 @@ use crate::{ TemporalStateMachine, WFCommand, }, protos::{ - coresdk::{wf_activation_job, StartWorkflow, UpdateRandomSeed, WfActivation}, + coresdk::{wf_activation_job, ActivityTask, StartWorkflow, UpdateRandomSeed, WfActivation}, temporal::api::{ enums::v1::{CommandType, EventType}, history::v1::{history_event, HistoryEvent}, @@ -92,8 +92,9 @@ pub enum MachineResponse { #[display(fmt = "PushWFJob")] PushWFJob(#[from(forward)] wf_activation_job::Variant), + // TODO remove and use IssueNewCommand #[display(fmt = "PushActivityJob")] - PushActivityJob(activity_task::Job), + PushActivityJob(activity_task::Variant), IssueNewCommand(ProtoCommand), #[display(fmt = "TriggerWFTaskStarted")] diff --git a/src/pollers/mod.rs b/src/pollers/mod.rs index 6563033bc..4b29fb470 100644 --- a/src/pollers/mod.rs +++ b/src/pollers/mod.rs @@ -2,7 +2,8 @@ use std::time::Duration; use crate::protos::temporal::api::common::v1::{Payloads, WorkflowExecution}; use crate::protos::temporal::api::workflowservice::v1::{ - SignalWorkflowExecutionRequest, SignalWorkflowExecutionResponse, + PollActivityTaskQueueRequest, PollActivityTaskQueueResponse, SignalWorkflowExecutionRequest, + SignalWorkflowExecutionResponse, }; use crate::{ machines::ProtoCommand, @@ -100,8 +101,13 @@ pub trait ServerGatewayApis { ) -> Result; /// Fetch new work. Should block indefinitely if there is no work. - async fn poll_workflow_task(&self, task_queue: String) - -> Result; + async fn poll_task(&self, req: PollTaskRequest) -> Result; + + /// Fetch new work. Should block indefinitely if there is no work. + async fn poll_workflow_task(&self, task_queue: String) -> Result; + + /// Fetch new work. Should block indefinitely if there is no work. + async fn poll_activity_task(&self, task_queue: String) -> Result; /// Complete a task by sending it to the server. `task_token` is the task token that would've /// been received from [PollWorkflowTaskQueueApi::poll]. `commands` is a list of new commands @@ -131,6 +137,16 @@ pub trait ServerGatewayApis { ) -> Result; } +pub enum PollTaskRequest { + Workflow(String), + Activity(String), +} + +pub enum PollTaskResponse { + WorkflowTask(PollWorkflowTaskQueueResponse), + ActivityTask(PollActivityTaskQueueResponse), +} + #[async_trait::async_trait] impl ServerGatewayApis for ServerGateway { async fn start_workflow( @@ -162,10 +178,18 @@ impl ServerGatewayApis for ServerGateway { .into_inner()) } - async fn poll_workflow_task( - &self, - task_queue: String, - ) -> Result { + async fn poll_task(&self, req: PollTaskRequest) -> Result { + match req { + PollTaskRequest::Workflow(task_queue) => { + Ok(Self::poll_workflow_task(self, task_queue).await?) + } + PollTaskRequest::Activity(task_queue) => { + Ok(Self::poll_activity_task(self, task_queue).await?) + } + } + } + + async fn poll_workflow_task(&self, task_queue: String) -> Result { let request = PollWorkflowTaskQueueRequest { namespace: self.opts.namespace.clone(), task_queue: Some(TaskQueue { @@ -176,12 +200,33 @@ impl ServerGatewayApis for ServerGateway { binary_checksum: self.opts.worker_binary_id.clone(), }; - Ok(self - .service - .clone() - .poll_workflow_task_queue(request) - .await? - .into_inner()) + Ok(PollTaskResponse::WorkflowTask( + self.service + .clone() + .poll_workflow_task_queue(request) + .await? + .into_inner(), + )) + } + + async fn poll_activity_task(&self, task_queue: String) -> Result { + let request = PollActivityTaskQueueRequest { + namespace: self.opts.namespace.clone(), + task_queue: Some(TaskQueue { + name: task_queue, + kind: TaskQueueKind::Unspecified as i32, + }), + identity: self.opts.identity.clone(), + task_queue_metadata: None, + }; + + Ok(PollTaskResponse::ActivityTask( + self.service + .clone() + .poll_activity_task_queue(request) + .await? + .into_inner(), + )) } async fn complete_workflow_task( diff --git a/src/workflow/driven_workflow.rs b/src/workflow/driven_workflow.rs index 4decbee1e..a541910a8 100644 --- a/src/workflow/driven_workflow.rs +++ b/src/workflow/driven_workflow.rs @@ -1,4 +1,4 @@ -use crate::protos::coresdk::SignalWorkflow; +use crate::protos::coresdk::{activity_task, SignalWorkflow}; use crate::{ machines::WFCommand, protos::coresdk::wf_activation_job, From d6171cfa556fc61b7c9a6a6c793c4454f094fa60 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Sun, 14 Mar 2021 23:07:54 -0700 Subject: [PATCH 23/45] Add complete activity task API to the core --- protos/local/core_interface.proto | 3 +- src/lib.rs | 48 +++++++++++++++++++++++-------- src/pollers/mod.rs | 29 +++++++++++++++++-- 3 files changed, 64 insertions(+), 16 deletions(-) diff --git a/protos/local/core_interface.proto b/protos/local/core_interface.proto index f54e8679d..4d9e6ed83 100644 --- a/protos/local/core_interface.proto +++ b/protos/local/core_interface.proto @@ -189,8 +189,7 @@ message WFActivationCompletion { message ActivityResult { oneof status { ActivityTaskSuccess completed = 1; - ActivityTaskCancelation canceled = 2; - ActivityTaskFailure failed = 3; + ActivityTaskFailure failed = 2; } } diff --git a/src/lib.rs b/src/lib.rs index f777a5baf..2c8a9cf73 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,15 +27,15 @@ pub use pollers::{ }; pub use url::Url; -use crate::protos::coresdk::ActivityTask; +use crate::protos::coresdk::{activity_result, ActivityTask}; use crate::protos::temporal::api::workflowservice::v1::PollActivityTaskQueueResponse; use crate::{ machines::{InconvertibleCommandError, ProtoCommand, WFCommand, WFMachinesError}, pending_activations::{PendingActivation, PendingActivations}, protos::{ coresdk::{ - task_completion, wf_activation_completion::Status, ActivityResult, Task, - TaskCompletion, WfActivationCompletion, WfActivationSuccess, + task_completion, wf_activation_completion, ActivityResult, Task, TaskCompletion, + WfActivationCompletion, WfActivationSuccess, }, temporal::api::{ enums::v1::WorkflowTaskFailedCause, workflowservice::v1::PollWorkflowTaskQueueResponse, @@ -81,6 +81,9 @@ pub trait Core: Send + Sync { /// code or executing an activity. fn complete_task(&self, req: TaskCompletion) -> Result<()>; + /// Tell the core that activity has completed. This will result in core calling the server and completing activity synchronously. + fn complete_activity_task(&self, req: TaskCompletion) -> Result<()>; + /// Returns an instance of ServerGateway. fn server_gateway(&self) -> Result>; @@ -230,7 +233,7 @@ where .map(|x| x.value().clone()) .ok_or_else(|| CoreError::NothingFoundForTaskToken(task_token.clone()))?; match wfstatus { - Status::Successful(success) => { + wf_activation_completion::Status::Successful(success) => { let commands = self.push_lang_commands(&run_id, success)?; // We only actually want to send commands back to the server if there are // no more pending activations -- in other words the lang SDK has caught @@ -242,7 +245,7 @@ where )?; } } - Status::Failed(failure) => { + wf_activation_completion::Status::Failed(failure) => { // Blow up any cached data associated with the workflow self.evict_run(&run_id); @@ -258,13 +261,31 @@ where } Ok(()) } + _ => Err(CoreError::MalformedCompletion(req)), + } + } + + #[instrument(skip(self))] + fn complete_activity_task(&self, req: TaskCompletion) -> Result<()> { + match req { TaskCompletion { task_token, variant: Some(task_completion::Variant::Activity(ActivityResult { - status: Some(activity_status), + status: Some(status), })), - } => unimplemented!(), + } => { + match status { + activity_result::Status::Completed(success) => { + self.runtime.block_on( + self.server_gateway + .complete_activity_task(task_token, success.result), + )?; + } + activity_result::Status::Failed(_) => unimplemented!(), + } + Ok(()) + } _ => Err(CoreError::MalformedCompletion(req)), } } @@ -843,11 +864,14 @@ mod test { let res = core.poll_task(TASK_Q).unwrap(); assert_matches!( res.get_wf_jobs().as_slice(), - [WfActivationJob { - variant: Some(wf_activation_job::Variant::SignalWorkflow(_)), - }, WfActivationJob { - variant: Some(wf_activation_job::Variant::SignalWorkflow(_)), - }] + [ + WfActivationJob { + variant: Some(wf_activation_job::Variant::SignalWorkflow(_)), + }, + WfActivationJob { + variant: Some(wf_activation_job::Variant::SignalWorkflow(_)), + } + ] ); } } diff --git a/src/pollers/mod.rs b/src/pollers/mod.rs index 4b29fb470..0efb1d25a 100644 --- a/src/pollers/mod.rs +++ b/src/pollers/mod.rs @@ -2,8 +2,9 @@ use std::time::Duration; use crate::protos::temporal::api::common::v1::{Payloads, WorkflowExecution}; use crate::protos::temporal::api::workflowservice::v1::{ - PollActivityTaskQueueRequest, PollActivityTaskQueueResponse, SignalWorkflowExecutionRequest, - SignalWorkflowExecutionResponse, + PollActivityTaskQueueRequest, PollActivityTaskQueueResponse, + RespondActivityTaskCompletedRequest, RespondActivityTaskCompletedResponse, + SignalWorkflowExecutionRequest, SignalWorkflowExecutionResponse, }; use crate::{ machines::ProtoCommand, @@ -118,6 +119,12 @@ pub trait ServerGatewayApis { commands: Vec, ) -> Result; + async fn complete_activity_task( + &self, + task_token: Vec, + result: Option, + ) -> Result; + /// Fail task by sending the failure to the server. `task_token` is the task token that would've /// been received from [PollWorkflowTaskQueueApi::poll]. async fn fail_workflow_task( @@ -305,4 +312,22 @@ impl ServerGatewayApis for ServerGateway { .await? .into_inner()) } + + async fn complete_activity_task( + &self, + task_token: Vec, + result: Option, + ) -> Result { + Ok(self + .service + .clone() + .respond_activity_task_completed(RespondActivityTaskCompletedRequest { + task_token, + result, + identity: self.opts.identity.clone(), + namespace: self.opts.namespace.clone(), + }) + .await? + .into_inner()) + } } From e48d1c45d00c6bb745c42c5df7bfc4c09d07467b Mon Sep 17 00:00:00 2001 From: Vitaly Date: Sun, 14 Mar 2021 23:20:52 -0700 Subject: [PATCH 24/45] Return cancellation result back --- protos/local/core_interface.proto | 1 + 1 file changed, 1 insertion(+) diff --git a/protos/local/core_interface.proto b/protos/local/core_interface.proto index 4d9e6ed83..bbfa7b891 100644 --- a/protos/local/core_interface.proto +++ b/protos/local/core_interface.proto @@ -190,6 +190,7 @@ message ActivityResult { oneof status { ActivityTaskSuccess completed = 1; ActivityTaskFailure failed = 2; + ActivityTaskCancelation canceled = 3; } } From 565d692deb2924ae00a2db2d96fca80d351be25e Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 15 Mar 2021 01:20:01 -0700 Subject: [PATCH 25/45] State machine changes for activity completion --- protos/local/core_interface.proto | 2 +- src/machines/activity_state_machine.rs | 82 +++++++++++++++----------- src/machines/workflow_machines.rs | 7 --- 3 files changed, 49 insertions(+), 42 deletions(-) diff --git a/protos/local/core_interface.proto b/protos/local/core_interface.proto index bbfa7b891..2fe4e0a0a 100644 --- a/protos/local/core_interface.proto +++ b/protos/local/core_interface.proto @@ -185,7 +185,7 @@ message WFActivationCompletion { } } -/// Used to report activity completion to core and to resolve the activity in a workflow activtion +/// Used to report activity completion to core and to resolve the activity in a workflow activation message ActivityResult { oneof status { ActivityTaskSuccess completed = 1; diff --git a/src/machines/activity_state_machine.rs b/src/machines/activity_state_machine.rs index 9f2220b84..62100cf9a 100644 --- a/src/machines/activity_state_machine.rs +++ b/src/machines/activity_state_machine.rs @@ -1,10 +1,15 @@ use crate::machines::workflow_machines::MachineResponse; -use crate::machines::workflow_machines::MachineResponse::PushActivityJob; use crate::machines::{Cancellable, NewMachineWithCommand, WFMachinesAdapter, WFMachinesError}; -use crate::protos::coresdk::{activity_task, CancelActivity, StartActivity}; +use crate::protos::coresdk::{ + activity_result, activity_task, ActivityResult, ActivityTaskSuccess, CancelActivity, + ResolveActivity, StartActivity, +}; use crate::protos::temporal::api::command::v1::{Command, ScheduleActivityTaskCommandAttributes}; +use crate::protos::temporal::api::common::v1::Payloads; use crate::protos::temporal::api::enums::v1::{CommandType, EventType}; -use crate::protos::temporal::api::history::v1::HistoryEvent; +use crate::protos::temporal::api::history::v1::{ + history_event, ActivityTaskCompletedEventAttributes, HistoryEvent, +}; use rustfsm::{fsm, MachineError, StateMachine, TransitionResult}; use std::convert::TryFrom; @@ -12,7 +17,7 @@ use std::convert::TryFrom; fsm! { pub(super) name ActivityMachine; - command ActivityCommand; + command ActivityMachineCommand; error WFMachinesError; shared_state SharedState; @@ -27,7 +32,7 @@ fsm! { ScheduledEventRecorded --(ActivityTaskTimedOut, on_task_timed_out) --> TimedOut; ScheduledEventRecorded --(Cancel, on_canceled) --> ScheduledActivityCancelCommandCreated; - Started --(ActivityTaskCompleted, on_activity_task_completed) --> Completed; + Started --(ActivityTaskCompleted(ActivityTaskCompletedEventAttributes), on_activity_task_completed) --> Completed; Started --(ActivityTaskFailed, on_activity_task_failed) --> Failed; Started --(ActivityTaskTimedOut, on_activity_task_timed_out) --> TimedOut; Started --(Cancel, on_canceled) --> StartedActivityCancelCommandCreated; @@ -53,7 +58,7 @@ fsm! { StartedActivityCancelEventRecorded --(ActivityTaskFailed, on_activity_task_failed) --> Failed; StartedActivityCancelEventRecorded - --(ActivityTaskCompleted, on_activity_task_completed) --> Completed; + --(ActivityTaskCompleted(ActivityTaskCompletedEventAttributes), on_activity_task_completed) --> Completed; StartedActivityCancelEventRecorded --(ActivityTaskTimedOut, on_activity_task_timed_out) --> TimedOut; StartedActivityCancelEventRecorded @@ -61,9 +66,9 @@ fsm! { } #[derive(Debug, derive_more::Display)] -pub(super) enum ActivityCommand { - StartActivity, - CancelActivity, +pub(super) enum ActivityMachineCommand { + #[display(fmt = "Complete")] + Complete(Option), } #[derive(Debug, Clone, derive_more::Display)] @@ -127,7 +132,19 @@ impl TryFrom for ActivityMachineEvents { Ok(match EventType::from_i32(e.event_type) { Some(EventType::ActivityTaskScheduled) => Self::ActivityTaskScheduled, Some(EventType::ActivityTaskStarted) => Self::ActivityTaskStarted, - Some(EventType::ActivityTaskCompleted) => Self::ActivityTaskCompleted, + Some(EventType::ActivityTaskCompleted) => { + if let Some(history_event::Attributes::ActivityTaskCompletedEventAttributes( + attrs, + )) = e.attributes + { + Self::ActivityTaskCompleted(attrs) + } else { + return Err(WFMachinesError::MalformedEvent( + e, + "Activity completion attributes were unset".to_string(), + )); + } + } Some(EventType::ActivityTaskFailed) => Self::ActivityTaskFailed, Some(EventType::ActivityTaskTimedOut) => Self::ActivityTaskTimedOut, Some(EventType::ActivityTaskCancelRequested) => Self::ActivityTaskCancelRequested, @@ -159,23 +176,18 @@ impl WFMachinesAdapter for ActivityMachine { &self, event: &HistoryEvent, has_next_event: bool, - my_command: ActivityCommand, + my_command: ActivityMachineCommand, ) -> Result, WFMachinesError> { Ok(match my_command { - ActivityCommand::StartActivity => { - vec![PushActivityJob(activity_task::Variant::Start( - StartActivity { - // TODO pass activity details - }, - ))] - } - ActivityCommand::CancelActivity => { - vec![PushActivityJob(activity_task::Variant::Cancel( - CancelActivity { - // TODO pass activity cancellation details - }, - ))] + ActivityMachineCommand::Complete(result) => vec![ResolveActivity { + activity_id: self.shared_state.attrs.activity_id.clone(), + result: Some(ActivityResult { + status: Some(activity_result::Status::Completed(ActivityTaskSuccess { + result, + })), + }), } + .into()], }) } } @@ -190,13 +202,6 @@ impl Cancellable for ActivityMachine { } } -#[derive(Debug, derive_more::Display)] -pub(super) enum ActivityMachineCommand { - Complete, - Canceled, - IssueCancelCmd(Command), -} - #[derive(Default, Clone)] pub(super) struct SharedState { attrs: ScheduleActivityTaskCommandAttributes, @@ -250,9 +255,15 @@ impl ScheduledEventRecorded { pub(super) struct Started {} impl Started { - pub(super) fn on_activity_task_completed(self) -> ActivityMachineTransition { + pub(super) fn on_activity_task_completed( + self, + attrs: ActivityTaskCompletedEventAttributes, + ) -> ActivityMachineTransition { // notify_completed - ActivityMachineTransition::default::() + ActivityMachineTransition::ok( + vec![ActivityMachineCommand::Complete(attrs.result)], + Completed::default(), + ) } pub(super) fn on_activity_task_failed(self) -> ActivityMachineTransition { // notify_failed @@ -312,7 +323,10 @@ impl StartedActivityCancelCommandCreated { pub(super) struct StartedActivityCancelEventRecorded {} impl StartedActivityCancelEventRecorded { - pub(super) fn on_activity_task_completed(self) -> ActivityMachineTransition { + pub(super) fn on_activity_task_completed( + self, + attrs: ActivityTaskCompletedEventAttributes, + ) -> ActivityMachineTransition { // notify_completed ActivityMachineTransition::default::() } diff --git a/src/machines/workflow_machines.rs b/src/machines/workflow_machines.rs index 83dfbb872..b7aef6e43 100644 --- a/src/machines/workflow_machines.rs +++ b/src/machines/workflow_machines.rs @@ -92,10 +92,6 @@ pub enum MachineResponse { #[display(fmt = "PushWFJob")] PushWFJob(#[from(forward)] wf_activation_job::Variant), - // TODO remove and use IssueNewCommand - #[display(fmt = "PushActivityJob")] - PushActivityJob(activity_task::Variant), - IssueNewCommand(ProtoCommand), #[display(fmt = "TriggerWFTaskStarted")] TriggerWFTaskStarted { @@ -524,9 +520,6 @@ impl WorkflowMachines { MachineResponse::IssueNewCommand(_) => { panic!("Issue new command machine response not expected here") } - MachineResponse::PushActivityJob(a) => { - unimplemented!() - } } } Ok(()) From df3d62475de96ab862a1d0c182f897aa5453516c Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 15 Mar 2021 01:20:19 -0700 Subject: [PATCH 26/45] update pattern --- src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 2c8a9cf73..2b4d15d05 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,6 +27,7 @@ pub use pollers::{ }; pub use url::Url; +use crate::protos::coresdk::activity_result::Status; use crate::protos::coresdk::{activity_result, ActivityTask}; use crate::protos::temporal::api::workflowservice::v1::PollActivityTaskQueueResponse; use crate::{ @@ -282,7 +283,7 @@ where .complete_activity_task(task_token, success.result), )?; } - activity_result::Status::Failed(_) => unimplemented!(), + _ => unimplemented!(), } Ok(()) } From 62d9b3fffc2a15d9ca051a7d0d2211379ec05f0d Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 15 Mar 2021 01:38:26 -0700 Subject: [PATCH 27/45] Add handling for failure/cancelation in completion API --- protos/local/core_interface.proto | 1 + src/lib.rs | 21 ++++++++++--- src/pollers/mod.rs | 50 +++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 4 deletions(-) diff --git a/protos/local/core_interface.proto b/protos/local/core_interface.proto index 2fe4e0a0a..797fba8b5 100644 --- a/protos/local/core_interface.proto +++ b/protos/local/core_interface.proto @@ -231,6 +231,7 @@ message WFActivationFailure { /// Used in ActivityResult to report cancellation message ActivityTaskCancelation { + temporal.api.common.v1.Payloads details = 1; } /// Used in ActivityResult to report successful completion diff --git a/src/lib.rs b/src/lib.rs index 2b4d15d05..ff7f86be9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,7 +28,10 @@ pub use pollers::{ pub use url::Url; use crate::protos::coresdk::activity_result::Status; -use crate::protos::coresdk::{activity_result, ActivityTask}; +use crate::protos::coresdk::{ + activity_result, ActivityTask, ActivityTaskCancelation, ActivityTaskFailure, + ActivityTaskSuccess, +}; use crate::protos::temporal::api::workflowservice::v1::PollActivityTaskQueueResponse; use crate::{ machines::{InconvertibleCommandError, ProtoCommand, WFCommand, WFMachinesError}, @@ -277,13 +280,23 @@ where })), } => { match status { - activity_result::Status::Completed(success) => { + activity_result::Status::Completed(ActivityTaskSuccess { result }) => { + self.runtime.block_on( + self.server_gateway + .complete_activity_task(task_token, result), + )?; + } + activity_result::Status::Failed(ActivityTaskFailure { failure }) => { + self.runtime.block_on( + self.server_gateway.fail_activity_task(task_token, failure), + )?; + } + activity_result::Status::Canceled(ActivityTaskCancelation { details }) => { self.runtime.block_on( self.server_gateway - .complete_activity_task(task_token, success.result), + .cancel_activity_task(task_token, details), )?; } - _ => unimplemented!(), } Ok(()) } diff --git a/src/pollers/mod.rs b/src/pollers/mod.rs index 0efb1d25a..41cd99106 100644 --- a/src/pollers/mod.rs +++ b/src/pollers/mod.rs @@ -3,7 +3,9 @@ use std::time::Duration; use crate::protos::temporal::api::common::v1::{Payloads, WorkflowExecution}; use crate::protos::temporal::api::workflowservice::v1::{ PollActivityTaskQueueRequest, PollActivityTaskQueueResponse, + RespondActivityTaskCanceledRequest, RespondActivityTaskCanceledResponse, RespondActivityTaskCompletedRequest, RespondActivityTaskCompletedResponse, + RespondActivityTaskFailedRequest, RespondActivityTaskFailedResponse, SignalWorkflowExecutionRequest, SignalWorkflowExecutionResponse, }; use crate::{ @@ -125,6 +127,18 @@ pub trait ServerGatewayApis { result: Option, ) -> Result; + async fn cancel_activity_task( + &self, + task_token: Vec, + details: Option, + ) -> Result; + + async fn fail_activity_task( + &self, + task_token: Vec, + failure: Option, + ) -> Result; + /// Fail task by sending the failure to the server. `task_token` is the task token that would've /// been received from [PollWorkflowTaskQueueApi::poll]. async fn fail_workflow_task( @@ -330,4 +344,40 @@ impl ServerGatewayApis for ServerGateway { .await? .into_inner()) } + + async fn cancel_activity_task( + &self, + task_token: Vec, + details: Option, + ) -> Result { + Ok(self + .service + .clone() + .respond_activity_task_canceled(RespondActivityTaskCanceledRequest { + task_token, + details, + identity: self.opts.identity.clone(), + namespace: self.opts.namespace.clone(), + }) + .await? + .into_inner()) + } + + async fn fail_activity_task( + &self, + task_token: Vec, + failure: Option, + ) -> Result { + Ok(self + .service + .clone() + .respond_activity_task_failed(RespondActivityTaskFailedRequest { + task_token, + failure, + identity: self.opts.identity.clone(), + namespace: self.opts.namespace.clone(), + }) + .await? + .into_inner()) + } } From 10546431c2c0d32e20001e32898d6a9232635bfd Mon Sep 17 00:00:00 2001 From: Vitaly Date: Wed, 17 Mar 2021 23:56:50 -0700 Subject: [PATCH 28/45] End to end flow with test --- src/lib.rs | 61 +++++++++++++++++++++++--- src/machines/activity_state_machine.rs | 5 ++- src/machines/mod.rs | 11 ++++- src/machines/test_help/mod.rs | 2 +- src/machines/workflow_machines.rs | 10 ++--- src/pollers/mod.rs | 17 ++++++- src/protos/mod.rs | 35 +++++++++++++++ src/test_help/canned_histories.rs | 1 + src/workflow/driven_workflow.rs | 2 +- 9 files changed, 127 insertions(+), 17 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ff7f86be9..cea2ce581 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,12 +27,9 @@ pub use pollers::{ }; pub use url::Url; -use crate::protos::coresdk::activity_result::Status; use crate::protos::coresdk::{ - activity_result, ActivityTask, ActivityTaskCancelation, ActivityTaskFailure, - ActivityTaskSuccess, + activity_result, task, ActivityTaskCancelation, ActivityTaskFailure, ActivityTaskSuccess, }; -use crate::protos::temporal::api::workflowservice::v1::PollActivityTaskQueueResponse; use crate::{ machines::{InconvertibleCommandError, ProtoCommand, WFCommand, WFMachinesError}, pending_activations::{PendingActivation, PendingActivations}, @@ -213,7 +210,7 @@ where let task_token = work.task_token.clone(); Ok(Task { task_token, - variant: None, + variant: Some(task::Variant::Activity(work.into())), }) } Err(e) => Err(e), @@ -450,7 +447,9 @@ pub enum CoreError { #[cfg(test)] mod test { use super::*; - use crate::protos::temporal::api::command::v1::FailWorkflowExecutionCommandAttributes; + use crate::protos::temporal::api::command::v1::{ + FailWorkflowExecutionCommandAttributes, ScheduleActivityTaskCommandAttributes, + }; use crate::{ machines::test_help::{build_fake_core, FakeCore, TestHistoryBuilder}, protos::{ @@ -483,6 +482,14 @@ mod test { build_fake_core(wfid, RUN_ID, &mut t, hist_batches) } + #[fixture(hist_batches = &[])] + fn single_activity_setup(hist_batches: &[usize]) -> FakeCore { + let wfid = "fake_wf_id"; + + let mut t = canned_histories::single_activity("fake_activity"); + build_fake_core(wfid, RUN_ID, &mut t, hist_batches) + } + #[rstest(core, case::incremental(single_timer_setup(&[1, 2])), case::replay(single_timer_setup(&[2])) @@ -523,7 +530,47 @@ mod test { .unwrap(); } - #[rstest(hist_batches, case::incremental(&[1, 2]), case::replay(&[2]))] + #[rstest(core, + case::incremental(single_activity_setup(& [1, 2])), + case::replay(single_activity_setup(& [2])) + )] + fn single_activity_completion(core: FakeCore) { + let res = core.poll_task(TASK_Q).unwrap(); + assert_matches!( + res.get_wf_jobs().as_slice(), + [WfActivationJob { + variant: Some(wf_activation_job::Variant::StartWorkflow(_)), + }] + ); + assert!(core.workflow_machines.exists(RUN_ID)); + + let task_tok = res.task_token; + core.complete_task(TaskCompletion::ok_from_api_attrs( + vec![ScheduleActivityTaskCommandAttributes { + activity_id: "fake_activity".to_string(), + ..Default::default() + } + .into()], + task_tok, + )) + .unwrap(); + + let res = core.poll_task(TASK_Q).unwrap(); + assert_matches!( + res.get_wf_jobs().as_slice(), + [WfActivationJob { + variant: Some(wf_activation_job::Variant::ResolveActivity(_)), + }] + ); + let task_tok = res.task_token; + core.complete_task(TaskCompletion::ok_from_api_attrs( + vec![CompleteWorkflowExecutionCommandAttributes { result: None }.into()], + task_tok, + )) + .unwrap(); + } + + #[rstest(hist_batches, case::incremental(& [1, 2]), case::replay(& [2]))] fn parallel_timer_test_across_wf_bridge(hist_batches: &[usize]) { let wfid = "fake_wf_id"; let run_id = "fake_run_id"; diff --git a/src/machines/activity_state_machine.rs b/src/machines/activity_state_machine.rs index 62100cf9a..731035e11 100644 --- a/src/machines/activity_state_machine.rs +++ b/src/machines/activity_state_machine.rs @@ -328,7 +328,10 @@ impl StartedActivityCancelEventRecorded { attrs: ActivityTaskCompletedEventAttributes, ) -> ActivityMachineTransition { // notify_completed - ActivityMachineTransition::default::() + ActivityMachineTransition::ok( + vec![ActivityMachineCommand::Complete(attrs.result)], + Completed::default(), + ) } pub(super) fn on_activity_task_failed(self) -> ActivityMachineTransition { // notify_failed diff --git a/src/machines/mod.rs b/src/machines/mod.rs index f371c33ea..86df95148 100644 --- a/src/machines/mod.rs +++ b/src/machines/mod.rs @@ -34,7 +34,8 @@ pub(crate) mod test_help; pub(crate) use workflow_machines::{WFMachinesError, WorkflowMachines}; use crate::protos::temporal::api::command::v1::{ - FailWorkflowExecutionCommandAttributes, ScheduleActivityTaskCommandAttributes, + FailWorkflowExecutionCommandAttributes, RequestCancelActivityTaskCommandAttributes, + ScheduleActivityTaskCommandAttributes, }; use crate::{ core_tracing::VecDisplayer, @@ -63,10 +64,12 @@ pub(crate) type ProtoCommand = Command; /// [DrivenWorkflow]s respond with these when called, to indicate what they want to do next. /// EX: Create a new timer, complete the workflow, etc. #[derive(Debug, derive_more::From)] +#[allow(clippy::large_enum_variant)] pub enum WFCommand { /// Returned when we need to wait for the lang sdk to send us something NoCommandsFromLang, AddActivity(ScheduleActivityTaskCommandAttributes), + RequestCancelActivity(RequestCancelActivityTaskCommandAttributes), AddTimer(StartTimerCommandAttributes), CancelTimer(CancelTimerCommandAttributes), CompleteWorkflow(CompleteWorkflowExecutionCommandAttributes), @@ -88,6 +91,12 @@ impl TryFrom for WFCommand { })) => match attrs { Attributes::StartTimerCommandAttributes(s) => Ok(WFCommand::AddTimer(s)), Attributes::CancelTimerCommandAttributes(s) => Ok(WFCommand::CancelTimer(s)), + Attributes::ScheduleActivityTaskCommandAttributes(s) => { + Ok(WFCommand::AddActivity(s)) + } + Attributes::RequestCancelActivityTaskCommandAttributes(s) => { + Ok(WFCommand::RequestCancelActivity(s)) + } Attributes::CompleteWorkflowExecutionCommandAttributes(c) => { Ok(WFCommand::CompleteWorkflow(c)) } diff --git a/src/machines/test_help/mod.rs b/src/machines/test_help/mod.rs index d0ba92241..2f9e75108 100644 --- a/src/machines/test_help/mod.rs +++ b/src/machines/test_help/mod.rs @@ -57,7 +57,7 @@ pub(crate) fn build_fake_core( let mut tasks = VecDeque::from(responses); let mut mock_gateway = MockServerGatewayApis::new(); mock_gateway - .expect_poll_workflow_task() + .expect_poll_task() .times(response_batches.len()) .returning(move |_| Ok(PollTaskResponse::WorkflowTask(tasks.pop_front().unwrap()))); // Response not really important here diff --git a/src/machines/workflow_machines.rs b/src/machines/workflow_machines.rs index b7aef6e43..0fece9690 100644 --- a/src/machines/workflow_machines.rs +++ b/src/machines/workflow_machines.rs @@ -1,5 +1,4 @@ use crate::machines::activity_state_machine::new_activity; -use crate::protos::coresdk::activity_task; use crate::workflow::{DrivenWorkflow, WorkflowFetcher}; use crate::{ core_tracing::VecDisplayer, @@ -10,7 +9,7 @@ use crate::{ TemporalStateMachine, WFCommand, }, protos::{ - coresdk::{wf_activation_job, ActivityTask, StartWorkflow, UpdateRandomSeed, WfActivation}, + coresdk::{wf_activation_job, StartWorkflow, UpdateRandomSeed, WfActivation}, temporal::api::{ enums::v1::{CommandType, EventType}, history::v1::{history_event, HistoryEvent}, @@ -163,7 +162,7 @@ impl WorkflowMachines { /// is the last event in the history. /// /// TODO: Describe what actually happens in here - #[instrument(level = "debug", skip(self), fields(run_id = %self.run_id))] + #[instrument(level = "debug", skip(self), fields(run_id = % self.run_id))] pub(crate) fn handle_event( &mut self, event: &HistoryEvent, @@ -371,7 +370,7 @@ impl WorkflowMachines { return Err(WFMachinesError::UnexpectedEvent( event.clone(), "The event is non a non-stateful event, but we tried to handle it as one", - )) + )); } } Ok(()) @@ -557,7 +556,7 @@ impl WorkflowMachines { return Err(WFMachinesError::UnexpectedMachineResponse( v, "When cancelling timer", - )) + )); } } } @@ -567,6 +566,7 @@ impl WorkflowMachines { self.activity_id_to_machine.insert(aid, activity.machine); self.current_wf_task_commands.push_back(activity); } + WFCommand::RequestCancelActivity(_) => unimplemented!(), WFCommand::CompleteWorkflow(attrs) => { let cwfm = self.add_new_machine(complete_workflow(attrs)); self.current_wf_task_commands.push_back(cwfm); diff --git a/src/pollers/mod.rs b/src/pollers/mod.rs index 41cd99106..e52eb7b07 100644 --- a/src/pollers/mod.rs +++ b/src/pollers/mod.rs @@ -113,7 +113,7 @@ pub trait ServerGatewayApis { async fn poll_activity_task(&self, task_queue: String) -> Result; /// Complete a task by sending it to the server. `task_token` is the task token that would've - /// been received from [PollWorkflowTaskQueueApi::poll]. `commands` is a list of new commands + /// been received from [poll_workflow_task_queue] API. `commands` is a list of new commands /// to send to the server, such as starting a timer. async fn complete_workflow_task( &self, @@ -121,18 +121,27 @@ pub trait ServerGatewayApis { commands: Vec, ) -> Result; + /// Complete activity task by sending response to the server. `task_token` contains activity identifier that + /// would've been received from [poll_activity_task_queue] API. `result` is a blob that contains + /// activity response. async fn complete_activity_task( &self, task_token: Vec, result: Option, ) -> Result; + /// Cancel activity task by sending response to the server. `task_token` contains activity identifier that + /// would've been received from [poll_activity_task_queue] API. `details` is a blob that provides arbitrary + /// user defined cancellation info. async fn cancel_activity_task( &self, task_token: Vec, details: Option, ) -> Result; + /// Fail activity task by sending response to the server. `task_token` contains activity identifier that + /// would've been received from [poll_activity_task_queue] API. `failure` provides failure details, such + /// as message, cause and stack trace. async fn fail_activity_task( &self, task_token: Vec, @@ -158,13 +167,19 @@ pub trait ServerGatewayApis { ) -> Result; } +/// Contains poll task request. String parameter defines a task queue to be polled. pub enum PollTaskRequest { + /// Instructs core to poll for workflow task. Workflow(String), + /// Instructs core to poll for activity task. Activity(String), } +/// Contains poll task response. pub enum PollTaskResponse { + /// Poll response for workflow task. WorkflowTask(PollWorkflowTaskQueueResponse), + /// Poll response for activity task. ActivityTask(PollActivityTaskQueueResponse), } diff --git a/src/protos/mod.rs b/src/protos/mod.rs index 87d82b5a9..df206f310 100644 --- a/src/protos/mod.rs +++ b/src/protos/mod.rs @@ -104,7 +104,9 @@ pub mod temporal { pub mod command { pub mod v1 { include!("temporal.api.command.v1.rs"); + use crate::protos::coresdk::{activity_task, ActivityTask, StartActivity}; use crate::protos::temporal::api::enums::v1::CommandType; + use crate::protos::temporal::api::workflowservice::v1::PollActivityTaskQueueResponse; use command::Attributes; use std::fmt::{Display, Formatter}; @@ -127,11 +129,44 @@ pub mod temporal { command_type: CommandType::FailWorkflowExecution as i32, attributes: Some(a), }, + a @ Attributes::ScheduleActivityTaskCommandAttributes(_) => Self { + command_type: CommandType::ScheduleActivityTask as i32, + attributes: Some(a), + }, + a @ Attributes::RequestCancelActivityTaskCommandAttributes(_) => Self { + command_type: CommandType::RequestCancelActivityTask as i32, + attributes: Some(a), + }, _ => unimplemented!(), } } } + impl From for ActivityTask { + fn from(r: PollActivityTaskQueueResponse) -> Self { + ActivityTask { + activity_id: r.activity_id, + variant: Some(activity_task::Variant::Start(StartActivity { + workflow_namespace: r.workflow_namespace, + workflow_type: r.workflow_type, + workflow_execution: r.workflow_execution, + activity_type: r.activity_type, + header: r.header, + input: r.input, + heartbeat_details: r.heartbeat_details, + scheduled_time: r.scheduled_time, + current_attempt_scheduled_time: r.current_attempt_scheduled_time, + started_time: r.started_time, + attempt: r.attempt, + schedule_to_close_timeout: r.schedule_to_close_timeout, + start_to_close_timeout: r.start_to_close_timeout, + heartbeat_timeout: r.heartbeat_timeout, + retry_policy: r.retry_policy, + })), + } + } + } + impl Display for Command { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let ct = CommandType::from_i32(self.command_type) diff --git a/src/test_help/canned_histories.rs b/src/test_help/canned_histories.rs index ed8df16c1..a2cbdf5a8 100644 --- a/src/test_help/canned_histories.rs +++ b/src/test_help/canned_histories.rs @@ -194,6 +194,7 @@ pub fn single_activity(activity_id: &str) -> TestHistoryBuilder { ActivityTaskCompletedEventAttributes { scheduled_event_id, started_event_id, + // todo add the result payload ..Default::default() }, ), diff --git a/src/workflow/driven_workflow.rs b/src/workflow/driven_workflow.rs index a541910a8..4decbee1e 100644 --- a/src/workflow/driven_workflow.rs +++ b/src/workflow/driven_workflow.rs @@ -1,4 +1,4 @@ -use crate::protos::coresdk::{activity_task, SignalWorkflow}; +use crate::protos::coresdk::SignalWorkflow; use crate::{ machines::WFCommand, protos::coresdk::wf_activation_job, From f3bb00caf7dcb0e3937b5e7ead8f9477e49b5072 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Wed, 17 Mar 2021 23:57:42 -0700 Subject: [PATCH 29/45] fmt --- tests/integ_tests/simple_wf_tests.rs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/tests/integ_tests/simple_wf_tests.rs b/tests/integ_tests/simple_wf_tests.rs index 36ded4eca..6bdc32630 100644 --- a/tests/integ_tests/simple_wf_tests.rs +++ b/tests/integ_tests/simple_wf_tests.rs @@ -460,14 +460,11 @@ fn signal_workflow() { let res = core.poll_task(task_q).unwrap(); assert_matches!( res.get_wf_jobs().as_slice(), - [ - WfActivationJob { - variant: Some(wf_activation_job::Variant::SignalWorkflow(_)), - }, - WfActivationJob { - variant: Some(wf_activation_job::Variant::SignalWorkflow(_)), - } - ] + [WfActivationJob { + variant: Some(wf_activation_job::Variant::SignalWorkflow(_)), + }, WfActivationJob { + variant: Some(wf_activation_job::Variant::SignalWorkflow(_)), + }] ); core.complete_task(TaskCompletion::ok_from_api_attrs( vec![CompleteWorkflowExecutionCommandAttributes { result: None }.into()], @@ -504,7 +501,7 @@ fn signal_workflow_signal_not_handled_on_workflow_completion() { res.get_wf_jobs().as_slice(), [WfActivationJob { variant: Some(wf_activation_job::Variant::FireTimer(_)), - },] + }] ); let task_token = res.task_token.clone(); @@ -535,7 +532,7 @@ fn signal_workflow_signal_not_handled_on_workflow_completion() { res.get_wf_jobs().as_slice(), [WfActivationJob { variant: Some(wf_activation_job::Variant::SignalWorkflow(_)), - },] + }] ); core.complete_task(TaskCompletion::ok_from_api_attrs( vec![CompleteWorkflowExecutionCommandAttributes { result: None }.into()], From b9616ddfbbf4b46f33e29e53b339f86404b6f8ee Mon Sep 17 00:00:00 2001 From: Vitaly Date: Thu, 18 Mar 2021 16:52:39 -0700 Subject: [PATCH 30/45] Address some CR comments --- src/lib.rs | 4 ++-- src/machines/activity_state_machine.rs | 17 +++++++---------- src/machines/workflow_machines.rs | 21 +++++++++------------ tests/integ_tests/simple_wf_tests.rs | 13 ++++++++----- 4 files changed, 26 insertions(+), 29 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index cea2ce581..6eeddd7ce 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -531,8 +531,8 @@ mod test { } #[rstest(core, - case::incremental(single_activity_setup(& [1, 2])), - case::replay(single_activity_setup(& [2])) + case::incremental(single_activity_setup(&[1, 2])), + case::replay(single_activity_setup(&[2])) )] fn single_activity_completion(core: FakeCore) { let res = core.poll_task(TASK_Q).unwrap(); diff --git a/src/machines/activity_state_machine.rs b/src/machines/activity_state_machine.rs index 731035e11..f17c17cc2 100644 --- a/src/machines/activity_state_machine.rs +++ b/src/machines/activity_state_machine.rs @@ -73,19 +73,15 @@ pub(super) enum ActivityMachineCommand { #[derive(Debug, Clone, derive_more::Display)] pub(super) enum ActivityCancellationType { - /** - * Wait for activity cancellation completion. Note that activity must heartbeat to receive a - * cancellation notification. This can block the cancellation for a long time if activity doesn't - * heartbeat or chooses to ignore the cancellation request. - */ + /// Wait for activity cancellation completion. Note that activity must heartbeat to receive a + /// cancellation notification. This can block the cancellation for a long time if activity doesn't + /// heartbeat or chooses to ignore the cancellation request. WaitCancellationCompleted, - /** Initiate a cancellation request and immediately report cancellation to the workflow. */ + /// Initiate a cancellation request and immediately report cancellation to the workflow. TryCancel, - /** - * Do not request cancellation of the activity and immediately report cancellation to the workflow - */ + /// Do not request cancellation of the activity and immediately report cancellation to the workflow Abandon, } @@ -95,7 +91,7 @@ impl Default for ActivityCancellationType { } } -/// Creates a new, scheduled, activity as a [CancellableCommand] +/// Creates a new activity state machine and a command to schedule it on the server. pub(super) fn new_activity( attribs: ScheduleActivityTaskCommandAttributes, ) -> NewMachineWithCommand { @@ -107,6 +103,7 @@ pub(super) fn new_activity( } impl ActivityMachine { + /// Create a new activity and immediately schedule it. pub(crate) fn new_scheduled(attribs: ScheduleActivityTaskCommandAttributes) -> (Self, Command) { let mut s = Self { state: Created {}.into(), diff --git a/src/machines/workflow_machines.rs b/src/machines/workflow_machines.rs index 0fece9690..c757d3adb 100644 --- a/src/machines/workflow_machines.rs +++ b/src/machines/workflow_machines.rs @@ -56,7 +56,7 @@ pub(crate) struct WorkflowMachines { /// Maps timer ids as created by workflow authors to their associated machines /// TODO: Make this apply to *all* cancellable things, once we've added more. Key can be enum. - timer_id_to_machine: HashMap, + id_to_machine: HashMap, /// TODO document activity_id_to_machine: HashMap, @@ -146,7 +146,7 @@ impl WorkflowMachines { current_wf_time: None, all_machines: Default::default(), machines_by_event_id: Default::default(), - timer_id_to_machine: Default::default(), + id_to_machine: Default::default(), activity_id_to_machine: Default::default(), commands: Default::default(), current_wf_task_commands: Default::default(), @@ -530,19 +530,16 @@ impl WorkflowMachines { WFCommand::AddTimer(attrs) => { let tid = attrs.timer_id.clone(); let timer = self.add_new_machine(new_timer(attrs)); - self.timer_id_to_machine.insert(tid, timer.machine); + self.id_to_machine.insert(tid, timer.machine); self.current_wf_task_commands.push_back(timer); } WFCommand::CancelTimer(attrs) => { - let mkey = *self - .timer_id_to_machine - .get(&attrs.timer_id) - .ok_or_else(|| { - WFMachinesError::MissingAssociatedMachine(format!( - "Missing associated machine for cancelling timer {}", - &attrs.timer_id - )) - })?; + let mkey = *self.id_to_machine.get(&attrs.timer_id).ok_or_else(|| { + WFMachinesError::MissingAssociatedMachine(format!( + "Missing associated machine for cancelling timer {}", + &attrs.timer_id + )) + })?; let res = self.machine_mut(mkey).cancel()?; match res { MachineResponse::IssueNewCommand(c) => { diff --git a/tests/integ_tests/simple_wf_tests.rs b/tests/integ_tests/simple_wf_tests.rs index 6bdc32630..f2afb894f 100644 --- a/tests/integ_tests/simple_wf_tests.rs +++ b/tests/integ_tests/simple_wf_tests.rs @@ -460,11 +460,14 @@ fn signal_workflow() { let res = core.poll_task(task_q).unwrap(); assert_matches!( res.get_wf_jobs().as_slice(), - [WfActivationJob { - variant: Some(wf_activation_job::Variant::SignalWorkflow(_)), - }, WfActivationJob { - variant: Some(wf_activation_job::Variant::SignalWorkflow(_)), - }] + [ + WfActivationJob { + variant: Some(wf_activation_job::Variant::SignalWorkflow(_)), + }, + WfActivationJob { + variant: Some(wf_activation_job::Variant::SignalWorkflow(_)), + } + ] ); core.complete_task(TaskCompletion::ok_from_api_attrs( vec![CompleteWorkflowExecutionCommandAttributes { result: None }.into()], From bac0197e925b951f39746f34bed6241bf70bf9bb Mon Sep 17 00:00:00 2001 From: Vitaly Date: Thu, 18 Mar 2021 17:00:43 -0700 Subject: [PATCH 31/45] Remove redundant test --- src/machines/activity_state_machine.rs | 52 -------------------------- 1 file changed, 52 deletions(-) diff --git a/src/machines/activity_state_machine.rs b/src/machines/activity_state_machine.rs index f17c17cc2..4b62328e9 100644 --- a/src/machines/activity_state_machine.rs +++ b/src/machines/activity_state_machine.rs @@ -361,55 +361,3 @@ pub(super) struct TimedOut {} #[derive(Default, Clone)] pub(super) struct Canceled {} - -#[cfg(test)] -mod activity_machine_tests { - use crate::machines::test_help::{CommandSender, TestHistoryBuilder, TestWorkflowDriver}; - use crate::machines::WorkflowMachines; - use crate::protos::temporal::api::command::v1::CompleteWorkflowExecutionCommandAttributes; - use crate::protos::temporal::api::command::v1::ScheduleActivityTaskCommandAttributes; - use crate::protos::temporal::api::enums::v1::CommandType; - use crate::test_help::canned_histories; - use rstest::{fixture, rstest}; - use tracing::Level; - - #[fixture] - fn activity_happy_hist() -> (TestHistoryBuilder, WorkflowMachines) { - let twd = TestWorkflowDriver::new(|mut command_sink: CommandSender| async move { - let activity = ScheduleActivityTaskCommandAttributes { - activity_id: "activity A".to_string(), - ..Default::default() - }; - command_sink.activity(activity, true); - - let complete = CompleteWorkflowExecutionCommandAttributes::default(); - command_sink.send(complete.into()); - }); - - let t = canned_histories::single_activity("activity1"); - let state_machines = WorkflowMachines::new( - "wfid".to_string(), - "runid".to_string(), - Box::new(twd).into(), - ); - - assert_eq!(2, t.as_history().get_workflow_task_count(None).unwrap()); - (t, state_machines) - } - - #[rstest] - fn test_activity_happy_path(activity_happy_hist: (TestHistoryBuilder, WorkflowMachines)) { - let s = span!(Level::DEBUG, "Test start", t = "activity_happy_path"); - let _enter = s.enter(); - let (t, mut state_machines) = activity_happy_hist; - let commands = t - .handle_workflow_task_take_cmds(&mut state_machines, Some(1)) - .unwrap(); - state_machines.get_wf_activation(); - assert_eq!(commands.len(), 1); - assert_eq!( - commands[0].command_type, - CommandType::ScheduleActivityTask as i32 - ); - } -} From 891a18a28af12a551bb2ef699d9dcbfe07cea439 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Thu, 18 Mar 2021 18:26:14 -0700 Subject: [PATCH 32/45] use command id enum as the issued_commands map key --- .../test_help/async_workflow_driver.rs | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/machines/test_help/async_workflow_driver.rs b/src/machines/test_help/async_workflow_driver.rs index 55bdade46..03f552ca6 100644 --- a/src/machines/test_help/async_workflow_driver.rs +++ b/src/machines/test_help/async_workflow_driver.rs @@ -21,6 +21,7 @@ use tokio::{ runtime::Runtime, task::{JoinError, JoinHandle}, }; +use CommandId::TimerId; pub struct TestWorkflowDriver { join_handle: Option>, @@ -36,23 +37,23 @@ struct TestWfDriverCache { impl TestWfDriverCache { /// Unblock a command by ID - fn unblock(&self, id: &str) { + fn unblock(&self, id: CommandId) { let mut bc = self.blocking_condvar.0.lock(); - if let Some(t) = bc.issued_commands.remove(id) { + if let Some(t) = bc.issued_commands.remove(&id) { t.unblocker.send(()).unwrap() }; } /// Cancel a timer by ID. Timers get some special handling here since they are always /// removed from the "lang" side without needing a response from core. - fn cancel_timer(&self, id: &str) { + fn cancel_timer(&self, id: CommandId) { let mut bc = self.blocking_condvar.0.lock(); - bc.issued_commands.remove(id); + bc.issued_commands.remove(&id); } /// Track a new command that the wf has sent down the command sink. The command starts in /// [CommandStatus::Sent] and will be marked blocked once it is `await`ed - fn add_sent_cmd(&self, id: String) -> oneshot::Receiver<()> { + fn add_sent_cmd(&self, id: CommandId) -> oneshot::Receiver<()> { let (tx, rx) = oneshot::channel(); let mut bc = self.blocking_condvar.0.lock(); bc.issued_commands.insert( @@ -66,9 +67,9 @@ impl TestWfDriverCache { } /// Indicate that a command is being `await`ed - fn set_cmd_blocked(&self, id: &str) { + fn set_cmd_blocked(&self, id: CommandId) { let mut bc = self.blocking_condvar.0.lock(); - if let Some(cmd) = bc.issued_commands.get_mut(id) { + if let Some(cmd) = bc.issued_commands.get_mut(&id) { cmd.status = CommandStatus::Blocked; } // Wake up the fetcher thread, since we have blocked on a command and that would mean we've @@ -77,14 +78,19 @@ impl TestWfDriverCache { } } +#[derive(Debug, PartialEq, Eq, Hash)] +enum CommandId { + TimerId(String), + ActivityId(String), +} + /// Contains the info needed to know if workflow code is "done" being iterated or not. A workflow /// iteration is considered complete if the workflow exits, or the top level task (the main codepath /// of the workflow) is blocked waiting on a command). #[derive(Default, Debug)] struct BlockingCondInfo { /// Holds a mapping of timer id -> oneshot channel to resolve it - /// TODO: Upgrade w/ enum key once activities are in - issued_commands: HashMap, + issued_commands: HashMap, wf_is_done: bool, } @@ -130,10 +136,10 @@ impl CommandSender { let tid = a.timer_id.clone(); let c = WFCommand::AddTimer(a); self.send(c); - let rx = self.twd_cache.add_sent_cmd(tid.clone()); + let rx = self.twd_cache.add_sent_cmd(TimerId(tid.clone())); let cache_clone = self.twd_cache.clone(); async move { - cache_clone.set_cmd_blocked(&tid); + cache_clone.set_cmd_blocked(TimerId(tid)); rx.await } } @@ -143,7 +149,7 @@ impl CommandSender { let c = WFCommand::CancelTimer(CancelTimerCommandAttributes { timer_id: timer_id.to_owned(), }); - self.twd_cache.cancel_timer(timer_id); + self.twd_cache.cancel_timer(TimerId(timer_id.to_string())); self.send(c); } } @@ -240,7 +246,7 @@ impl WorkflowFetcher for TestWorkflowDriver { impl ActivationListener for TestWorkflowDriver { fn on_activation_job(&mut self, activation: &wf_activation_job::Variant) { if let wf_activation_job::Variant::FireTimer(FireTimer { timer_id }) = activation { - self.cache.unblock(timer_id); + self.cache.unblock(TimerId(timer_id.to_owned())); } } } From a6103c1169b3df082c567e725182ed4ef9f97489 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Thu, 18 Mar 2021 18:30:30 -0700 Subject: [PATCH 33/45] rename enum params --- .../test_help/async_workflow_driver.rs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/machines/test_help/async_workflow_driver.rs b/src/machines/test_help/async_workflow_driver.rs index 03f552ca6..596d83d94 100644 --- a/src/machines/test_help/async_workflow_driver.rs +++ b/src/machines/test_help/async_workflow_driver.rs @@ -21,7 +21,7 @@ use tokio::{ runtime::Runtime, task::{JoinError, JoinHandle}, }; -use CommandId::TimerId; +use CommandID::Timer; pub struct TestWorkflowDriver { join_handle: Option>, @@ -37,7 +37,7 @@ struct TestWfDriverCache { impl TestWfDriverCache { /// Unblock a command by ID - fn unblock(&self, id: CommandId) { + fn unblock(&self, id: CommandID) { let mut bc = self.blocking_condvar.0.lock(); if let Some(t) = bc.issued_commands.remove(&id) { t.unblocker.send(()).unwrap() @@ -46,14 +46,14 @@ impl TestWfDriverCache { /// Cancel a timer by ID. Timers get some special handling here since they are always /// removed from the "lang" side without needing a response from core. - fn cancel_timer(&self, id: CommandId) { + fn cancel_timer(&self, id: CommandID) { let mut bc = self.blocking_condvar.0.lock(); bc.issued_commands.remove(&id); } /// Track a new command that the wf has sent down the command sink. The command starts in /// [CommandStatus::Sent] and will be marked blocked once it is `await`ed - fn add_sent_cmd(&self, id: CommandId) -> oneshot::Receiver<()> { + fn add_sent_cmd(&self, id: CommandID) -> oneshot::Receiver<()> { let (tx, rx) = oneshot::channel(); let mut bc = self.blocking_condvar.0.lock(); bc.issued_commands.insert( @@ -67,7 +67,7 @@ impl TestWfDriverCache { } /// Indicate that a command is being `await`ed - fn set_cmd_blocked(&self, id: CommandId) { + fn set_cmd_blocked(&self, id: CommandID) { let mut bc = self.blocking_condvar.0.lock(); if let Some(cmd) = bc.issued_commands.get_mut(&id) { cmd.status = CommandStatus::Blocked; @@ -79,9 +79,9 @@ impl TestWfDriverCache { } #[derive(Debug, PartialEq, Eq, Hash)] -enum CommandId { - TimerId(String), - ActivityId(String), +enum CommandID { + Timer(String), + Activity(String), } /// Contains the info needed to know if workflow code is "done" being iterated or not. A workflow @@ -90,7 +90,7 @@ enum CommandId { #[derive(Default, Debug)] struct BlockingCondInfo { /// Holds a mapping of timer id -> oneshot channel to resolve it - issued_commands: HashMap, + issued_commands: HashMap, wf_is_done: bool, } @@ -136,10 +136,10 @@ impl CommandSender { let tid = a.timer_id.clone(); let c = WFCommand::AddTimer(a); self.send(c); - let rx = self.twd_cache.add_sent_cmd(TimerId(tid.clone())); + let rx = self.twd_cache.add_sent_cmd(Timer(tid.clone())); let cache_clone = self.twd_cache.clone(); async move { - cache_clone.set_cmd_blocked(TimerId(tid)); + cache_clone.set_cmd_blocked(Timer(tid)); rx.await } } @@ -149,7 +149,7 @@ impl CommandSender { let c = WFCommand::CancelTimer(CancelTimerCommandAttributes { timer_id: timer_id.to_owned(), }); - self.twd_cache.cancel_timer(TimerId(timer_id.to_string())); + self.twd_cache.cancel_timer(Timer(timer_id.to_string())); self.send(c); } } @@ -246,7 +246,7 @@ impl WorkflowFetcher for TestWorkflowDriver { impl ActivationListener for TestWorkflowDriver { fn on_activation_job(&mut self, activation: &wf_activation_job::Variant) { if let wf_activation_job::Variant::FireTimer(FireTimer { timer_id }) = activation { - self.cache.unblock(TimerId(timer_id.to_owned())); + self.cache.unblock(Timer(timer_id.to_owned())); } } } From 81c73eda9b5857878b2a2d262118d426a331f113 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Thu, 18 Mar 2021 20:12:05 -0700 Subject: [PATCH 34/45] Add end-to-end integration test --- src/pollers/mod.rs | 2 +- src/protos/mod.rs | 20 ++++++++ tests/integ_tests/simple_wf_tests.rs | 74 +++++++++++++++++++++++++++- 3 files changed, 94 insertions(+), 2 deletions(-) diff --git a/src/pollers/mod.rs b/src/pollers/mod.rs index e52eb7b07..f057ffdc5 100644 --- a/src/pollers/mod.rs +++ b/src/pollers/mod.rs @@ -250,7 +250,7 @@ impl ServerGatewayApis for ServerGateway { namespace: self.opts.namespace.clone(), task_queue: Some(TaskQueue { name: task_queue, - kind: TaskQueueKind::Unspecified as i32, + kind: TaskQueueKind::Normal as i32, }), identity: self.opts.identity.clone(), task_queue_metadata: None, diff --git a/src/protos/mod.rs b/src/protos/mod.rs index df206f310..223b1dbf9 100644 --- a/src/protos/mod.rs +++ b/src/protos/mod.rs @@ -13,6 +13,7 @@ pub mod coresdk { use super::temporal::api::command::v1::Command as ApiCommand; use super::temporal::api::enums::v1::WorkflowTaskFailedCause; use super::temporal::api::failure::v1::Failure; + use crate::protos::temporal::api::common::v1::Payloads; use command::Variant; pub type HistoryEventId = i64; @@ -33,6 +34,14 @@ pub mod coresdk { vec![] } } + /// Returns any contained jobs if this task was a wf activation and it had some + pub fn get_activity_variant(&self) -> Option { + if let Some(task::Variant::Activity(a)) = &self.variant { + a.variant.clone() + } else { + None + } + } /// Returns the workflow run id if the task was a workflow pub fn get_run_id(&self) -> Option<&str> { @@ -79,6 +88,17 @@ pub mod coresdk { } } + pub fn ok_activity(result: Option, task_token: Vec) -> Self { + TaskCompletion { + task_token, + variant: Some(task_completion::Variant::Activity(ActivityResult { + status: Some(activity_result::Status::Completed(ActivityTaskSuccess { + result, + })), + })), + } + } + pub fn fail(task_token: Vec, cause: WorkflowTaskFailedCause, failure: Failure) -> Self { Self { task_token, diff --git a/tests/integ_tests/simple_wf_tests.rs b/tests/integ_tests/simple_wf_tests.rs index f2afb894f..defb1a8d9 100644 --- a/tests/integ_tests/simple_wf_tests.rs +++ b/tests/integ_tests/simple_wf_tests.rs @@ -11,10 +11,14 @@ use std::{ }, time::Duration, }; +use temporal_sdk_core::protos::temporal::api::command::v1::ScheduleActivityTaskCommandAttributes; +use temporal_sdk_core::protos::temporal::api::common::v1::{ActivityType, Payload, Payloads}; +use temporal_sdk_core::protos::temporal::api::taskqueue::v1::TaskQueue; use temporal_sdk_core::{ protos::{ coresdk::{ - wf_activation_job, FireTimer, StartWorkflow, Task, TaskCompletion, WfActivationJob, + activity_result, activity_task, wf_activation_job, ActivityResult, ActivityTaskSuccess, + FireTimer, ResolveActivity, StartWorkflow, Task, TaskCompletion, WfActivationJob, }, temporal::api::{ command::v1::{ @@ -111,6 +115,74 @@ fn timer_workflow() { .unwrap(); } +#[test] +fn activity_workflow() { + let mut rng = rand::thread_rng(); + let task_q_salt: u32 = rng.gen(); + let task_q = &format!("activity_workflow_{}", task_q_salt.to_string()); + let core = get_integ_core(); + let workflow_id: u32 = rng.gen(); + create_workflow(&core, task_q, &workflow_id.to_string(), None); + let activity_id: String = rng.gen::().to_string(); + let task = core.poll_task(task_q).unwrap(); + core.complete_task(TaskCompletion::ok_from_api_attrs( + vec![ScheduleActivityTaskCommandAttributes { + activity_id: activity_id.to_string(), + activity_type: Some(ActivityType { + name: "test_activity".to_string(), + }), + namespace: NAMESPACE.to_owned(), + task_queue: Some(TaskQueue { + name: task_q.to_owned(), + kind: 1, + }), + schedule_to_start_timeout: Some(Duration::from_secs(30).into()), + start_to_close_timeout: Some(Duration::from_secs(30).into()), + schedule_to_close_timeout: Some(Duration::from_secs(60).into()), + heartbeat_timeout: Some(Duration::from_secs(60).into()), + ..Default::default() + } + .into()], + task.task_token, + )) + .unwrap(); + let task = dbg!(core.poll_activity_task(task_q).unwrap()); + assert_matches!( + task.get_activity_variant(), + Some(activity_task::Variant::Start(start_activity)) => { + assert_eq!(start_activity.activity_type, Some(ActivityType { + name: "test_activity".to_string(), + })) + } + ); + let response_payloads = vec![Payload { + metadata: Default::default(), + data: b"hello ".to_vec(), + }]; + core.complete_activity_task(TaskCompletion::ok_activity( + Some(Payloads { + payloads: response_payloads.clone(), + }), + task.task_token, + )) + .unwrap(); + let task = core.poll_task(task_q).unwrap(); + assert_matches!( + task.get_wf_jobs().as_slice(), + [ + WfActivationJob { + variant: Some(wf_activation_job::Variant::ResolveActivity( + ResolveActivity {activity_id: a_id, result: Some(ActivityResult{ + status: Some(activity_result::Status::Completed(ActivityTaskSuccess{result: Some(r)}))})} + )), + }, + ] => { + assert_eq!(a_id, &activity_id); + assert_eq!(r, &Payloads{ payloads: response_payloads.clone()}); + } + ); +} + #[test] fn parallel_timer_workflow() { let task_q = "parallel_timer_workflow"; From 1fb36dccd52dc4fa3f36b3f968105e89387f2d8e Mon Sep 17 00:00:00 2001 From: Vitaly Date: Thu, 18 Mar 2021 20:15:53 -0700 Subject: [PATCH 35/45] complete wf --- tests/integ_tests/simple_wf_tests.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/integ_tests/simple_wf_tests.rs b/tests/integ_tests/simple_wf_tests.rs index defb1a8d9..7ea3726cf 100644 --- a/tests/integ_tests/simple_wf_tests.rs +++ b/tests/integ_tests/simple_wf_tests.rs @@ -181,6 +181,11 @@ fn activity_workflow() { assert_eq!(r, &Payloads{ payloads: response_payloads.clone()}); } ); + core.complete_task(TaskCompletion::ok_from_api_attrs( + vec![CompleteWorkflowExecutionCommandAttributes { result: None }.into()], + task.task_token, + )) + .unwrap() } #[test] From 64e1f900c4838231d93b266ad778b687fbae33cd Mon Sep 17 00:00:00 2001 From: Vitaly Date: Thu, 18 Mar 2021 23:03:54 -0700 Subject: [PATCH 36/45] make id to machine map keyed of command id --- .../test_help/async_workflow_driver.rs | 7 +--- src/machines/workflow_machines.rs | 33 +++++++++++-------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/machines/test_help/async_workflow_driver.rs b/src/machines/test_help/async_workflow_driver.rs index 596d83d94..c150fa7a6 100644 --- a/src/machines/test_help/async_workflow_driver.rs +++ b/src/machines/test_help/async_workflow_driver.rs @@ -1,3 +1,4 @@ +use crate::machines::workflow_machines::CommandID; use crate::{ machines::WFCommand, protos::{ @@ -78,12 +79,6 @@ impl TestWfDriverCache { } } -#[derive(Debug, PartialEq, Eq, Hash)] -enum CommandID { - Timer(String), - Activity(String), -} - /// Contains the info needed to know if workflow code is "done" being iterated or not. A workflow /// iteration is considered complete if the workflow exits, or the top level task (the main codepath /// of the workflow) is blocked waiting on a command). diff --git a/src/machines/workflow_machines.rs b/src/machines/workflow_machines.rs index c757d3adb..b25c285a4 100644 --- a/src/machines/workflow_machines.rs +++ b/src/machines/workflow_machines.rs @@ -56,10 +56,7 @@ pub(crate) struct WorkflowMachines { /// Maps timer ids as created by workflow authors to their associated machines /// TODO: Make this apply to *all* cancellable things, once we've added more. Key can be enum. - id_to_machine: HashMap, - - /// TODO document - activity_id_to_machine: HashMap, + id_to_machine: HashMap, /// Queued commands which have been produced by machines and await processing / being sent to /// the server. @@ -75,6 +72,12 @@ pub(crate) struct WorkflowMachines { drive_me: DrivenWorkflow, } +#[derive(Debug, PartialEq, Eq, Hash)] +pub enum CommandID { + Timer(String), + Activity(String), +} + slotmap::new_key_type! { struct MachineKey; } #[derive(Debug, derive_more::Display)] #[display(fmt = "Cmd&Machine({})", "command")] @@ -147,7 +150,6 @@ impl WorkflowMachines { all_machines: Default::default(), machines_by_event_id: Default::default(), id_to_machine: Default::default(), - activity_id_to_machine: Default::default(), commands: Default::default(), current_wf_task_commands: Default::default(), } @@ -530,16 +532,20 @@ impl WorkflowMachines { WFCommand::AddTimer(attrs) => { let tid = attrs.timer_id.clone(); let timer = self.add_new_machine(new_timer(attrs)); - self.id_to_machine.insert(tid, timer.machine); + self.id_to_machine + .insert(CommandID::Timer(tid), timer.machine); self.current_wf_task_commands.push_back(timer); } WFCommand::CancelTimer(attrs) => { - let mkey = *self.id_to_machine.get(&attrs.timer_id).ok_or_else(|| { - WFMachinesError::MissingAssociatedMachine(format!( - "Missing associated machine for cancelling timer {}", - &attrs.timer_id - )) - })?; + let mkey = *self + .id_to_machine + .get(&CommandID::Timer(attrs.timer_id.to_owned())) + .ok_or_else(|| { + WFMachinesError::MissingAssociatedMachine(format!( + "Missing associated machine for cancelling timer {}", + &attrs.timer_id + )) + })?; let res = self.machine_mut(mkey).cancel()?; match res { MachineResponse::IssueNewCommand(c) => { @@ -560,7 +566,8 @@ impl WorkflowMachines { WFCommand::AddActivity(attrs) => { let aid = attrs.activity_id.clone(); let activity = self.add_new_machine(new_activity(attrs)); - self.activity_id_to_machine.insert(aid, activity.machine); + self.id_to_machine + .insert(CommandID::Activity(aid), activity.machine); self.current_wf_task_commands.push_back(activity); } WFCommand::RequestCancelActivity(_) => unimplemented!(), From 8a7d480620076a049d46bebb2b83d43319a50298 Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Mon, 22 Mar 2021 15:05:55 -0700 Subject: [PATCH 37/45] Fix 8 billion compile errors --- build.rs | 12 +- protos/local/workflow_commands.proto | 9 +- src/lib.rs | 106 ++++----- src/machines/activity_state_machine.rs | 53 +++-- .../complete_workflow_state_machine.rs | 13 +- src/machines/fail_workflow_state_machine.rs | 15 +- src/machines/mod.rs | 49 ++-- .../test_help/async_workflow_driver.rs | 12 +- src/machines/timer_state_machine.rs | 43 ++-- src/machines/workflow_machines.rs | 17 +- src/protos/mod.rs | 224 ++++++++++++++++-- src/workflow/driven_workflow.rs | 21 +- src/workflow/mod.rs | 2 +- 13 files changed, 386 insertions(+), 190 deletions(-) diff --git a/build.rs b/build.rs index 474f91870..040b1f1e0 100644 --- a/build.rs +++ b/build.rs @@ -14,10 +14,16 @@ fn main() -> Result<(), Box> { "temporal.api.command.v1.Command.attributes", "#[derive(::derive_more::From)]", ) - .type_attribute("coresdk.Command.variant", "#[derive(::derive_more::From)]") - .type_attribute("coresdk.WFActivationJob", "#[derive(::derive_more::From)]") .type_attribute( - "coresdk.WFActivationJob.variant", + "coresdk.workflow_commands.WorkflowCommand.variant", + "#[derive(::derive_more::From)]", + ) + .type_attribute( + "coresdk.workflow_activation.wf_activation_job", + "#[derive(::derive_more::From)]", + ) + .type_attribute( + "coresdk.workflow_activation.WFActivationJob.variant", "#[derive(::derive_more::From)]", ) .type_attribute("coresdk.Task.variant", "#[derive(::derive_more::From)]") diff --git a/protos/local/workflow_commands.proto b/protos/local/workflow_commands.proto index 596f9921a..7f0541154 100644 --- a/protos/local/workflow_commands.proto +++ b/protos/local/workflow_commands.proto @@ -17,8 +17,9 @@ message WorkflowCommand { ScheduleActivity schedule_activity = 2; QueryResult respond_to_query = 3; RequestActivityCancellation request_activity_cancellation = 4; - CompleteWorkflowExecution complete_workflow_execution = 5; - FailWorkflowExecution fail_workflow_execution = 6; + CancelTimer cancel_timer = 5; + CompleteWorkflowExecution complete_workflow_execution = 6; + FailWorkflowExecution fail_workflow_execution = 7; // To be added as/if needed: // RequestCancelActivityTask request_cancel_activity_task_command_attributes = 6; @@ -37,6 +38,10 @@ message StartTimer { google.protobuf.Duration start_to_fire_timeout = 2; } +message CancelTimer { + string timer_id = 1; +} + message ScheduleActivity { string activity_id = 1; string activity_type = 2; diff --git a/src/lib.rs b/src/lib.rs index ff7f86be9..d67cc9714 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,22 +27,20 @@ pub use pollers::{ }; pub use url::Url; -use crate::protos::coresdk::activity_result::Status; -use crate::protos::coresdk::{ - activity_result, ActivityTask, ActivityTaskCancelation, ActivityTaskFailure, - ActivityTaskSuccess, -}; -use crate::protos::temporal::api::workflowservice::v1::PollActivityTaskQueueResponse; +use crate::machines::EmptyWorkflowCommandErr; use crate::{ - machines::{InconvertibleCommandError, ProtoCommand, WFCommand, WFMachinesError}, + machines::{ProtoCommand, WFCommand, WFMachinesError}, pending_activations::{PendingActivation, PendingActivations}, protos::{ coresdk::{ - task_completion, wf_activation_completion, ActivityResult, Task, TaskCompletion, - WfActivationCompletion, WfActivationSuccess, + activity_result::{self as ar, activity_result, ActivityResult}, + task_completion, workflow_completion, + workflow_completion::{wf_activation_completion, WfActivationCompletion}, + PayloadsExt, Task, TaskCompletion, }, temporal::api::{ - enums::v1::WorkflowTaskFailedCause, workflowservice::v1::PollWorkflowTaskQueueResponse, + enums::v1::WorkflowTaskFailedCause, + workflowservice::v1::{PollActivityTaskQueueResponse, PollWorkflowTaskQueueResponse}, }, }, protosext::{fmt_task_token, HistoryInfoError}, @@ -253,14 +251,12 @@ where // Blow up any cached data associated with the workflow self.evict_run(&run_id); - self.runtime.block_on( - self.server_gateway.fail_workflow_task( + self.runtime + .block_on(self.server_gateway.fail_workflow_task( task_token, - WorkflowTaskFailedCause::from_i32(failure.cause) - .unwrap_or(WorkflowTaskFailedCause::Unspecified), - failure.failure, - ), - )?; + WorkflowTaskFailedCause::Unspecified, + failure.failure.map(Into::into), + ))?; } } Ok(()) @@ -280,21 +276,22 @@ where })), } => { match status { - activity_result::Status::Completed(ActivityTaskSuccess { result }) => { + activity_result::Status::Completed(ar::Success { result }) => { self.runtime.block_on( self.server_gateway - .complete_activity_task(task_token, result), + .complete_activity_task(task_token, result.into_payloads()), )?; } - activity_result::Status::Failed(ActivityTaskFailure { failure }) => { + activity_result::Status::Failed(ar::Failure { failure }) => { self.runtime.block_on( - self.server_gateway.fail_activity_task(task_token, failure), + self.server_gateway + .fail_activity_task(task_token, failure.map(Into::into)), )?; } - activity_result::Status::Canceled(ActivityTaskCancelation { details }) => { + activity_result::Status::Canceled(ar::Cancelation { details }) => { self.runtime.block_on( self.server_gateway - .cancel_activity_task(task_token, details), + .cancel_activity_task(task_token, details.into_payloads()), )?; } } @@ -352,7 +349,7 @@ impl CoreSDK { fn push_lang_commands( &self, run_id: &str, - success: WfActivationSuccess, + success: workflow_completion::Success, ) -> Result> { // Convert to wf commands let cmds = success @@ -420,8 +417,8 @@ pub enum CoreError { MalformedCompletion(TaskCompletion), /// Error buffering commands CantSendCommands(#[from] SendError>), - /// Couldn't interpret command from - UninterpretableCommand(#[from] InconvertibleCommandError), + /// Land SDK sent us an empty workflow command (no variant) + UninterpretableCommand(#[from] EmptyWorkflowCommandErr), /// Underlying error in history processing UnderlyingHistError(#[from] HistoryInfoError), /// Underlying error in state machines: {0:?} @@ -450,19 +447,19 @@ pub enum CoreError { #[cfg(test)] mod test { use super::*; - use crate::protos::temporal::api::command::v1::FailWorkflowExecutionCommandAttributes; + use crate::protos::coresdk::common::UserCodeFailure; + use crate::protos::coresdk::workflow_commands::{ + CancelTimer, CompleteWorkflowExecution, FailWorkflowExecution, StartTimer, + }; use crate::{ machines::test_help::{build_fake_core, FakeCore, TestHistoryBuilder}, protos::{ coresdk::{ - wf_activation_job, FireTimer, StartWorkflow, TaskCompletion, UpdateRandomSeed, - WfActivationJob, + workflow_activation::{wf_activation_job, WfActivationJob}, + workflow_activation::{FireTimer, StartWorkflow, UpdateRandomSeed}, + TaskCompletion, }, temporal::api::{ - command::v1::{ - CancelTimerCommandAttributes, CompleteWorkflowExecutionCommandAttributes, - StartTimerCommandAttributes, - }, enums::v1::{EventType, WorkflowTaskFailedCause}, failure::v1::Failure, workflowservice::v1::RespondWorkflowTaskFailedResponse, @@ -499,7 +496,7 @@ mod test { let task_tok = res.task_token; core.complete_task(TaskCompletion::ok_from_api_attrs( - vec![StartTimerCommandAttributes { + vec![StartTimer { timer_id: "fake_timer".to_string(), ..Default::default() } @@ -517,7 +514,7 @@ mod test { ); let task_tok = res.task_token; core.complete_task(TaskCompletion::ok_from_api_attrs( - vec![CompleteWorkflowExecutionCommandAttributes { result: None }.into()], + vec![CompleteWorkflowExecution { result: vec![] }.into()], task_tok, )) .unwrap(); @@ -546,12 +543,12 @@ mod test { let task_tok = res.task_token; core.complete_task(TaskCompletion::ok_from_api_attrs( vec![ - StartTimerCommandAttributes { + StartTimer { timer_id: timer_1_id.to_string(), ..Default::default() } .into(), - StartTimerCommandAttributes { + StartTimer { timer_id: timer_2_id.to_string(), ..Default::default() } @@ -582,7 +579,7 @@ mod test { ); let task_tok = res.task_token; core.complete_task(TaskCompletion::ok_from_api_attrs( - vec![CompleteWorkflowExecutionCommandAttributes { result: None }.into()], + vec![CompleteWorkflowExecution { result: vec![] }.into()], task_tok, )) .unwrap(); @@ -611,12 +608,12 @@ mod test { let task_tok = res.task_token; core.complete_task(TaskCompletion::ok_from_api_attrs( vec![ - StartTimerCommandAttributes { + StartTimer { timer_id: cancel_timer_id.to_string(), ..Default::default() } .into(), - StartTimerCommandAttributes { + StartTimer { timer_id: timer_id.to_string(), ..Default::default() } @@ -636,11 +633,11 @@ mod test { let task_tok = res.task_token; core.complete_task(TaskCompletion::ok_from_api_attrs( vec![ - CancelTimerCommandAttributes { + CancelTimer { timer_id: cancel_timer_id.to_string(), } .into(), - CompleteWorkflowExecutionCommandAttributes { result: None }.into(), + CompleteWorkflowExecution { result: vec![] }.into(), ], task_tok, )) @@ -687,7 +684,7 @@ mod test { let task_tok = res.task_token; core.complete_task(TaskCompletion::ok_from_api_attrs( - vec![StartTimerCommandAttributes { + vec![StartTimer { timer_id: timer_1_id.to_string(), ..Default::default() } @@ -710,7 +707,7 @@ mod test { ); let task_tok = res.task_token; core.complete_task(TaskCompletion::ok_from_api_attrs( - vec![CompleteWorkflowExecutionCommandAttributes { result: None }.into()], + vec![CompleteWorkflowExecution { result: vec![] }.into()], task_tok, )) .unwrap(); @@ -741,16 +738,16 @@ mod test { let task_tok = res.task_token; core.complete_task(TaskCompletion::ok_from_api_attrs( vec![ - StartTimerCommandAttributes { + StartTimer { timer_id: cancel_timer_id.to_string(), ..Default::default() } .into(), - CancelTimerCommandAttributes { + CancelTimer { timer_id: cancel_timer_id.to_string(), } .into(), - CompleteWorkflowExecutionCommandAttributes { result: None }.into(), + CompleteWorkflowExecution { result: vec![] }.into(), ], task_tok, )) @@ -776,7 +773,7 @@ mod test { let res = core.poll_task(TASK_Q).unwrap(); core.complete_task(TaskCompletion::ok_from_api_attrs( - vec![StartTimerCommandAttributes { + vec![StartTimer { timer_id: timer_id.to_string(), ..Default::default() } @@ -788,8 +785,7 @@ mod test { let res = core.poll_task(TASK_Q).unwrap(); core.complete_task(TaskCompletion::fail( res.task_token, - WorkflowTaskFailedCause::BadBinary, - Failure { + UserCodeFailure { message: "oh noooooooo".to_string(), ..Default::default() }, @@ -805,7 +801,7 @@ mod test { ); // Need to re-issue the start timer command (we are replaying) core.complete_task(TaskCompletion::ok_from_api_attrs( - vec![StartTimerCommandAttributes { + vec![StartTimer { timer_id: timer_id.to_string(), ..Default::default() } @@ -822,7 +818,7 @@ mod test { }] ); core.complete_task(TaskCompletion::ok_from_api_attrs( - vec![CompleteWorkflowExecutionCommandAttributes { result: None }.into()], + vec![CompleteWorkflowExecution { result: vec![] }.into()], res.task_token, )) .unwrap(); @@ -839,7 +835,7 @@ mod test { let res = core.poll_task(TASK_Q).unwrap(); core.complete_task(TaskCompletion::ok_from_api_attrs( - vec![StartTimerCommandAttributes { + vec![StartTimer { timer_id: timer_id.to_string(), ..Default::default() } @@ -850,8 +846,8 @@ mod test { let res = core.poll_task(TASK_Q).unwrap(); core.complete_task(TaskCompletion::ok_from_api_attrs( - vec![FailWorkflowExecutionCommandAttributes { - failure: Some(Failure { + vec![FailWorkflowExecution { + failure: Some(UserCodeFailure { message: "I'm ded".to_string(), ..Default::default() }), diff --git a/src/machines/activity_state_machine.rs b/src/machines/activity_state_machine.rs index 62100cf9a..ec0617349 100644 --- a/src/machines/activity_state_machine.rs +++ b/src/machines/activity_state_machine.rs @@ -1,14 +1,23 @@ -use crate::machines::workflow_machines::MachineResponse; -use crate::machines::{Cancellable, NewMachineWithCommand, WFMachinesAdapter, WFMachinesError}; -use crate::protos::coresdk::{ - activity_result, activity_task, ActivityResult, ActivityTaskSuccess, CancelActivity, - ResolveActivity, StartActivity, -}; -use crate::protos::temporal::api::command::v1::{Command, ScheduleActivityTaskCommandAttributes}; -use crate::protos::temporal::api::common::v1::Payloads; -use crate::protos::temporal::api::enums::v1::{CommandType, EventType}; -use crate::protos::temporal::api::history::v1::{ - history_event, ActivityTaskCompletedEventAttributes, HistoryEvent, +use crate::protos::coresdk::PayloadsExt; +use crate::{ + machines::{ + workflow_machines::MachineResponse, Cancellable, NewMachineWithCommand, WFMachinesAdapter, + WFMachinesError, + }, + protos::coresdk::workflow_commands::ScheduleActivity, + protos::{ + coresdk::{ + activity_result::{self as ar, activity_result, ActivityResult}, + activity_task, + workflow_activation::ResolveActivity, + }, + temporal::api::{ + command::v1::Command, + common::v1::Payloads, + enums::v1::{CommandType, EventType}, + history::v1::{history_event, ActivityTaskCompletedEventAttributes, HistoryEvent}, + }, + }, }; use rustfsm::{fsm, MachineError, StateMachine, TransitionResult}; use std::convert::TryFrom; @@ -96,9 +105,7 @@ impl Default for ActivityCancellationType { } /// Creates a new, scheduled, activity as a [CancellableCommand] -pub(super) fn new_activity( - attribs: ScheduleActivityTaskCommandAttributes, -) -> NewMachineWithCommand { +pub(super) fn new_activity(attribs: ScheduleActivity) -> NewMachineWithCommand { let (activity, add_cmd) = ActivityMachine::new_scheduled(attribs); NewMachineWithCommand { command: add_cmd, @@ -107,7 +114,7 @@ pub(super) fn new_activity( } impl ActivityMachine { - pub(crate) fn new_scheduled(attribs: ScheduleActivityTaskCommandAttributes) -> (Self, Command) { + pub(crate) fn new_scheduled(attribs: ScheduleActivity) -> (Self, Command) { let mut s = Self { state: Created {}.into(), shared_state: SharedState { @@ -182,8 +189,8 @@ impl WFMachinesAdapter for ActivityMachine { ActivityMachineCommand::Complete(result) => vec![ResolveActivity { activity_id: self.shared_state.attrs.activity_id.clone(), result: Some(ActivityResult { - status: Some(activity_result::Status::Completed(ActivityTaskSuccess { - result, + status: Some(activity_result::Status::Completed(ar::Success { + result: Vec::from_payloads(result), })), }), } @@ -204,7 +211,7 @@ impl Cancellable for ActivityMachine { #[derive(Default, Clone)] pub(super) struct SharedState { - attrs: ScheduleActivityTaskCommandAttributes, + attrs: ScheduleActivity, cancellation_type: ActivityCancellationType, } @@ -364,10 +371,10 @@ pub(super) struct Canceled {} #[cfg(test)] mod activity_machine_tests { + use super::*; use crate::machines::test_help::{CommandSender, TestHistoryBuilder, TestWorkflowDriver}; use crate::machines::WorkflowMachines; - use crate::protos::temporal::api::command::v1::CompleteWorkflowExecutionCommandAttributes; - use crate::protos::temporal::api::command::v1::ScheduleActivityTaskCommandAttributes; + use crate::protos::coresdk::workflow_commands::CompleteWorkflowExecution; use crate::protos::temporal::api::enums::v1::CommandType; use crate::test_help::canned_histories; use rstest::{fixture, rstest}; @@ -376,13 +383,13 @@ mod activity_machine_tests { #[fixture] fn activity_happy_hist() -> (TestHistoryBuilder, WorkflowMachines) { let twd = TestWorkflowDriver::new(|mut command_sink: CommandSender| async move { - let activity = ScheduleActivityTaskCommandAttributes { + let activity = ScheduleActivity { activity_id: "activity A".to_string(), ..Default::default() }; - command_sink.activity(activity, true); + command_sink.activity(activity).await; - let complete = CompleteWorkflowExecutionCommandAttributes::default(); + let complete = CompleteWorkflowExecution::default(); command_sink.send(complete.into()); }); diff --git a/src/machines/complete_workflow_state_machine.rs b/src/machines/complete_workflow_state_machine.rs index 300b218df..38fc592ca 100644 --- a/src/machines/complete_workflow_state_machine.rs +++ b/src/machines/complete_workflow_state_machine.rs @@ -1,10 +1,11 @@ +use crate::protos::coresdk::workflow_commands::CompleteWorkflowExecution; use crate::{ machines::{ workflow_machines::MachineResponse, Cancellable, NewMachineWithCommand, WFMachinesAdapter, WFMachinesError, }, protos::temporal::api::{ - command::v1::{Command, CompleteWorkflowExecutionCommandAttributes}, + command::v1::Command, enums::v1::{CommandType, EventType}, history::v1::HistoryEvent, }, @@ -17,7 +18,7 @@ fsm! { name CompleteWorkflowMachine; command CompleteWFCommand; error WFMachinesError; - shared_state CompleteWorkflowExecutionCommandAttributes; + shared_state CompleteWorkflowExecution; Created --(Schedule, shared on_schedule) --> CompleteWorkflowCommandCreated; @@ -34,7 +35,7 @@ pub(super) enum CompleteWFCommand { /// Complete a workflow pub(super) fn complete_workflow( - attribs: CompleteWorkflowExecutionCommandAttributes, + attribs: CompleteWorkflowExecution, ) -> NewMachineWithCommand { let (machine, add_cmd) = CompleteWorkflowMachine::new_scheduled(attribs); NewMachineWithCommand { @@ -45,9 +46,7 @@ pub(super) fn complete_workflow( impl CompleteWorkflowMachine { /// Create a new WF machine and schedule it - pub(crate) fn new_scheduled( - attribs: CompleteWorkflowExecutionCommandAttributes, - ) -> (Self, Command) { + pub(crate) fn new_scheduled(attribs: CompleteWorkflowExecution) -> (Self, Command) { let mut s = Self { state: Created {}.into(), shared_state: attribs, @@ -97,7 +96,7 @@ pub(super) struct Created {} impl Created { pub(super) fn on_schedule( self, - dat: CompleteWorkflowExecutionCommandAttributes, + dat: CompleteWorkflowExecution, ) -> CompleteWorkflowMachineTransition { let cmd = Command { command_type: CommandType::CompleteWorkflowExecution as i32, diff --git a/src/machines/fail_workflow_state_machine.rs b/src/machines/fail_workflow_state_machine.rs index 89235d5c9..3fd6985b4 100644 --- a/src/machines/fail_workflow_state_machine.rs +++ b/src/machines/fail_workflow_state_machine.rs @@ -1,9 +1,9 @@ +use crate::protos::coresdk::workflow_commands::FailWorkflowExecution; use crate::{ machines::{ workflow_machines::MachineResponse, Cancellable, NewMachineWithCommand, ProtoCommand, WFMachinesAdapter, WFMachinesError, }, - protos::temporal::api::command::v1::FailWorkflowExecutionCommandAttributes, protos::temporal::api::enums::v1::CommandType, protos::temporal::api::enums::v1::EventType, protos::temporal::api::history::v1::HistoryEvent, @@ -15,7 +15,7 @@ fsm! { pub(super) name FailWorkflowMachine; command FailWFCommand; error WFMachinesError; - shared_state FailWorkflowExecutionCommandAttributes; + shared_state FailWorkflowExecution; Created --(Schedule, shared on_schedule) --> FailWorkflowCommandCreated; @@ -30,7 +30,7 @@ pub(super) enum FailWFCommand { /// Fail a workflow pub(super) fn fail_workflow( - attribs: FailWorkflowExecutionCommandAttributes, + attribs: FailWorkflowExecution, ) -> NewMachineWithCommand { let (machine, add_cmd) = FailWorkflowMachine::new_scheduled(attribs); NewMachineWithCommand { @@ -41,9 +41,7 @@ pub(super) fn fail_workflow( impl FailWorkflowMachine { /// Create a new WF machine and schedule it - pub(crate) fn new_scheduled( - attribs: FailWorkflowExecutionCommandAttributes, - ) -> (Self, ProtoCommand) { + pub(crate) fn new_scheduled(attribs: FailWorkflowExecution) -> (Self, ProtoCommand) { let mut s = Self { state: Created {}.into(), shared_state: attribs, @@ -64,10 +62,7 @@ impl FailWorkflowMachine { pub(super) struct Created {} impl Created { - pub(super) fn on_schedule( - self, - dat: FailWorkflowExecutionCommandAttributes, - ) -> FailWorkflowMachineTransition { + pub(super) fn on_schedule(self, dat: FailWorkflowExecution) -> FailWorkflowMachineTransition { let cmd = ProtoCommand { command_type: CommandType::FailWorkflowExecution as i32, attributes: Some(dat.into()), diff --git a/src/machines/mod.rs b/src/machines/mod.rs index f371c33ea..75f7d8abd 100644 --- a/src/machines/mod.rs +++ b/src/machines/mod.rs @@ -33,6 +33,9 @@ pub(crate) mod test_help; pub(crate) use workflow_machines::{WFMachinesError, WorkflowMachines}; +use crate::protos::coresdk::workflow_commands::{ + CancelTimer, CompleteWorkflowExecution, FailWorkflowExecution, ScheduleActivity, StartTimer, +}; use crate::protos::temporal::api::command::v1::{ FailWorkflowExecutionCommandAttributes, ScheduleActivityTaskCommandAttributes, }; @@ -40,7 +43,7 @@ use crate::{ core_tracing::VecDisplayer, machines::workflow_machines::MachineResponse, protos::{ - coresdk::{self, command::Variant}, + coresdk::workflow_commands::{workflow_command, WorkflowCommand}, temporal::api::{ command::v1::{ command::Attributes, CancelTimerCommandAttributes, Command, @@ -66,37 +69,29 @@ pub(crate) type ProtoCommand = Command; pub enum WFCommand { /// Returned when we need to wait for the lang sdk to send us something NoCommandsFromLang, - AddActivity(ScheduleActivityTaskCommandAttributes), - AddTimer(StartTimerCommandAttributes), - CancelTimer(CancelTimerCommandAttributes), - CompleteWorkflow(CompleteWorkflowExecutionCommandAttributes), - FailWorkflow(FailWorkflowExecutionCommandAttributes), + AddActivity(ScheduleActivity), + AddTimer(StartTimer), + CancelTimer(CancelTimer), + CompleteWorkflow(CompleteWorkflowExecution), + FailWorkflow(FailWorkflowExecution), } #[derive(thiserror::Error, Debug, derive_more::From)] -#[error("Couldn't convert command")] -pub struct InconvertibleCommandError(pub coresdk::Command); +#[error("Lang provided workflow command with empty variant")] +pub struct EmptyWorkflowCommandErr; -impl TryFrom for WFCommand { - type Error = InconvertibleCommandError; +impl TryFrom for WFCommand { + type Error = EmptyWorkflowCommandErr; - fn try_from(c: coresdk::Command) -> Result { - match c.variant { - Some(Variant::Api(Command { - attributes: Some(attrs), - .. - })) => match attrs { - Attributes::StartTimerCommandAttributes(s) => Ok(WFCommand::AddTimer(s)), - Attributes::CancelTimerCommandAttributes(s) => Ok(WFCommand::CancelTimer(s)), - Attributes::CompleteWorkflowExecutionCommandAttributes(c) => { - Ok(WFCommand::CompleteWorkflow(c)) - } - Attributes::FailWorkflowExecutionCommandAttributes(s) => { - Ok(WFCommand::FailWorkflow(s)) - } - _ => unimplemented!(), - }, - _ => Err(c.into()), + fn try_from(c: WorkflowCommand) -> Result { + match c.variant.ok_or(EmptyWorkflowCommandErr)? { + workflow_command::Variant::StartTimer(s) => Ok(WFCommand::AddTimer(s)), + workflow_command::Variant::CancelTimer(s) => Ok(WFCommand::CancelTimer(s)), + workflow_command::Variant::CompleteWorkflowExecution(c) => { + Ok(WFCommand::CompleteWorkflow(c)) + } + workflow_command::Variant::FailWorkflowExecution(s) => Ok(WFCommand::FailWorkflow(s)), + _ => unimplemented!(), } } } diff --git a/src/machines/test_help/async_workflow_driver.rs b/src/machines/test_help/async_workflow_driver.rs index 55bdade46..76da8b634 100644 --- a/src/machines/test_help/async_workflow_driver.rs +++ b/src/machines/test_help/async_workflow_driver.rs @@ -1,7 +1,8 @@ +use crate::protos::coresdk::workflow_commands::{CancelTimer, ScheduleActivity, StartTimer}; use crate::{ machines::WFCommand, protos::{ - coresdk::{wf_activation_job, FireTimer}, + coresdk::workflow_activation::{wf_activation_job, FireTimer}, temporal::api::command::v1::{CancelTimerCommandAttributes, StartTimerCommandAttributes}, }, workflow::{ActivationListener, WorkflowFetcher}, @@ -126,7 +127,7 @@ impl CommandSender { } /// Request to create a timer - pub fn timer(&mut self, a: StartTimerCommandAttributes) -> impl Future { + pub fn timer(&mut self, a: StartTimer) -> impl Future { let tid = a.timer_id.clone(); let c = WFCommand::AddTimer(a); self.send(c); @@ -137,15 +138,18 @@ impl CommandSender { rx.await } } - /// Cancel a timer pub fn cancel_timer(&self, timer_id: &str) { - let c = WFCommand::CancelTimer(CancelTimerCommandAttributes { + let c = WFCommand::CancelTimer(CancelTimer { timer_id: timer_id.to_owned(), }); self.twd_cache.cancel_timer(timer_id); self.send(c); } + + pub fn activity(&mut self, a: ScheduleActivity) -> impl Future { + async { unimplemented!() } + } } impl TestWorkflowDriver { diff --git a/src/machines/timer_state_machine.rs b/src/machines/timer_state_machine.rs index 56c8696f3..2303d466e 100644 --- a/src/machines/timer_state_machine.rs +++ b/src/machines/timer_state_machine.rs @@ -1,14 +1,15 @@ #![allow(clippy::large_enum_variant)] +use crate::protos::coresdk::workflow_commands::{CancelTimer, StartTimer}; use crate::{ machines::{ workflow_machines::{MachineResponse, WFMachinesError}, Cancellable, NewMachineWithCommand, WFMachinesAdapter, }, protos::{ - coresdk::{CancelTimer, FireTimer, HistoryEventId}, + coresdk::{workflow_activation::FireTimer, HistoryEventId}, temporal::api::{ - command::v1::{CancelTimerCommandAttributes, Command, StartTimerCommandAttributes}, + command::v1::Command, enums::v1::{CommandType, EventType}, history::v1::{history_event, HistoryEvent, TimerFiredEventAttributes}, }, @@ -48,14 +49,12 @@ pub(super) enum TimerMachineCommand { #[derive(Default, Clone)] pub(super) struct SharedState { - attrs: StartTimerCommandAttributes, + attrs: StartTimer, cancelled_before_sent: bool, } /// Creates a new, scheduled, timer as a [CancellableCommand] -pub(super) fn new_timer( - attribs: StartTimerCommandAttributes, -) -> NewMachineWithCommand { +pub(super) fn new_timer(attribs: StartTimer) -> NewMachineWithCommand { let (timer, add_cmd) = TimerMachine::new_scheduled(attribs); NewMachineWithCommand { command: add_cmd, @@ -65,7 +64,7 @@ pub(super) fn new_timer( impl TimerMachine { /// Create a new timer and immediately schedule it - pub(crate) fn new_scheduled(attribs: StartTimerCommandAttributes) -> (Self, Command) { + pub(crate) fn new_scheduled(attribs: StartTimer) -> (Self, Command) { let mut s = Self::new(attribs); s.on_event_mut(TimerMachineEvents::Schedule) .expect("Scheduling timers doesn't fail"); @@ -76,7 +75,7 @@ impl TimerMachine { (s, cmd) } - fn new(attribs: StartTimerCommandAttributes) -> Self { + fn new(attribs: StartTimer) -> Self { Self { state: Created {}.into(), shared_state: SharedState { @@ -207,7 +206,7 @@ impl StartCommandRecorded { let cmd = Command { command_type: CommandType::CancelTimer as i32, attributes: Some( - CancelTimerCommandAttributes { + CancelTimer { timer_id: dat.attrs.timer_id, } .into(), @@ -233,10 +232,10 @@ impl WFMachinesAdapter for TimerMachine { timer_id: self.shared_state.attrs.timer_id.clone(), } .into()], - TimerMachineCommand::Canceled => vec![CancelTimer { - timer_id: self.shared_state.attrs.timer_id.clone(), - } - .into()], + // We don't issue activations for timer cancellations. Lang SDK is expected to cancel + // it's own timers when user calls cancel, and they cannot be cancelled by any other + // means. + TimerMachineCommand::Canceled => vec![], TimerMachineCommand::IssueCancelCmd(c) => vec![MachineResponse::IssueNewCommand(c)], }) } @@ -264,7 +263,7 @@ mod test { test_help::{CommandSender, TestHistoryBuilder, TestWorkflowDriver}, workflow_machines::WorkflowMachines, }, - protos::temporal::api::command::v1::CompleteWorkflowExecutionCommandAttributes, + protos::coresdk::workflow_commands::CompleteWorkflowExecution, test_help::canned_histories, }; use rstest::{fixture, rstest}; @@ -284,13 +283,13 @@ mod test { task, hence no extra loop. */ let twd = TestWorkflowDriver::new(|mut command_sink: CommandSender| async move { - let timer = StartTimerCommandAttributes { + let timer = StartTimer { timer_id: "timer1".to_string(), start_to_fire_timeout: Some(Duration::from_secs(5).into()), }; command_sink.timer(timer).await; - let complete = CompleteWorkflowExecutionCommandAttributes::default(); + let complete = CompleteWorkflowExecution::default(); command_sink.send(complete.into()); }); @@ -342,7 +341,7 @@ mod test { #[test] fn mismatched_timer_ids_errors() { let twd = TestWorkflowDriver::new(|mut command_sink: CommandSender| async move { - let timer = StartTimerCommandAttributes { + let timer = StartTimer { timer_id: "realid".to_string(), start_to_fire_timeout: Some(Duration::from_secs(5).into()), }; @@ -366,12 +365,12 @@ mod test { #[fixture] fn cancellation_setup() -> (TestHistoryBuilder, WorkflowMachines) { let twd = TestWorkflowDriver::new(|mut cmd_sink: CommandSender| async move { - let cancel_timer_fut = cmd_sink.timer(StartTimerCommandAttributes { + let cancel_timer_fut = cmd_sink.timer(StartTimer { timer_id: "cancel_timer".to_string(), start_to_fire_timeout: Some(Duration::from_secs(500).into()), }); cmd_sink - .timer(StartTimerCommandAttributes { + .timer(StartTimer { timer_id: "wait_timer".to_string(), start_to_fire_timeout: Some(Duration::from_secs(5).into()), }) @@ -380,7 +379,7 @@ mod test { cmd_sink.cancel_timer("cancel_timer"); cancel_timer_fut.await; - let complete = CompleteWorkflowExecutionCommandAttributes::default(); + let complete = CompleteWorkflowExecution::default(); cmd_sink.send(complete.into()); }); @@ -431,7 +430,7 @@ mod test { #[test] fn cancel_before_sent_to_server() { let twd = TestWorkflowDriver::new(|mut cmd_sink: CommandSender| async move { - let cancel_timer_fut = cmd_sink.timer(StartTimerCommandAttributes { + let cancel_timer_fut = cmd_sink.timer(StartTimer { timer_id: "cancel_timer".to_string(), start_to_fire_timeout: Some(Duration::from_secs(500).into()), }); @@ -439,7 +438,7 @@ mod test { cmd_sink.cancel_timer("cancel_timer"); cancel_timer_fut.await; - let complete = CompleteWorkflowExecutionCommandAttributes::default(); + let complete = CompleteWorkflowExecution::default(); cmd_sink.send(complete.into()); }); diff --git a/src/machines/workflow_machines.rs b/src/machines/workflow_machines.rs index b7aef6e43..ab2fe108c 100644 --- a/src/machines/workflow_machines.rs +++ b/src/machines/workflow_machines.rs @@ -1,22 +1,25 @@ -use crate::machines::activity_state_machine::new_activity; -use crate::protos::coresdk::activity_task; -use crate::workflow::{DrivenWorkflow, WorkflowFetcher}; use crate::{ core_tracing::VecDisplayer, machines::{ - complete_workflow_state_machine::complete_workflow, + activity_state_machine::new_activity, complete_workflow_state_machine::complete_workflow, fail_workflow_state_machine::fail_workflow, timer_state_machine::new_timer, workflow_task_state_machine::WorkflowTaskMachine, NewMachineWithCommand, ProtoCommand, TemporalStateMachine, WFCommand, }, protos::{ - coresdk::{wf_activation_job, ActivityTask, StartWorkflow, UpdateRandomSeed, WfActivation}, + coresdk::{ + workflow_activation::{ + wf_activation_job, StartWorkflow, UpdateRandomSeed, WfActivation, + }, + PayloadsExt, + }, temporal::api::{ enums::v1::{CommandType, EventType}, history::v1::{history_event, HistoryEvent}, }, }, protosext::HistoryInfo, + workflow::{DrivenWorkflow, WorkflowFetcher}, }; use slotmap::SlotMap; use std::{ @@ -332,7 +335,7 @@ impl WorkflowMachines { .map(|wt| wt.name.clone()) .unwrap_or_default(), workflow_id: self.workflow_id.clone(), - arguments: attrs.input.clone(), + arguments: Vec::from_payloads(attrs.input.clone()), randomness_seed: str_to_randomness_seed( &attrs.original_execution_run_id, ), @@ -359,7 +362,7 @@ impl WorkflowMachines { attrs, )) = &event.attributes { - self.drive_me.signal(attrs.clone()); + self.drive_me.signal(attrs.clone().into()); } else { // err } diff --git a/src/protos/mod.rs b/src/protos/mod.rs index 87d82b5a9..e914edc7b 100644 --- a/src/protos/mod.rs +++ b/src/protos/mod.rs @@ -9,11 +9,37 @@ pub mod coresdk { //! Contains all protobufs relating to communication between core and lang-specific SDKs include!("coresdk.rs"); + pub mod activity_task { + include!("coresdk.activity_task.rs"); + } + pub mod activity_result { + include!("coresdk.activity_result.rs"); + } + pub mod common { + include!("coresdk.common.rs"); + } + pub mod workflow_activation { + include!("coresdk.workflow_activation.rs"); + } + pub mod workflow_completion { + include!("coresdk.workflow_completion.rs"); + } + pub mod workflow_commands { + include!("coresdk.workflow_commands.rs"); + } + use super::temporal::api::command::v1 as api_command; use super::temporal::api::command::v1::Command as ApiCommand; - use super::temporal::api::enums::v1::WorkflowTaskFailedCause; use super::temporal::api::failure::v1::Failure; - use command::Variant; + use crate::protos::coresdk::common::{Payload, UserCodeFailure}; + use crate::protos::coresdk::workflow_activation::SignalWorkflow; + use crate::protos::temporal::api::common::v1::Payloads; + use crate::protos::temporal::api::failure::v1::failure::FailureInfo; + use crate::protos::temporal::api::failure::v1::ApplicationFailureInfo; + use crate::protos::temporal::api::history::v1::WorkflowExecutionSignaledEventAttributes; + use workflow_activation::{wf_activation_job, WfActivation, WfActivationJob}; + use workflow_commands::{workflow_command, WorkflowCommand}; + use workflow_completion::{wf_activation_completion, WfActivationCompletion}; pub type HistoryEventId = i64; @@ -50,27 +76,23 @@ pub mod coresdk { } } - impl From> for WfActivationSuccess { - fn from(v: Vec) -> Self { - WfActivationSuccess { - commands: v - .into_iter() - .map(|cmd| Command { - variant: Some(Variant::Api(cmd)), - }) - .collect(), - } + impl From> for workflow_completion::Success { + fn from(v: Vec) -> Self { + Self { commands: v } } } impl TaskCompletion { /// Build a successful completion from some api command attributes and a task token pub fn ok_from_api_attrs( - cmds: Vec, + cmds: Vec, task_token: Vec, ) -> Self { - let cmds: Vec = cmds.into_iter().map(Into::into).collect(); - let success: WfActivationSuccess = cmds.into(); + let cmds: Vec<_> = cmds + .into_iter() + .map(|c| WorkflowCommand { variant: Some(c) }) + .collect(); + let success: workflow_completion::Success = cmds.into(); TaskCompletion { task_token, variant: Some(task_completion::Variant::Workflow(WfActivationCompletion { @@ -79,13 +101,12 @@ pub mod coresdk { } } - pub fn fail(task_token: Vec, cause: WorkflowTaskFailedCause, failure: Failure) -> Self { + pub fn fail(task_token: Vec, failure: UserCodeFailure) -> Self { Self { task_token, variant: Some(task_completion::Variant::Workflow(WfActivationCompletion { status: Some(wf_activation_completion::Status::Failed( - WfActivationFailure { - cause: cause as i32, + workflow_completion::Failure { failure: Some(failure), }, )), @@ -93,6 +114,76 @@ pub mod coresdk { } } } + + impl From for Failure { + fn from(f: UserCodeFailure) -> Self { + Self { + message: f.message, + source: f.source, + stack_trace: f.stack_trace, + cause: f.cause.map(|b| Box::new((*b).into())), + failure_info: Some(FailureInfo::ApplicationFailureInfo( + ApplicationFailureInfo { + r#type: f.r#type, + non_retryable: f.non_retryable, + details: None, + }, + )), + } + } + } + + impl From for super::temporal::api::common::v1::Payload { + fn from(p: Payload) -> Self { + Self { + metadata: p.metadata, + data: p.data, + } + } + } + + impl From for common::Payload { + fn from(p: super::temporal::api::common::v1::Payload) -> Self { + Self { + metadata: p.metadata, + data: p.data, + } + } + } + + pub trait PayloadsExt { + fn into_payloads(self) -> Option; + fn from_payloads(p: Option) -> Self; + } + + impl PayloadsExt for Vec { + fn into_payloads(self) -> Option { + if self.is_empty() { + None + } else { + Some(Payloads { + payloads: self.into_iter().map(Into::into).collect(), + }) + } + } + + fn from_payloads(p: Option) -> Self { + match p { + None => vec![], + Some(p) => p.payloads.into_iter().map(Into::into).collect(), + } + } + } + + impl From for SignalWorkflow { + fn from(a: WorkflowExecutionSignaledEventAttributes) -> Self { + Self { + signal_name: a.signal_name, + input: Vec::from_payloads(a.input), + identity: a.identity, + } + } + } } // No need to lint these @@ -104,10 +195,14 @@ pub mod temporal { pub mod command { pub mod v1 { include!("temporal.api.command.v1.rs"); + use crate::protos::coresdk::workflow_commands::{ + CompleteWorkflowExecution, ScheduleActivity, + }; + use crate::protos::coresdk::{workflow_commands, PayloadsExt}; + use crate::protos::temporal::api::common::v1::ActivityType; use crate::protos::temporal::api::enums::v1::CommandType; use command::Attributes; use std::fmt::{Display, Formatter}; - impl From for Command { fn from(c: command::Attributes) -> Self { match c { @@ -139,6 +234,65 @@ pub mod temporal { write!(f, "{:?}", ct) } } + + impl From for command::Attributes { + fn from(s: workflow_commands::StartTimer) -> Self { + Self::StartTimerCommandAttributes(StartTimerCommandAttributes { + timer_id: s.timer_id, + start_to_fire_timeout: s.start_to_fire_timeout, + }) + } + } + + impl From for command::Attributes { + fn from(s: workflow_commands::CancelTimer) -> Self { + Self::CancelTimerCommandAttributes(CancelTimerCommandAttributes { + timer_id: s.timer_id, + }) + } + } + + impl From for command::Attributes { + fn from(s: workflow_commands::ScheduleActivity) -> Self { + Self::ScheduleActivityTaskCommandAttributes( + ScheduleActivityTaskCommandAttributes { + activity_id: s.activity_id, + activity_type: Some(ActivityType { + name: s.activity_type, + }), + namespace: s.namespace, + task_queue: Some(s.task_queue.into()), + header: Some(s.header_fields.into()), + input: s.arguments.into_payloads(), + schedule_to_close_timeout: s.schedule_to_close_timeout, + schedule_to_start_timeout: s.schedule_to_start_timeout, + start_to_close_timeout: s.start_to_close_timeout, + heartbeat_timeout: s.heartbeat_timeout, + retry_policy: s.retry_policy.map(Into::into), + }, + ) + } + } + + impl From for command::Attributes { + fn from(c: workflow_commands::CompleteWorkflowExecution) -> Self { + Self::CompleteWorkflowExecutionCommandAttributes( + CompleteWorkflowExecutionCommandAttributes { + result: c.result.into_payloads(), + }, + ) + } + } + + impl From for command::Attributes { + fn from(c: workflow_commands::FailWorkflowExecution) -> Self { + Self::FailWorkflowExecutionCommandAttributes( + FailWorkflowExecutionCommandAttributes { + failure: c.failure.map(Into::into), + }, + ) + } + } } } pub mod enums { @@ -158,7 +312,29 @@ pub mod temporal { } pub mod common { pub mod v1 { + use crate::protos::coresdk::common; + use std::collections::HashMap; include!("temporal.api.common.v1.rs"); + + impl From> for Header { + fn from(h: HashMap) -> Self { + Self { + fields: h.into_iter().map(|(k, v)| (k, v.into())).collect(), + } + } + } + + impl From for RetryPolicy { + fn from(r: common::RetryPolicy) -> Self { + Self { + initial_interval: r.initial_interval, + backoff_coefficient: r.backoff_coefficient, + maximum_interval: r.maximum_interval, + maximum_attempts: r.maximum_attempts, + non_retryable_error_types: r.non_retryable_error_types, + } + } + } } } pub mod history { @@ -336,7 +512,17 @@ pub mod temporal { pub mod taskqueue { pub mod v1 { + use crate::protos::temporal::api::enums::v1::TaskQueueKind; include!("temporal.api.taskqueue.v1.rs"); + + impl From for TaskQueue { + fn from(name: String) -> Self { + Self { + name, + kind: TaskQueueKind::Normal as i32, + } + } + } } } diff --git a/src/workflow/driven_workflow.rs b/src/workflow/driven_workflow.rs index a541910a8..6a16c0144 100644 --- a/src/workflow/driven_workflow.rs +++ b/src/workflow/driven_workflow.rs @@ -1,11 +1,14 @@ -use crate::protos::coresdk::{activity_task, SignalWorkflow}; use crate::{ machines::WFCommand, - protos::coresdk::wf_activation_job, - protos::coresdk::WfActivationJob, - protos::temporal::api::history::v1::{ - WorkflowExecutionCanceledEventAttributes, WorkflowExecutionSignaledEventAttributes, - WorkflowExecutionStartedEventAttributes, + protos::{ + coresdk::{ + activity_task, + workflow_activation::{wf_activation_job, SignalWorkflow, WfActivationJob}, + }, + temporal::api::history::v1::{ + WorkflowExecutionCanceledEventAttributes, WorkflowExecutionSignaledEventAttributes, + WorkflowExecutionStartedEventAttributes, + }, }, }; use std::collections::VecDeque; @@ -54,10 +57,8 @@ impl DrivenWorkflow { } /// Signal the workflow - pub fn signal(&mut self, attribs: WorkflowExecutionSignaledEventAttributes) { - self.send_job(wf_activation_job::Variant::SignalWorkflow(SignalWorkflow { - signal: Some(attribs), - })) + pub fn signal(&mut self, signal: SignalWorkflow) { + self.send_job(wf_activation_job::Variant::SignalWorkflow(signal)) } /// Cancel the workflow diff --git a/src/workflow/mod.rs b/src/workflow/mod.rs index 67413bbc3..ca57345e7 100644 --- a/src/workflow/mod.rs +++ b/src/workflow/mod.rs @@ -9,7 +9,7 @@ pub(crate) use driven_workflow::{ActivationListener, DrivenWorkflow, WorkflowFet use crate::{ machines::{ProtoCommand, WFCommand, WorkflowMachines}, protos::{ - coresdk::WfActivation, + coresdk::workflow_activation::WfActivation, temporal::api::{history::v1::History, workflowservice::v1::PollWorkflowTaskQueueResponse}, }, protosext::HistoryInfo, From 4a9fc05e3c899d7c3f7cbead784cde16ab73ddfb Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Mon, 22 Mar 2021 15:09:03 -0700 Subject: [PATCH 38/45] Cleanup unused imports --- src/lib.rs | 7 ++----- src/machines/mod.rs | 13 ++----------- src/machines/test_help/async_workflow_driver.rs | 3 +-- src/protos/mod.rs | 6 +----- src/workflow/driven_workflow.rs | 8 ++------ 5 files changed, 8 insertions(+), 29 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d67cc9714..7c20aa48c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,8 +39,7 @@ use crate::{ PayloadsExt, Task, TaskCompletion, }, temporal::api::{ - enums::v1::WorkflowTaskFailedCause, - workflowservice::v1::{PollActivityTaskQueueResponse, PollWorkflowTaskQueueResponse}, + enums::v1::WorkflowTaskFailedCause, workflowservice::v1::PollWorkflowTaskQueueResponse, }, }, protosext::{fmt_task_token, HistoryInfoError}, @@ -460,9 +459,7 @@ mod test { TaskCompletion, }, temporal::api::{ - enums::v1::{EventType, WorkflowTaskFailedCause}, - failure::v1::Failure, - workflowservice::v1::RespondWorkflowTaskFailedResponse, + enums::v1::EventType, workflowservice::v1::RespondWorkflowTaskFailedResponse, }, }, test_help::canned_histories, diff --git a/src/machines/mod.rs b/src/machines/mod.rs index 75f7d8abd..6acb80655 100644 --- a/src/machines/mod.rs +++ b/src/machines/mod.rs @@ -36,22 +36,13 @@ pub(crate) use workflow_machines::{WFMachinesError, WorkflowMachines}; use crate::protos::coresdk::workflow_commands::{ CancelTimer, CompleteWorkflowExecution, FailWorkflowExecution, ScheduleActivity, StartTimer, }; -use crate::protos::temporal::api::command::v1::{ - FailWorkflowExecutionCommandAttributes, ScheduleActivityTaskCommandAttributes, -}; + use crate::{ core_tracing::VecDisplayer, machines::workflow_machines::MachineResponse, protos::{ coresdk::workflow_commands::{workflow_command, WorkflowCommand}, - temporal::api::{ - command::v1::{ - command::Attributes, CancelTimerCommandAttributes, Command, - CompleteWorkflowExecutionCommandAttributes, StartTimerCommandAttributes, - }, - enums::v1::CommandType, - history::v1::HistoryEvent, - }, + temporal::api::{command::v1::Command, enums::v1::CommandType, history::v1::HistoryEvent}, }, }; use prost::alloc::fmt::Formatter; diff --git a/src/machines/test_help/async_workflow_driver.rs b/src/machines/test_help/async_workflow_driver.rs index 76da8b634..3328e29cc 100644 --- a/src/machines/test_help/async_workflow_driver.rs +++ b/src/machines/test_help/async_workflow_driver.rs @@ -3,7 +3,6 @@ use crate::{ machines::WFCommand, protos::{ coresdk::workflow_activation::{wf_activation_job, FireTimer}, - temporal::api::command::v1::{CancelTimerCommandAttributes, StartTimerCommandAttributes}, }, workflow::{ActivationListener, WorkflowFetcher}, }; @@ -147,7 +146,7 @@ impl CommandSender { self.send(c); } - pub fn activity(&mut self, a: ScheduleActivity) -> impl Future { + pub fn activity(&mut self, _a: ScheduleActivity) -> impl Future { async { unimplemented!() } } } diff --git a/src/protos/mod.rs b/src/protos/mod.rs index e914edc7b..f45318c5a 100644 --- a/src/protos/mod.rs +++ b/src/protos/mod.rs @@ -28,8 +28,6 @@ pub mod coresdk { include!("coresdk.workflow_commands.rs"); } - use super::temporal::api::command::v1 as api_command; - use super::temporal::api::command::v1::Command as ApiCommand; use super::temporal::api::failure::v1::Failure; use crate::protos::coresdk::common::{Payload, UserCodeFailure}; use crate::protos::coresdk::workflow_activation::SignalWorkflow; @@ -195,9 +193,7 @@ pub mod temporal { pub mod command { pub mod v1 { include!("temporal.api.command.v1.rs"); - use crate::protos::coresdk::workflow_commands::{ - CompleteWorkflowExecution, ScheduleActivity, - }; + use crate::protos::coresdk::{workflow_commands, PayloadsExt}; use crate::protos::temporal::api::common::v1::ActivityType; use crate::protos::temporal::api::enums::v1::CommandType; diff --git a/src/workflow/driven_workflow.rs b/src/workflow/driven_workflow.rs index 6a16c0144..978c0b0f5 100644 --- a/src/workflow/driven_workflow.rs +++ b/src/workflow/driven_workflow.rs @@ -1,13 +1,9 @@ use crate::{ machines::WFCommand, protos::{ - coresdk::{ - activity_task, - workflow_activation::{wf_activation_job, SignalWorkflow, WfActivationJob}, - }, + coresdk::workflow_activation::{wf_activation_job, SignalWorkflow, WfActivationJob}, temporal::api::history::v1::{ - WorkflowExecutionCanceledEventAttributes, WorkflowExecutionSignaledEventAttributes, - WorkflowExecutionStartedEventAttributes, + WorkflowExecutionCanceledEventAttributes, WorkflowExecutionStartedEventAttributes, }, }, }; From 1595d8640f48dacada989504c1001e0f55648206 Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Mon, 22 Mar 2021 15:38:20 -0700 Subject: [PATCH 39/45] Fix problems from merge --- protos/local/workflow_commands.proto | 6 +- src/lib.rs | 7 +- src/machines/activity_state_machine.rs | 12 +-- src/machines/mod.rs | 4 +- .../test_help/async_workflow_driver.rs | 6 +- src/machines/workflow_machines.rs | 4 - src/protos/mod.rs | 97 +++++++++++++------ 7 files changed, 86 insertions(+), 50 deletions(-) diff --git a/protos/local/workflow_commands.proto b/protos/local/workflow_commands.proto index 7f0541154..93f7cfea8 100644 --- a/protos/local/workflow_commands.proto +++ b/protos/local/workflow_commands.proto @@ -16,7 +16,7 @@ message WorkflowCommand { StartTimer start_timer = 1; ScheduleActivity schedule_activity = 2; QueryResult respond_to_query = 3; - RequestActivityCancellation request_activity_cancellation = 4; + RequestCancelActivity request_cancel_activity = 4; CancelTimer cancel_timer = 5; CompleteWorkflowExecution complete_workflow_execution = 6; FailWorkflowExecution fail_workflow_execution = 7; @@ -72,6 +72,10 @@ message ScheduleActivity { common.RetryPolicy retry_policy = 11; } +message RequestCancelActivity { + string activity_id = 1; +} + message QueryResult { oneof variant { QuerySuccess succeeded = 1; diff --git a/src/lib.rs b/src/lib.rs index fd4f07923..bbfef3a60 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,6 +28,7 @@ pub use pollers::{ pub use url::Url; use crate::machines::EmptyWorkflowCommandErr; +use crate::protos::coresdk::task; use crate::{ machines::{ProtoCommand, WFCommand, WFMachinesError}, pending_activations::{PendingActivation, PendingActivations}, @@ -448,7 +449,7 @@ mod test { use super::*; use crate::protos::coresdk::common::UserCodeFailure; use crate::protos::coresdk::workflow_commands::{ - CancelTimer, CompleteWorkflowExecution, FailWorkflowExecution, StartTimer, + CancelTimer, CompleteWorkflowExecution, FailWorkflowExecution, ScheduleActivity, StartTimer, }; use crate::{ machines::test_help::{build_fake_core, FakeCore, TestHistoryBuilder}, @@ -541,7 +542,7 @@ mod test { let task_tok = res.task_token; core.complete_task(TaskCompletion::ok_from_api_attrs( - vec![ScheduleActivityTaskCommandAttributes { + vec![ScheduleActivity { activity_id: "fake_activity".to_string(), ..Default::default() } @@ -559,7 +560,7 @@ mod test { ); let task_tok = res.task_token; core.complete_task(TaskCompletion::ok_from_api_attrs( - vec![CompleteWorkflowExecutionCommandAttributes { result: None }.into()], + vec![CompleteWorkflowExecution { result: vec![] }.into()], task_tok, )) .unwrap(); diff --git a/src/machines/activity_state_machine.rs b/src/machines/activity_state_machine.rs index cbc0d359b..1bc7eb76b 100644 --- a/src/machines/activity_state_machine.rs +++ b/src/machines/activity_state_machine.rs @@ -101,9 +101,7 @@ impl Default for ActivityCancellationType { } /// Creates a new activity state machine and a command to schedule it on the server. -pub(super) fn new_activity( - attribs: ScheduleActivityTaskCommandAttributes, -) -> NewMachineWithCommand { +pub(super) fn new_activity(attribs: ScheduleActivity) -> NewMachineWithCommand { let (activity, add_cmd) = ActivityMachine::new_scheduled(attribs); NewMachineWithCommand { command: add_cmd, @@ -113,7 +111,7 @@ pub(super) fn new_activity( impl ActivityMachine { /// Create a new activity and immediately schedule it. - pub(crate) fn new_scheduled(attribs: ScheduleActivityTaskCommandAttributes) -> (Self, Command) { + pub(crate) fn new_scheduled(attribs: ScheduleActivity) -> (Self, Command) { let mut s = Self { state: Created {}.into(), shared_state: SharedState { @@ -188,8 +186,8 @@ impl WFMachinesAdapter for ActivityMachine { ActivityMachineCommand::Complete(result) => vec![ResolveActivity { activity_id: self.shared_state.attrs.activity_id.clone(), result: Some(ActivityResult { - status: Some(activity_result::Status::Completed(ActivityTaskSuccess { - result, + status: Some(activity_result::Status::Completed(ar::Success { + result: Vec::from_payloads(result), })), }), } @@ -210,7 +208,7 @@ impl Cancellable for ActivityMachine { #[derive(Default, Clone)] pub(super) struct SharedState { - attrs: ScheduleActivityTaskCommandAttributes, + attrs: ScheduleActivity, cancellation_type: ActivityCancellationType, } diff --git a/src/machines/mod.rs b/src/machines/mod.rs index 936551386..07a9b84a4 100644 --- a/src/machines/mod.rs +++ b/src/machines/mod.rs @@ -34,9 +34,9 @@ pub(crate) mod test_help; pub(crate) use workflow_machines::{WFMachinesError, WorkflowMachines}; use crate::protos::coresdk::workflow_commands::{ - CancelTimer, CompleteWorkflowExecution, FailWorkflowExecution, ScheduleActivity, StartTimer, + CancelTimer, CompleteWorkflowExecution, FailWorkflowExecution, RequestCancelActivity, + ScheduleActivity, StartTimer, }; - use crate::{ core_tracing::VecDisplayer, machines::workflow_machines::MachineResponse, diff --git a/src/machines/test_help/async_workflow_driver.rs b/src/machines/test_help/async_workflow_driver.rs index c85e15577..ae114bc3b 100644 --- a/src/machines/test_help/async_workflow_driver.rs +++ b/src/machines/test_help/async_workflow_driver.rs @@ -1,5 +1,5 @@ use crate::machines::workflow_machines::CommandID; -use crate::protos::coresdk::workflow_commands::{CancelTimer, ScheduleActivity, StartTimer}; +use crate::protos::coresdk::workflow_commands::{CancelTimer, StartTimer}; use crate::{ machines::WFCommand, protos::coresdk::workflow_activation::{wf_activation_job, FireTimer}, @@ -144,10 +144,6 @@ impl CommandSender { self.twd_cache.cancel_timer(Timer(timer_id.to_string())); self.send(c); } - - pub fn activity(&mut self, _a: ScheduleActivity) -> impl Future { - async { unimplemented!() } - } } impl TestWorkflowDriver { diff --git a/src/machines/workflow_machines.rs b/src/machines/workflow_machines.rs index 1c49f0d89..ca29d88f2 100644 --- a/src/machines/workflow_machines.rs +++ b/src/machines/workflow_machines.rs @@ -62,9 +62,6 @@ pub(crate) struct WorkflowMachines { /// TODO: Make this apply to *all* cancellable things, once we've added more. Key can be enum. id_to_machine: HashMap, - /// TODO document - activity_id_to_machine: HashMap, - /// Queued commands which have been produced by machines and await processing / being sent to /// the server. commands: VecDeque, @@ -157,7 +154,6 @@ impl WorkflowMachines { all_machines: Default::default(), machines_by_event_id: Default::default(), id_to_machine: Default::default(), - activity_id_to_machine: Default::default(), commands: Default::default(), current_wf_task_commands: Default::default(), } diff --git a/src/protos/mod.rs b/src/protos/mod.rs index dca36e1df..b72ac7980 100644 --- a/src/protos/mod.rs +++ b/src/protos/mod.rs @@ -29,10 +29,10 @@ pub mod coresdk { } use super::temporal::api::failure::v1::Failure; + use crate::protos::coresdk::activity_result::ActivityResult; use crate::protos::coresdk::common::{Payload, UserCodeFailure}; use crate::protos::coresdk::workflow_activation::SignalWorkflow; - use crate::protos::temporal::api::common::v1::Payloads; - use crate::protos::temporal::api::common::v1::Payloads; + use crate::protos::temporal::api::common::v1::{Payloads, WorkflowExecution}; use crate::protos::temporal::api::failure::v1::failure::FailureInfo; use crate::protos::temporal::api::failure::v1::ApplicationFailureInfo; use crate::protos::temporal::api::history::v1::WorkflowExecutionSignaledEventAttributes; @@ -59,7 +59,7 @@ pub mod coresdk { } } /// Returns any contained jobs if this task was a wf activation and it had some - pub fn get_activity_variant(&self) -> Option { + pub fn get_activity_variant(&self) -> Option { if let Some(task::Variant::Activity(a)) = &self.variant { a.variant.clone() } else { @@ -108,13 +108,13 @@ pub mod coresdk { } } - pub fn ok_activity(result: Option, task_token: Vec) -> Self { + pub fn ok_activity(result: Vec, task_token: Vec) -> Self { TaskCompletion { task_token, variant: Some(task_completion::Variant::Activity(ActivityResult { - status: Some(activity_result::Status::Completed(ActivityTaskSuccess { - result, - })), + status: Some(activity_result::activity_result::Status::Completed( + activity_result::Success { result }, + )), })), } } @@ -202,6 +202,15 @@ pub mod coresdk { } } } + + impl From for common::WorkflowExecution { + fn from(w: WorkflowExecution) -> Self { + Self { + workflow_id: w.workflow_id, + run_id: w.run_id, + } + } + } } // No need to lint these @@ -214,12 +223,17 @@ pub mod temporal { pub mod v1 { include!("temporal.api.command.v1.rs"); - use crate::protos::coresdk::{workflow_commands, PayloadsExt}; - use crate::protos::temporal::api::common::v1::ActivityType; - use crate::protos::temporal::api::enums::v1::CommandType; - use crate::protos::temporal::api::workflowservice::v1::PollActivityTaskQueueResponse; + use crate::protos::{ + coresdk::{ + activity_task, activity_task::ActivityTask, workflow_commands, PayloadsExt, + }, + temporal::api::common::v1::ActivityType, + temporal::api::enums::v1::CommandType, + temporal::api::workflowservice::v1::PollActivityTaskQueueResponse, + }; use command::Attributes; use std::fmt::{Display, Formatter}; + impl From for Command { fn from(c: command::Attributes) -> Self { match c { @@ -256,23 +270,32 @@ pub mod temporal { fn from(r: PollActivityTaskQueueResponse) -> Self { ActivityTask { activity_id: r.activity_id, - variant: Some(activity_task::Variant::Start(StartActivity { - workflow_namespace: r.workflow_namespace, - workflow_type: r.workflow_type, - workflow_execution: r.workflow_execution, - activity_type: r.activity_type, - header: r.header, - input: r.input, - heartbeat_details: r.heartbeat_details, - scheduled_time: r.scheduled_time, - current_attempt_scheduled_time: r.current_attempt_scheduled_time, - started_time: r.started_time, - attempt: r.attempt, - schedule_to_close_timeout: r.schedule_to_close_timeout, - start_to_close_timeout: r.start_to_close_timeout, - heartbeat_timeout: r.heartbeat_timeout, - retry_policy: r.retry_policy, - })), + variant: Some(activity_task::activity_task::Variant::Start( + activity_task::Start { + workflow_namespace: r.workflow_namespace, + workflow_type: r + .workflow_type + .map(|wt| wt.name) + .unwrap_or("".to_string()), + workflow_execution: r.workflow_execution.map(Into::into), + activity_type: r + .activity_type + .map(|at| at.name) + .unwrap_or("".to_string()), + header_fields: r.header.map(Into::into).unwrap_or_default(), + input: Vec::from_payloads(r.input), + heartbeat_details: Vec::from_payloads(r.heartbeat_details), + scheduled_time: r.scheduled_time, + current_attempt_scheduled_time: r + .current_attempt_scheduled_time, + started_time: r.started_time, + attempt: r.attempt, + schedule_to_close_timeout: r.schedule_to_close_timeout, + start_to_close_timeout: r.start_to_close_timeout, + heartbeat_timeout: r.heartbeat_timeout, + retry_policy: r.retry_policy.map(Into::into), + }, + )), } } } @@ -374,6 +397,12 @@ pub mod temporal { } } + impl From
for HashMap { + fn from(h: Header) -> Self { + h.fields.into_iter().map(|(k, v)| (k, v.into())).collect() + } + } + impl From for RetryPolicy { fn from(r: common::RetryPolicy) -> Self { Self { @@ -385,6 +414,18 @@ pub mod temporal { } } } + + impl From for common::RetryPolicy { + fn from(r: RetryPolicy) -> Self { + Self { + initial_interval: r.initial_interval, + backoff_coefficient: r.backoff_coefficient, + maximum_interval: r.maximum_interval, + maximum_attempts: r.maximum_attempts, + non_retryable_error_types: r.non_retryable_error_types, + } + } + } } } pub mod history { From 9c222c23b589ccb9947a70d6123e17b41ee6f0af Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Mon, 22 Mar 2021 16:20:51 -0700 Subject: [PATCH 40/45] Clippy fix --- src/protos/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/protos/mod.rs b/src/protos/mod.rs index b72ac7980..39580156e 100644 --- a/src/protos/mod.rs +++ b/src/protos/mod.rs @@ -9,9 +9,11 @@ pub mod coresdk { //! Contains all protobufs relating to communication between core and lang-specific SDKs include!("coresdk.rs"); + #[allow(clippy::module_inception)] pub mod activity_task { include!("coresdk.activity_task.rs"); } + #[allow(clippy::module_inception)] pub mod activity_result { include!("coresdk.activity_result.rs"); } From 47b9f43cf2e06f868eeca83b7274cf01dcf9f571 Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Mon, 22 Mar 2021 16:27:35 -0700 Subject: [PATCH 41/45] Git you can sure be dumb sometimes --- src/lib.rs | 42 +------------------ .../test_help/async_workflow_driver.rs | 5 ++- src/protos/mod.rs | 14 ------- 3 files changed, 4 insertions(+), 57 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0a447bcdf..96cc60c27 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -566,47 +566,7 @@ mod test { .unwrap(); } - #[rstest(core, - case::incremental(single_activity_setup(&[1, 2])), - case::replay(single_activity_setup(&[2])) - )] - fn single_activity_completion(core: FakeCore) { - let res = core.poll_task(TASK_Q).unwrap(); - assert_matches!( - res.get_wf_jobs().as_slice(), - [WfActivationJob { - variant: Some(wf_activation_job::Variant::StartWorkflow(_)), - }] - ); - assert!(core.workflow_machines.exists(RUN_ID)); - - let task_tok = res.task_token; - core.complete_task(TaskCompletion::ok_from_api_attrs( - vec![ScheduleActivityTaskCommandAttributes { - activity_id: "fake_activity".to_string(), - ..Default::default() - } - .into()], - task_tok, - )) - .unwrap(); - - let res = core.poll_task(TASK_Q).unwrap(); - assert_matches!( - res.get_wf_jobs().as_slice(), - [WfActivationJob { - variant: Some(wf_activation_job::Variant::ResolveActivity(_)), - }] - ); - let task_tok = res.task_token; - core.complete_task(TaskCompletion::ok_from_api_attrs( - vec![CompleteWorkflowExecutionCommandAttributes { result: None }.into()], - task_tok, - )) - .unwrap(); - } - - #[rstest(hist_batches, case::incremental(& [1, 2]), case::replay(& [2]))] + #[rstest(hist_batches, case::incremental(&[1, 2]), case::replay(&[2]))] fn parallel_timer_test_across_wf_bridge(hist_batches: &[usize]) { let wfid = "fake_wf_id"; let run_id = "fake_run_id"; diff --git a/src/machines/test_help/async_workflow_driver.rs b/src/machines/test_help/async_workflow_driver.rs index d0fbaf84e..ab11d341d 100644 --- a/src/machines/test_help/async_workflow_driver.rs +++ b/src/machines/test_help/async_workflow_driver.rs @@ -45,7 +45,7 @@ impl TestWfDriverCache { /// Cancel a timer by ID. Timers get some special handling here since they are always /// removed from the "lang" side without needing a response from core. - fn cancel_timer(&self, id: CommandID) { + fn cancel_timer(&self, id: &str) { let mut bc = self.blocking_condvar.0.lock(); bc.issued_commands.remove(&CommandID::Timer(id.to_owned())); } @@ -136,12 +136,13 @@ impl CommandSender { rx.await } } + /// Cancel a timer pub fn cancel_timer(&self, timer_id: &str) { let c = WFCommand::CancelTimer(CancelTimer { timer_id: timer_id.to_owned(), }); - self.twd_cache.cancel_timer(Timer(timer_id.to_string())); + self.twd_cache.cancel_timer(timer_id); self.send(c); } } diff --git a/src/protos/mod.rs b/src/protos/mod.rs index b177b4be0..39580156e 100644 --- a/src/protos/mod.rs +++ b/src/protos/mod.rs @@ -34,7 +34,6 @@ pub mod coresdk { use crate::protos::coresdk::activity_result::ActivityResult; use crate::protos::coresdk::common::{Payload, UserCodeFailure}; use crate::protos::coresdk::workflow_activation::SignalWorkflow; - use crate::protos::temporal::api::common::v1::Payloads; use crate::protos::temporal::api::common::v1::{Payloads, WorkflowExecution}; use crate::protos::temporal::api::failure::v1::failure::FailureInfo; use crate::protos::temporal::api::failure::v1::ApplicationFailureInfo; @@ -111,17 +110,6 @@ pub mod coresdk { } } - pub fn ok_activity(result: Option, task_token: Vec) -> Self { - TaskCompletion { - task_token, - variant: Some(task_completion::Variant::Activity(ActivityResult { - status: Some(activity_result::Status::Completed(ActivityTaskSuccess { - result, - })), - })), - } - } - pub fn ok_activity(result: Vec, task_token: Vec) -> Self { TaskCompletion { task_token, @@ -237,8 +225,6 @@ pub mod temporal { pub mod v1 { include!("temporal.api.command.v1.rs"); - use crate::protos::coresdk::{activity_task, ActivityTask, StartActivity}; - use crate::protos::temporal::api::workflowservice::v1::PollActivityTaskQueueResponse; use crate::protos::{ coresdk::{ activity_task, activity_task::ActivityTask, workflow_commands, PayloadsExt, From 2db7adc4cfdb8864a45b39207b83b3c0beb17cc5 Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Mon, 22 Mar 2021 16:32:46 -0700 Subject: [PATCH 42/45] Some import cleanup --- src/machines/complete_workflow_state_machine.rs | 12 +++++++----- src/machines/mod.rs | 9 ++++----- src/machines/test_help/async_workflow_driver.rs | 9 +++++---- src/machines/timer_state_machine.rs | 7 +++++-- 4 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/machines/complete_workflow_state_machine.rs b/src/machines/complete_workflow_state_machine.rs index 38fc592ca..e9d88b5a7 100644 --- a/src/machines/complete_workflow_state_machine.rs +++ b/src/machines/complete_workflow_state_machine.rs @@ -1,13 +1,15 @@ -use crate::protos::coresdk::workflow_commands::CompleteWorkflowExecution; use crate::{ machines::{ workflow_machines::MachineResponse, Cancellable, NewMachineWithCommand, WFMachinesAdapter, WFMachinesError, }, - protos::temporal::api::{ - command::v1::Command, - enums::v1::{CommandType, EventType}, - history::v1::HistoryEvent, + protos::{ + coresdk::workflow_commands::CompleteWorkflowExecution, + temporal::api::{ + command::v1::Command, + enums::v1::{CommandType, EventType}, + history::v1::HistoryEvent, + }, }, }; use rustfsm::{fsm, StateMachine, TransitionResult}; diff --git a/src/machines/mod.rs b/src/machines/mod.rs index 07a9b84a4..4c12e9886 100644 --- a/src/machines/mod.rs +++ b/src/machines/mod.rs @@ -33,15 +33,14 @@ pub(crate) mod test_help; pub(crate) use workflow_machines::{WFMachinesError, WorkflowMachines}; -use crate::protos::coresdk::workflow_commands::{ - CancelTimer, CompleteWorkflowExecution, FailWorkflowExecution, RequestCancelActivity, - ScheduleActivity, StartTimer, -}; use crate::{ core_tracing::VecDisplayer, machines::workflow_machines::MachineResponse, protos::{ - coresdk::workflow_commands::{workflow_command, WorkflowCommand}, + coresdk::workflow_commands::{ + workflow_command, CancelTimer, CompleteWorkflowExecution, FailWorkflowExecution, + RequestCancelActivity, ScheduleActivity, StartTimer, WorkflowCommand, + }, temporal::api::{command::v1::Command, enums::v1::CommandType, history::v1::HistoryEvent}, }, }; diff --git a/src/machines/test_help/async_workflow_driver.rs b/src/machines/test_help/async_workflow_driver.rs index ab11d341d..6ad4cfd37 100644 --- a/src/machines/test_help/async_workflow_driver.rs +++ b/src/machines/test_help/async_workflow_driver.rs @@ -1,8 +1,9 @@ -use crate::machines::workflow_machines::CommandID; -use crate::protos::coresdk::workflow_commands::{CancelTimer, StartTimer}; use crate::{ - machines::WFCommand, - protos::coresdk::workflow_activation::{wf_activation_job, FireTimer}, + machines::{workflow_machines::CommandID, WFCommand}, + protos::coresdk::{ + workflow_activation::{wf_activation_job, FireTimer}, + workflow_commands::{CancelTimer, StartTimer}, + }, workflow::{ActivationListener, WorkflowFetcher}, }; use futures::channel::oneshot; diff --git a/src/machines/timer_state_machine.rs b/src/machines/timer_state_machine.rs index 2303d466e..05973379c 100644 --- a/src/machines/timer_state_machine.rs +++ b/src/machines/timer_state_machine.rs @@ -1,13 +1,16 @@ #![allow(clippy::large_enum_variant)] -use crate::protos::coresdk::workflow_commands::{CancelTimer, StartTimer}; use crate::{ machines::{ workflow_machines::{MachineResponse, WFMachinesError}, Cancellable, NewMachineWithCommand, WFMachinesAdapter, }, protos::{ - coresdk::{workflow_activation::FireTimer, HistoryEventId}, + coresdk::{ + workflow_activation::FireTimer, + workflow_commands::{CancelTimer, StartTimer}, + HistoryEventId, + }, temporal::api::{ command::v1::Command, enums::v1::{CommandType, EventType}, From fddb40c00446d5a01a9399e66c0ddf9aef5c6bd5 Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Mon, 22 Mar 2021 16:45:17 -0700 Subject: [PATCH 43/45] Integ test fixes --- tests/integ_tests/simple_wf_tests.rs | 105 ++++++++++++--------------- 1 file changed, 46 insertions(+), 59 deletions(-) diff --git a/tests/integ_tests/simple_wf_tests.rs b/tests/integ_tests/simple_wf_tests.rs index 7ea3726cf..648feda89 100644 --- a/tests/integ_tests/simple_wf_tests.rs +++ b/tests/integ_tests/simple_wf_tests.rs @@ -11,23 +11,20 @@ use std::{ }, time::Duration, }; -use temporal_sdk_core::protos::temporal::api::command::v1::ScheduleActivityTaskCommandAttributes; -use temporal_sdk_core::protos::temporal::api::common::v1::{ActivityType, Payload, Payloads}; -use temporal_sdk_core::protos::temporal::api::taskqueue::v1::TaskQueue; +use temporal_sdk_core::protos::coresdk::common::Payload; use temporal_sdk_core::{ - protos::{ - coresdk::{ - activity_result, activity_task, wf_activation_job, ActivityResult, ActivityTaskSuccess, - FireTimer, ResolveActivity, StartWorkflow, Task, TaskCompletion, WfActivationJob, + protos::coresdk::{ + activity_result::{self, activity_result as act_res, ActivityResult}, + activity_task::activity_task as act_task, + common::UserCodeFailure, + workflow_activation::{ + wf_activation_job, FireTimer, ResolveActivity, StartWorkflow, WfActivationJob, }, - temporal::api::{ - command::v1::{ - CancelTimerCommandAttributes, CompleteWorkflowExecutionCommandAttributes, - FailWorkflowExecutionCommandAttributes, StartTimerCommandAttributes, - }, - enums::v1::WorkflowTaskFailedCause, - failure::v1::Failure, + workflow_commands::{ + CancelTimer, CompleteWorkflowExecution, FailWorkflowExecution, ScheduleActivity, + StartTimer, }, + Task, TaskCompletion, }, Core, CoreError, CoreInitOptions, ServerGatewayApis, ServerGatewayOptions, Url, }; @@ -98,7 +95,7 @@ fn timer_workflow() { let timer_id: String = rng.gen::().to_string(); let task = core.poll_task(task_q).unwrap(); core.complete_task(TaskCompletion::ok_from_api_attrs( - vec![StartTimerCommandAttributes { + vec![StartTimer { timer_id: timer_id.to_string(), start_to_fire_timeout: Some(Duration::from_secs(1).into()), ..Default::default() @@ -109,7 +106,7 @@ fn timer_workflow() { .unwrap(); let task = dbg!(core.poll_task(task_q).unwrap()); core.complete_task(TaskCompletion::ok_from_api_attrs( - vec![CompleteWorkflowExecutionCommandAttributes { result: None }.into()], + vec![CompleteWorkflowExecution { result: vec![] }.into()], task.task_token, )) .unwrap(); @@ -126,16 +123,11 @@ fn activity_workflow() { let activity_id: String = rng.gen::().to_string(); let task = core.poll_task(task_q).unwrap(); core.complete_task(TaskCompletion::ok_from_api_attrs( - vec![ScheduleActivityTaskCommandAttributes { + vec![ScheduleActivity { activity_id: activity_id.to_string(), - activity_type: Some(ActivityType { - name: "test_activity".to_string(), - }), + activity_type: "test_activity".to_string(), namespace: NAMESPACE.to_owned(), - task_queue: Some(TaskQueue { - name: task_q.to_owned(), - kind: 1, - }), + task_queue: task_q.to_owned(), schedule_to_start_timeout: Some(Duration::from_secs(30).into()), start_to_close_timeout: Some(Duration::from_secs(30).into()), schedule_to_close_timeout: Some(Duration::from_secs(60).into()), @@ -149,20 +141,16 @@ fn activity_workflow() { let task = dbg!(core.poll_activity_task(task_q).unwrap()); assert_matches!( task.get_activity_variant(), - Some(activity_task::Variant::Start(start_activity)) => { - assert_eq!(start_activity.activity_type, Some(ActivityType { - name: "test_activity".to_string(), - })) + Some(act_task::Variant::Start(start_activity)) => { + assert_eq!(start_activity.activity_type, "test_activity".to_string()) } ); let response_payloads = vec![Payload { - metadata: Default::default(), data: b"hello ".to_vec(), + metadata: Default::default(), }]; core.complete_activity_task(TaskCompletion::ok_activity( - Some(Payloads { - payloads: response_payloads.clone(), - }), + response_payloads.clone(), task.task_token, )) .unwrap(); @@ -173,16 +161,16 @@ fn activity_workflow() { WfActivationJob { variant: Some(wf_activation_job::Variant::ResolveActivity( ResolveActivity {activity_id: a_id, result: Some(ActivityResult{ - status: Some(activity_result::Status::Completed(ActivityTaskSuccess{result: Some(r)}))})} + status: Some(act_res::Status::Completed(activity_result::Success{result: r}))})} )), }, ] => { assert_eq!(a_id, &activity_id); - assert_eq!(r, &Payloads{ payloads: response_payloads.clone()}); + assert_eq!(r, &response_payloads); } ); core.complete_task(TaskCompletion::ok_from_api_attrs( - vec![CompleteWorkflowExecutionCommandAttributes { result: None }.into()], + vec![CompleteWorkflowExecution { result: vec![] }.into()], task.task_token, )) .unwrap() @@ -200,13 +188,13 @@ fn parallel_timer_workflow() { let task = dbg!(core.poll_task(task_q).unwrap()); core.complete_task(TaskCompletion::ok_from_api_attrs( vec![ - StartTimerCommandAttributes { + StartTimer { timer_id: timer_id.clone(), start_to_fire_timeout: Some(Duration::from_millis(50).into()), ..Default::default() } .into(), - StartTimerCommandAttributes { + StartTimer { timer_id: timer_2_id.clone(), start_to_fire_timeout: Some(Duration::from_millis(100).into()), ..Default::default() @@ -239,7 +227,7 @@ fn parallel_timer_workflow() { } ); core.complete_task(TaskCompletion::ok_from_api_attrs( - vec![CompleteWorkflowExecutionCommandAttributes { result: None }.into()], + vec![CompleteWorkflowExecution { result: vec![] }.into()], task.task_token, )) .unwrap(); @@ -262,13 +250,13 @@ fn timer_cancel_workflow() { let task = core.poll_task(task_q).unwrap(); core.complete_task(TaskCompletion::ok_from_api_attrs( vec![ - StartTimerCommandAttributes { + StartTimer { timer_id: timer_id.to_string(), start_to_fire_timeout: Some(Duration::from_millis(50).into()), ..Default::default() } .into(), - StartTimerCommandAttributes { + StartTimer { timer_id: cancel_timer_id.to_string(), start_to_fire_timeout: Some(Duration::from_secs(10).into()), ..Default::default() @@ -281,11 +269,11 @@ fn timer_cancel_workflow() { let task = dbg!(core.poll_task(task_q).unwrap()); core.complete_task(TaskCompletion::ok_from_api_attrs( vec![ - CancelTimerCommandAttributes { + CancelTimer { timer_id: cancel_timer_id.to_string(), } .into(), - CompleteWorkflowExecutionCommandAttributes { result: None }.into(), + CompleteWorkflowExecution { result: vec![] }.into(), ], task.task_token, )) @@ -303,17 +291,17 @@ fn timer_immediate_cancel_workflow() { let task = core.poll_task(task_q).unwrap(); core.complete_task(TaskCompletion::ok_from_api_attrs( vec![ - StartTimerCommandAttributes { + StartTimer { timer_id: cancel_timer_id.to_string(), ..Default::default() } .into(), - CancelTimerCommandAttributes { + CancelTimer { timer_id: cancel_timer_id.to_string(), ..Default::default() } .into(), - CompleteWorkflowExecutionCommandAttributes { result: None }.into(), + CompleteWorkflowExecution { result: vec![] }.into(), ], task.task_token, )) @@ -346,7 +334,7 @@ fn parallel_workflows_same_queue() { }] => assert_eq!(&workflow_type, &"wf-type-1") ); core.complete_task(TaskCompletion::ok_from_api_attrs( - vec![StartTimerCommandAttributes { + vec![StartTimer { timer_id: "timer".to_string(), start_to_fire_timeout: Some(Duration::from_secs(1).into()), ..Default::default() @@ -357,7 +345,7 @@ fn parallel_workflows_same_queue() { .unwrap(); let task = task_chan.recv().unwrap(); core.complete_task(TaskCompletion::ok_from_api_attrs( - vec![CompleteWorkflowExecutionCommandAttributes { result: None }.into()], + vec![CompleteWorkflowExecution { result: vec![] }.into()], task.task_token, )) .unwrap(); @@ -417,7 +405,7 @@ fn fail_wf_task() { // Start with a timer let task = core.poll_task(task_q).unwrap(); core.complete_task(TaskCompletion::ok_from_api_attrs( - vec![StartTimerCommandAttributes { + vec![StartTimer { timer_id: "best-timer".to_string(), start_to_fire_timeout: Some(Duration::from_millis(200).into()), ..Default::default() @@ -434,8 +422,7 @@ fn fail_wf_task() { let task = core.poll_task(task_q).unwrap(); core.complete_task(TaskCompletion::fail( task.task_token, - WorkflowTaskFailedCause::WorkflowWorkerUnhandledFailure, - Failure { + UserCodeFailure { message: "I did an oopsie".to_string(), ..Default::default() }, @@ -446,7 +433,7 @@ fn fail_wf_task() { // to poll a couple of times as there will be more than one required workflow activation. let task = core.poll_task(task_q).unwrap(); core.complete_task(TaskCompletion::ok_from_api_attrs( - vec![StartTimerCommandAttributes { + vec![StartTimer { timer_id: "best-timer".to_string(), start_to_fire_timeout: Some(Duration::from_millis(200).into()), ..Default::default() @@ -457,7 +444,7 @@ fn fail_wf_task() { .unwrap(); let task = core.poll_task(task_q).unwrap(); core.complete_task(TaskCompletion::ok_from_api_attrs( - vec![CompleteWorkflowExecutionCommandAttributes { result: None }.into()], + vec![CompleteWorkflowExecution { result: vec![] }.into()], task.task_token, )) .unwrap(); @@ -473,7 +460,7 @@ fn fail_workflow_execution() { let timer_id: String = rng.gen::().to_string(); let task = core.poll_task(task_q).unwrap(); core.complete_task(TaskCompletion::ok_from_api_attrs( - vec![StartTimerCommandAttributes { + vec![StartTimer { timer_id: timer_id.to_string(), start_to_fire_timeout: Some(Duration::from_secs(1).into()), ..Default::default() @@ -484,8 +471,8 @@ fn fail_workflow_execution() { .unwrap(); let task = core.poll_task(task_q).unwrap(); core.complete_task(TaskCompletion::ok_from_api_attrs( - vec![FailWorkflowExecutionCommandAttributes { - failure: Some(Failure { + vec![FailWorkflowExecution { + failure: Some(UserCodeFailure { message: "I'm ded".to_string(), ..Default::default() }), @@ -547,7 +534,7 @@ fn signal_workflow() { ] ); core.complete_task(TaskCompletion::ok_from_api_attrs( - vec![CompleteWorkflowExecutionCommandAttributes { result: None }.into()], + vec![CompleteWorkflowExecution { result: vec![] }.into()], res.task_token, )) .unwrap(); @@ -565,7 +552,7 @@ fn signal_workflow_signal_not_handled_on_workflow_completion() { let res = core.poll_task(task_q).unwrap(); // Task is completed with a timer core.complete_task(TaskCompletion::ok_from_api_attrs( - vec![StartTimerCommandAttributes { + vec![StartTimer { timer_id: "sometimer".to_string(), start_to_fire_timeout: Some(Duration::from_millis(10).into()), ..Default::default() @@ -600,7 +587,7 @@ fn signal_workflow_signal_not_handled_on_workflow_completion() { // Send completion - not having seen a poll response with a signal in it yet let unhandled = core .complete_task(TaskCompletion::ok_from_api_attrs( - vec![CompleteWorkflowExecutionCommandAttributes { result: None }.into()], + vec![CompleteWorkflowExecution { result: vec![] }.into()], task_token, )) .unwrap_err(); @@ -615,7 +602,7 @@ fn signal_workflow_signal_not_handled_on_workflow_completion() { }] ); core.complete_task(TaskCompletion::ok_from_api_attrs( - vec![CompleteWorkflowExecutionCommandAttributes { result: None }.into()], + vec![CompleteWorkflowExecution { result: vec![] }.into()], res.task_token, )) .unwrap(); From b7e6dca9f61cd7305b153156c6ecf982c0778672 Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Mon, 22 Mar 2021 16:54:57 -0700 Subject: [PATCH 44/45] Realized integ test was not getting linted --- .buildkite/pipeline.yml | 2 +- tests/integ_tests/simple_wf_tests.rs | 22 +++++----------------- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 4df301d43..e84b65cad 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -13,7 +13,7 @@ steps: agents: queue: "default" docker: "*" - command: "cargo clippy --all -- -D warnings" + command: "cargo clippy --workspace --all-features --all-targets -- -D warnings && cargo clippy --test integ_tests --all-features -- --D warnings" timeout_in_minutes: 15 plugins: - docker-compose#v3.0.0: diff --git a/tests/integ_tests/simple_wf_tests.rs b/tests/integ_tests/simple_wf_tests.rs index 648feda89..d7197c872 100644 --- a/tests/integ_tests/simple_wf_tests.rs +++ b/tests/integ_tests/simple_wf_tests.rs @@ -11,12 +11,11 @@ use std::{ }, time::Duration, }; -use temporal_sdk_core::protos::coresdk::common::Payload; use temporal_sdk_core::{ protos::coresdk::{ activity_result::{self, activity_result as act_res, ActivityResult}, activity_task::activity_task as act_task, - common::UserCodeFailure, + common::{Payload, UserCodeFailure}, workflow_activation::{ wf_activation_job, FireTimer, ResolveActivity, StartWorkflow, WfActivationJob, }, @@ -81,8 +80,7 @@ fn get_integ_server_options() -> ServerGatewayOptions { fn get_integ_core() -> impl Core { let gateway_opts = get_integ_server_options(); - let core = temporal_sdk_core::init(CoreInitOptions { gateway_opts }).unwrap(); - core + temporal_sdk_core::init(CoreInitOptions { gateway_opts }).unwrap() } #[test] @@ -96,9 +94,8 @@ fn timer_workflow() { let task = core.poll_task(task_q).unwrap(); core.complete_task(TaskCompletion::ok_from_api_attrs( vec![StartTimer { - timer_id: timer_id.to_string(), + timer_id, start_to_fire_timeout: Some(Duration::from_secs(1).into()), - ..Default::default() } .into()], task.task_token, @@ -191,13 +188,11 @@ fn parallel_timer_workflow() { StartTimer { timer_id: timer_id.clone(), start_to_fire_timeout: Some(Duration::from_millis(50).into()), - ..Default::default() } .into(), StartTimer { timer_id: timer_2_id.clone(), start_to_fire_timeout: Some(Duration::from_millis(100).into()), - ..Default::default() } .into(), ], @@ -253,13 +248,11 @@ fn timer_cancel_workflow() { StartTimer { timer_id: timer_id.to_string(), start_to_fire_timeout: Some(Duration::from_millis(50).into()), - ..Default::default() } .into(), StartTimer { timer_id: cancel_timer_id.to_string(), start_to_fire_timeout: Some(Duration::from_secs(10).into()), - ..Default::default() } .into(), ], @@ -298,7 +291,6 @@ fn timer_immediate_cancel_workflow() { .into(), CancelTimer { timer_id: cancel_timer_id.to_string(), - ..Default::default() } .into(), CompleteWorkflowExecution { result: vec![] }.into(), @@ -337,7 +329,6 @@ fn parallel_workflows_same_queue() { vec![StartTimer { timer_id: "timer".to_string(), start_to_fire_timeout: Some(Duration::from_secs(1).into()), - ..Default::default() } .into()], task.task_token, @@ -408,7 +399,6 @@ fn fail_wf_task() { vec![StartTimer { timer_id: "best-timer".to_string(), start_to_fire_timeout: Some(Duration::from_millis(200).into()), - ..Default::default() } .into()], task.task_token, @@ -461,9 +451,8 @@ fn fail_workflow_execution() { let task = core.poll_task(task_q).unwrap(); core.complete_task(TaskCompletion::ok_from_api_attrs( vec![StartTimer { - timer_id: timer_id.to_string(), + timer_id, start_to_fire_timeout: Some(Duration::from_secs(1).into()), - ..Default::default() } .into()], task.task_token, @@ -555,10 +544,9 @@ fn signal_workflow_signal_not_handled_on_workflow_completion() { vec![StartTimer { timer_id: "sometimer".to_string(), start_to_fire_timeout: Some(Duration::from_millis(10).into()), - ..Default::default() } .into()], - res.task_token.clone(), + res.task_token, )) .unwrap(); From e3ba4883ae23b975b8d5f80c655623429625eeb8 Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Mon, 22 Mar 2021 17:45:12 -0700 Subject: [PATCH 45/45] Forgot to remove intentional lint failure while testing --- tests/integ_tests/simple_wf_tests.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/integ_tests/simple_wf_tests.rs b/tests/integ_tests/simple_wf_tests.rs index d7197c872..71065dfbd 100644 --- a/tests/integ_tests/simple_wf_tests.rs +++ b/tests/integ_tests/simple_wf_tests.rs @@ -426,7 +426,6 @@ fn fail_wf_task() { vec![StartTimer { timer_id: "best-timer".to_string(), start_to_fire_timeout: Some(Duration::from_millis(200).into()), - ..Default::default() } .into()], task.task_token,