go.uber.org/cadence@v1.2.9/workflow/workflow.go (about) 1 // Copyright (c) 2017-2020 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package workflow 22 23 import ( 24 "github.com/uber-go/tally" 25 "go.uber.org/zap" 26 27 "go.uber.org/cadence/encoded" 28 "go.uber.org/cadence/internal" 29 ) 30 31 type ( 32 33 // ChildWorkflowFuture represents the result of a child workflow execution 34 ChildWorkflowFuture = internal.ChildWorkflowFuture 35 36 // Type identifies a workflow type. 37 Type = internal.WorkflowType 38 39 // Execution Details. 40 Execution = internal.WorkflowExecution 41 42 // Version represents a change version. See GetVersion call. 43 Version = internal.Version 44 45 // ChildWorkflowOptions stores all child workflow specific parameters that will be stored inside of a Context. 46 ChildWorkflowOptions = internal.ChildWorkflowOptions 47 48 // Bugports contains opt-in flags for backports of old, buggy behavior that has been fixed. 49 // See the internal docs for details. 50 Bugports = internal.Bugports 51 52 // RegisterOptions consists of options for registering a workflow 53 RegisterOptions = internal.RegisterWorkflowOptions 54 55 // Info information about currently executing workflow 56 Info = internal.WorkflowInfo 57 ) 58 59 // Register - registers a workflow function with the framework. 60 // A workflow takes a workflow context and input and returns a (result, error) or just error. 61 // Examples: 62 // 63 // func sampleWorkflow(ctx workflow.Context, input []byte) (result []byte, err error) 64 // func sampleWorkflow(ctx workflow.Context, arg1 int, arg2 string) (result []byte, err error) 65 // func sampleWorkflow(ctx workflow.Context) (result []byte, err error) 66 // func sampleWorkflow(ctx workflow.Context, arg1 int) (result string, err error) 67 // 68 // Serialization of all primitive types, structures is supported ... except channels, functions, variadic, unsafe pointer. 69 // This method calls panic if workflowFunc doesn't comply with the expected format. 70 // Deprecated: Global workflow registration methods are replaced by equivalent Worker instance methods. 71 // This method is kept to maintain backward compatibility and should not be used. 72 func Register(workflowFunc interface{}) { 73 internal.RegisterWorkflow(workflowFunc) 74 } 75 76 // RegisterWithOptions registers the workflow function with options 77 // The user can use options to provide an external name for the workflow or leave it empty if no 78 // external name is required. This can be used as 79 // 80 // client.RegisterWorkflow(sampleWorkflow, RegisterOptions{}) 81 // client.RegisterWorkflow(sampleWorkflow, RegisterOptions{Name: "foo"}) 82 // 83 // A workflow takes a workflow context and input and returns a (result, error) or just error. 84 // Examples: 85 // 86 // func sampleWorkflow(ctx workflow.Context, input []byte) (result []byte, err error) 87 // func sampleWorkflow(ctx workflow.Context, arg1 int, arg2 string) (result []byte, err error) 88 // func sampleWorkflow(ctx workflow.Context) (result []byte, err error) 89 // func sampleWorkflow(ctx workflow.Context, arg1 int) (result string, err error) 90 // 91 // Serialization of all primitive types, structures is supported ... except channels, functions, variadic, unsafe pointer. 92 // This method calls panic if workflowFunc doesn't comply with the expected format. 93 // Deprecated: Global workflow registration methods are replaced by equivalent Worker instance methods. 94 // This method is kept to maintain backward compatibility and should not be used. 95 func RegisterWithOptions(workflowFunc interface{}, opts RegisterOptions) { 96 internal.RegisterWorkflowWithOptions(workflowFunc, opts) 97 } 98 99 // GetRegisteredWorkflowTypes returns the registered workflow function/alias names. 100 func GetRegisteredWorkflowTypes() []string { 101 return internal.GetRegisteredWorkflowTypes() 102 } 103 104 // ExecuteActivity requests activity execution in the context of a workflow. 105 // Context can be used to pass the settings for this activity. 106 // For example: task list that this need to be routed, timeouts that need to be configured. 107 // Use ActivityOptions to pass down the options. 108 // 109 // ao := ActivityOptions{ 110 // TaskList: "exampleTaskList", 111 // ScheduleToStartTimeout: 10 * time.Second, 112 // StartToCloseTimeout: 5 * time.Second, 113 // ScheduleToCloseTimeout: 10 * time.Second, 114 // HeartbeatTimeout: 0, 115 // } 116 // ctx := WithActivityOptions(ctx, ao) 117 // 118 // Or to override a single option 119 // 120 // ctx := WithTaskList(ctx, "exampleTaskList") 121 // 122 // Input activity is either an activity name (string) or a function representing an activity that is getting scheduled. 123 // Input args are the arguments that need to be passed to the scheduled activity. 124 // 125 // If the activity failed to complete then the future get error would indicate the failure, and it can be one of 126 // CustomError, TimeoutError, CanceledError, PanicError, GenericError. 127 // You can cancel the pending activity using context(workflow.WithCancel(ctx)) and that will fail the activity with 128 // error CanceledError. 129 // 130 // ExecuteActivity returns Future with activity result or failure. 131 func ExecuteActivity(ctx Context, activity interface{}, args ...interface{}) Future { 132 return internal.ExecuteActivity(ctx, activity, args...) 133 } 134 135 // ExecuteLocalActivity requests to run a local activity. A local activity is like a regular activity with some key 136 // differences: 137 // 138 // • Local activity is scheduled and run by the workflow worker locally. 139 // 140 // • Local activity does not need Cadence server to schedule activity task and does not rely on activity worker. 141 // 142 // • No need to register local activity. 143 // 144 // • The parameter activity to ExecuteLocalActivity() must be a function. 145 // 146 // • Local activity is for short living activities (usually finishes within seconds). 147 // 148 // • Local activity cannot heartbeat. 149 // 150 // Context can be used to pass the settings for this local activity. 151 // For now there is only one setting for timeout to be set: 152 // 153 // lao := LocalActivityOptions{ 154 // ScheduleToCloseTimeout: 5 * time.Second, 155 // } 156 // ctx := WithLocalActivityOptions(ctx, lao) 157 // 158 // The timeout here should be relative shorter than the DecisionTaskStartToCloseTimeout of the workflow. If you need a 159 // longer timeout, you probably should not use local activity and instead should use regular activity. Local activity is 160 // designed to be used for short living activities (usually finishes within seconds). 161 // 162 // Input args are the arguments that will to be passed to the local activity. The input args will be hand over directly 163 // to local activity function without serialization/deserialization because we don't need to pass the input across process 164 // boundary. However, the result will still go through serialization/deserialization because we need to record the result 165 // as history to cadence server so if the workflow crashes, a different worker can replay the history without running 166 // the local activity again. 167 // 168 // If the activity failed to complete then the future get error would indicate the failure, and it can be one of 169 // CustomError, TimeoutError, CanceledError, PanicError, GenericError. 170 // You can cancel the pending activity by cancel the context(workflow.WithCancel(ctx)) and that will fail the activity 171 // with error CanceledError. 172 // 173 // ExecuteLocalActivity returns Future with local activity result or failure. 174 func ExecuteLocalActivity(ctx Context, activity interface{}, args ...interface{}) Future { 175 return internal.ExecuteLocalActivity(ctx, activity, args...) 176 } 177 178 // ExecuteChildWorkflow requests child workflow execution in the context of a workflow. 179 // Context can be used to pass the settings for the child workflow. 180 // For example: task list that this child workflow should be routed, timeouts that need to be configured. 181 // Use ChildWorkflowOptions to pass down the options. 182 // 183 // cwo := ChildWorkflowOptions{ 184 // ExecutionStartToCloseTimeout: 10 * time.Minute, 185 // TaskStartToCloseTimeout: time.Minute, 186 // } 187 // ctx := WithChildOptions(ctx, cwo) 188 // 189 // Input childWorkflow is either a workflow name or a workflow function that is getting scheduled. 190 // Input args are the arguments that need to be passed to the child workflow function represented by childWorkflow. 191 // If the child workflow failed to complete then the future get error would indicate the failure and it can be one of 192 // CustomError, TimeoutError, CanceledError, GenericError. 193 // You can cancel the pending child workflow using context(workflow.WithCancel(ctx)) and that will fail the workflow with 194 // error CanceledError. 195 // ExecuteChildWorkflow returns ChildWorkflowFuture. 196 func ExecuteChildWorkflow(ctx Context, childWorkflow interface{}, args ...interface{}) ChildWorkflowFuture { 197 return internal.ExecuteChildWorkflow(ctx, childWorkflow, args...) 198 } 199 200 // GetInfo extracts info of a current workflow from a context. 201 func GetInfo(ctx Context) *Info { 202 return internal.GetWorkflowInfo(ctx) 203 } 204 205 // GetLogger returns a logger to be used in workflow's context 206 func GetLogger(ctx Context) *zap.Logger { 207 return internal.GetLogger(ctx) 208 } 209 210 // GetUnhandledSignalNames returns signal names that have unconsumed signals. 211 func GetUnhandledSignalNames(ctx Context) []string { 212 return internal.GetUnhandledSignalNames(ctx) 213 } 214 215 // GetMetricsScope returns a metrics scope to be used in workflow's context 216 func GetMetricsScope(ctx Context) tally.Scope { 217 return internal.GetMetricsScope(ctx) 218 } 219 220 // GetTotalHistoryBytes returns the current history size of that workflow 221 func GetTotalHistoryBytes(ctx Context) int64 { 222 return internal.GetTotalEstimatedHistoryBytes(ctx) 223 } 224 225 // GetHistoryCount returns the current number of history event of that workflow 226 func GetHistoryCount(ctx Context) int64 { 227 return internal.GetHistoryCount(ctx) 228 } 229 230 // RequestCancelExternalWorkflow can be used to request cancellation of an external workflow. 231 // Input workflowID is the workflow ID of target workflow. 232 // Input runID indicates the instance of a workflow. Input runID is optional (default is ""). When runID is not specified, 233 // then the currently running instance of that workflowID will be used. 234 // By default, the current workflow's domain will be used as target domain. However, you can specify a different domain 235 // of the target workflow using the context like: 236 // 237 // ctx := WithWorkflowDomain(ctx, "domain-name") 238 // 239 // RequestCancelExternalWorkflow return Future with failure or empty success result. 240 func RequestCancelExternalWorkflow(ctx Context, workflowID, runID string) Future { 241 return internal.RequestCancelExternalWorkflow(ctx, workflowID, runID) 242 } 243 244 // SignalExternalWorkflow can be used to send signal info to an external workflow. 245 // Input workflowID is the workflow ID of target workflow. 246 // Input runID indicates the instance of a workflow. Input runID is optional (default is ""). When runID is not specified, 247 // then the currently running instance of that workflowID will be used. 248 // By default, the current workflow's domain will be used as target domain. However, you can specify a different domain 249 // of the target workflow using the context like: 250 // 251 // ctx := WithWorkflowDomain(ctx, "domain-name") 252 // 253 // SignalExternalWorkflow return Future with failure or empty success result. 254 func SignalExternalWorkflow(ctx Context, workflowID, runID, signalName string, arg interface{}) Future { 255 return internal.SignalExternalWorkflow(ctx, workflowID, runID, signalName, arg) 256 } 257 258 // GetSignalChannel returns channel corresponding to the signal name. 259 func GetSignalChannel(ctx Context, signalName string) Channel { 260 return internal.GetSignalChannel(ctx, signalName) 261 } 262 263 // SideEffect executes the provided function once, records its result into the workflow history. The recorded result on 264 // history will be returned without executing the provided function during replay. This guarantees the deterministic 265 // requirement for workflow as the exact same result will be returned in replay. 266 // Common use case is to run some short non-deterministic code in workflow, like getting random number or new UUID. 267 // The only way to fail SideEffect is to panic which causes decision task failure. The decision task after timeout is 268 // rescheduled and re-executed giving SideEffect another chance to succeed. 269 // 270 // Caution: do not use SideEffect to modify closures. Always retrieve result from SideEffect's encoded return value. 271 // For example this code is BROKEN: 272 // 273 // // Bad example: 274 // var random int 275 // workflow.SideEffect(func(ctx workflow.Context) interface{} { 276 // random = rand.Intn(100) 277 // return nil 278 // }) 279 // // random will always be 0 in replay, thus this code is non-deterministic 280 // if random < 50 { 281 // .... 282 // } else { 283 // .... 284 // } 285 // 286 // On replay the provided function is not executed, the random will always be 0, and the workflow could takes a 287 // different path breaking the determinism. 288 // 289 // Here is the correct way to use SideEffect: 290 // 291 // // Good example: 292 // encodedRandom := SideEffect(func(ctx workflow.Context) interface{} { 293 // return rand.Intn(100) 294 // }) 295 // var random int 296 // encodedRandom.Get(&random) 297 // if random < 50 { 298 // .... 299 // } else { 300 // .... 301 // } 302 func SideEffect(ctx Context, f func(ctx Context) interface{}) encoded.Value { 303 return internal.SideEffect(ctx, f) 304 } 305 306 // MutableSideEffect executes the provided function once, then it looks up the history for the value with the given id. 307 // If there is no existing value, then it records the function result as a value with the given id on history; 308 // otherwise, it compares whether the existing value from history has changed from the new function result by calling the 309 // provided equals function. If they are equal, it returns the value without recording a new one in history; 310 // 311 // otherwise, it records the new value with the same id on history. 312 // 313 // Caution: do not use MutableSideEffect to modify closures. Always retrieve result from MutableSideEffect's encoded 314 // return value. 315 // 316 // The difference between MutableSideEffect() and SideEffect() is that every new SideEffect() call in non-replay will 317 // result in a new marker being recorded on history. However, MutableSideEffect() only records a new marker if the value 318 // changed. During replay, MutableSideEffect() will not execute the function again, but it will return the exact same 319 // value as it was returning during the non-replay run. 320 // 321 // One good use case of MutableSideEffect() is to access dynamically changing config without breaking determinism. 322 func MutableSideEffect(ctx Context, id string, f func(ctx Context) interface{}, equals func(a, b interface{}) bool) encoded.Value { 323 return internal.MutableSideEffect(ctx, id, f, equals) 324 } 325 326 // DefaultVersion is a version returned by GetVersion for code that wasn't versioned before 327 const DefaultVersion Version = internal.DefaultVersion 328 329 // GetVersion is used to safely perform backwards incompatible changes to workflow definitions. 330 // It is not allowed to update workflow code while there are workflows running as it is going to break 331 // determinism. The solution is to have both old code that is used to replay existing workflows 332 // as well as the new one that is used when it is executed for the first time. 333 // GetVersion returns maxSupported version when is executed for the first time. This version is recorded into the 334 // workflow history as a marker event. Even if maxSupported version is changed the version that was recorded is 335 // returned on replay. DefaultVersion constant contains version of code that wasn't versioned before. 336 // For example initially workflow has the following code: 337 // 338 // err = workflow.ExecuteActivity(ctx, foo).Get(ctx, nil) 339 // 340 // it should be updated to 341 // 342 // err = workflow.ExecuteActivity(ctx, bar).Get(ctx, nil) 343 // 344 // The backwards compatible way to execute the update is 345 // 346 // v := GetVersion(ctx, "fooChange", DefaultVersion, 1) 347 // if v == DefaultVersion { 348 // err = workflow.ExecuteActivity(ctx, foo).Get(ctx, nil) 349 // } else { 350 // err = workflow.ExecuteActivity(ctx, bar).Get(ctx, nil) 351 // } 352 // 353 // Then bar has to be changed to baz: 354 // 355 // v := GetVersion(ctx, "fooChange", DefaultVersion, 2) 356 // if v == DefaultVersion { 357 // err = workflow.ExecuteActivity(ctx, foo).Get(ctx, nil) 358 // } else if v == 1 { 359 // err = workflow.ExecuteActivity(ctx, bar).Get(ctx, nil) 360 // } else { 361 // err = workflow.ExecuteActivity(ctx, baz).Get(ctx, nil) 362 // } 363 // 364 // Later when there are no workflow executions running DefaultVersion the correspondent branch can be removed: 365 // 366 // v := GetVersion(ctx, "fooChange", 1, 2) 367 // if v == 1 { 368 // err = workflow.ExecuteActivity(ctx, bar).Get(ctx, nil) 369 // } else { 370 // err = workflow.ExecuteActivity(ctx, baz).Get(ctx, nil) 371 // } 372 // 373 // It is recommended to keep the GetVersion() call even if single branch is left: 374 // 375 // GetVersion(ctx, "fooChange", 2, 2) 376 // err = workflow.ExecuteActivity(ctx, baz).Get(ctx, nil) 377 // 378 // The reason to keep it is: 1) it ensures that if there is older version execution still running, it will fail here 379 // and not proceed; 2) if you ever need to make more changes for “fooChange”, for example change activity from baz to qux, 380 // you just need to update the maxVersion from 2 to 3. 381 // 382 // Note that, you only need to preserve the first call to GetVersion() for each changeID. All subsequent call to GetVersion() 383 // with same changeID are safe to remove. However, if you really want to get rid of the first GetVersion() call as well, 384 // you can do so, but you need to make sure: 1) all older version executions are completed; 2) you can no longer use “fooChange” 385 // as changeID. If you ever need to make changes to that same part like change from baz to qux, you would need to use a 386 // different changeID like “fooChange-fix2”, and start minVersion from DefaultVersion again. The code would looks like: 387 // 388 // v := workflow.GetVersion(ctx, "fooChange-fix2", workflow.DefaultVersion, 1) 389 // if v == workflow.DefaultVersion { 390 // err = workflow.ExecuteActivity(ctx, baz, data).Get(ctx, nil) 391 // } else { 392 // err = workflow.ExecuteActivity(ctx, qux, data).Get(ctx, nil) 393 // } 394 func GetVersion(ctx Context, changeID string, minSupported, maxSupported Version) Version { 395 return internal.GetVersion(ctx, changeID, minSupported, maxSupported) 396 } 397 398 // SetQueryHandler sets the query handler to handle workflow query. The queryType specify which query type this handler 399 // should handle. The handler must be a function that returns 2 values. The first return value must be a serializable 400 // result. The second return value must be an error. The handler function could receive any number of input parameters. 401 // All the input parameter must be serializable. You should call workflow.SetQueryHandler() at the beginning of the workflow 402 // code. When client calls Client.QueryWorkflow() to cadence server, a task will be generated on server that will be dispatched 403 // to a workflow worker, which will replay the history events and then execute a query handler based on the query type. 404 // The query handler will be invoked out of the context of the workflow, meaning that the handler code must not use workflow 405 // context to do things like workflow.NewChannel(), workflow.Go() or to call any workflow blocking functions like 406 // Channel.Get() or Future.Get(). Trying to do so in query handler code will fail the query and client will receive 407 // QueryFailedError. 408 // Example of workflow code that support query type "current_state": 409 // 410 // func MyWorkflow(ctx workflow.Context, input string) error { 411 // currentState := "started" // this could be any serializable struct 412 // err := workflow.SetQueryHandler(ctx, "current_state", func() (string, error) { 413 // return currentState, nil 414 // }) 415 // if err != nil { 416 // currentState = "failed to register query handler" 417 // return err 418 // } 419 // // your normal workflow code begins here, and you update the currentState as the code makes progress. 420 // currentState = "waiting timer" 421 // err = NewTimer(ctx, time.Hour).Get(ctx, nil) 422 // if err != nil { 423 // currentState = "timer failed" 424 // return err 425 // } 426 // 427 // currentState = "waiting activity" 428 // ctx = WithActivityOptions(ctx, myActivityOptions) 429 // err = ExecuteActivity(ctx, MyActivity, "my_input").Get(ctx, nil) 430 // if err != nil { 431 // currentState = "activity failed" 432 // return err 433 // } 434 // currentState = "done" 435 // return nil 436 // } 437 func SetQueryHandler(ctx Context, queryType string, handler interface{}) error { 438 return internal.SetQueryHandler(ctx, queryType, handler) 439 } 440 441 // IsReplaying returns whether the current workflow code is replaying. 442 // 443 // Warning! Never make decisions, like schedule activity/childWorkflow/timer or send/wait on future/channel, based on 444 // this flag as it is going to break workflow determinism requirement. 445 // The only reasonable use case for this flag is to avoid some external actions during replay, like custom logging or 446 // metric reporting. Please note that Cadence already provide standard logging/metric via workflow.GetLogger(ctx) and 447 // workflow.GetMetricsScope(ctx), and those standard mechanism are replay-aware and it will automatically suppress during 448 // replay. Only use this flag if you need custom logging/metrics reporting, for example if you want to log to kafka. 449 // 450 // Warning! Any action protected by this flag should not fail or if it does fail should ignore that failure or panic 451 // on the failure. If workflow don't want to be blocked on those failure, it should ignore those failure; if workflow do 452 // want to make sure it proceed only when that action succeed then it should panic on that failure. Panic raised from a 453 // workflow causes decision task to fail and cadence server will rescheduled later to retry. 454 func IsReplaying(ctx Context) bool { 455 return internal.IsReplaying(ctx) 456 } 457 458 // HasLastCompletionResult checks if there is completion result from previous runs. 459 // This is used in combination with cron schedule. A workflow can be started with an optional cron schedule. 460 // If a cron workflow wants to pass some data to next schedule, it can return any data and that data will become 461 // available when next run starts. 462 // This HasLastCompletionResult() checks if there is such data available passing down from previous successful run. 463 func HasLastCompletionResult(ctx Context) bool { 464 return internal.HasLastCompletionResult(ctx) 465 } 466 467 // GetLastCompletionResult extract last completion result from previous run for this cron workflow. 468 // This is used in combination with cron schedule. A workflow can be started with an optional cron schedule. 469 // If a cron workflow wants to pass some data to next schedule, it can return any data and that data will become 470 // available when next run starts. 471 // This GetLastCompletionResult() extract the data into expected data structure. 472 // See TestWorkflowEnvironment.SetLastCompletionResult() for unit test support. 473 func GetLastCompletionResult(ctx Context, d ...interface{}) error { 474 return internal.GetLastCompletionResult(ctx, d...) 475 } 476 477 // UpsertSearchAttributes is used to add or update workflow search attributes. 478 // The search attributes can be used in query of List/Scan/Count workflow APIs. 479 // The key and value type must be registered on cadence server side; 480 // The value has to deterministic when replay; 481 // The value has to be Json serializable. 482 // UpsertSearchAttributes will merge attributes to existing map in workflow, for example workflow code: 483 // 484 // func MyWorkflow(ctx workflow.Context, input string) error { 485 // attr1 := map[string]interface{}{ 486 // "CustomIntField": 1, 487 // "CustomBoolField": true, 488 // } 489 // workflow.UpsertSearchAttributes(ctx, attr1) 490 // 491 // attr2 := map[string]interface{}{ 492 // "CustomIntField": 2, 493 // "CustomKeywordField": "seattle", 494 // } 495 // workflow.UpsertSearchAttributes(ctx, attr2) 496 // } 497 // 498 // will eventually have search attributes: 499 // 500 // map[string]interface{}{ 501 // "CustomIntField": 2, 502 // "CustomBoolField": true, 503 // "CustomKeywordField": "seattle", 504 // } 505 // 506 // This is only supported when using ElasticSearch. 507 func UpsertSearchAttributes(ctx Context, attributes map[string]interface{}) error { 508 return internal.UpsertSearchAttributes(ctx, attributes) 509 }