go.uber.org/cadence@v1.2.9/test/workflow_test.go (about) 1 // Copyright (c) 2017-2020 Uber Technologies Inc. 2 // Portions of the Software are attributed to Copyright (c) 2020 Temporal Technologies Inc. 3 // 4 // Permission is hereby granted, free of charge, to any person obtaining a copy 5 // of this software and associated documentation files (the "Software"), to deal 6 // in the Software without restriction, including without limitation the rights 7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 // copies of the Software, and to permit persons to whom the Software is 9 // furnished to do so, subject to the following conditions: 10 // 11 // The above copyright notice and this permission notice shall be included in 12 // all copies or substantial portions of the Software. 13 // 14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 // THE SOFTWARE. 21 22 package test 23 24 import ( 25 "errors" 26 "fmt" 27 "math/rand" 28 "time" 29 30 "go.uber.org/cadence" 31 "go.uber.org/cadence/.gen/go/shared" 32 "go.uber.org/cadence/client" 33 "go.uber.org/cadence/internal" 34 "go.uber.org/cadence/worker" 35 "go.uber.org/cadence/workflow" 36 ) 37 38 const ( 39 consistentQuerySignalCh = "consistent-query-signal-chan" 40 ) 41 42 type Workflows struct { 43 nonDeterminismSimulatorWorkflowCallCount int 44 } 45 46 func (w *Workflows) Basic(ctx workflow.Context) ([]string, error) { 47 ctx = workflow.WithActivityOptions(ctx, w.defaultActivityOptions()) 48 var ans1 string 49 workflow.GetLogger(ctx).Info("calling ExecuteActivity") 50 err := workflow.ExecuteActivity(ctx, "Prefix_ToUpperWithDelay", "hello", time.Second).Get(ctx, &ans1) 51 if err != nil { 52 return nil, err 53 } 54 var ans2 string 55 if err := workflow.ExecuteActivity(ctx, "Prefix_ToUpper", ans1).Get(ctx, &ans2); err != nil { 56 return nil, err 57 } 58 if ans2 != "HELLO" { 59 return nil, fmt.Errorf("incorrect return value from activity: expected=%v,got=%v", "HELLO", ans2) 60 } 61 return []string{"toUpperWithDelay", "toUpper"}, nil 62 } 63 64 func (w *Workflows) ActivityRetryOnError(ctx workflow.Context) ([]string, error) { 65 ctx = workflow.WithActivityOptions(ctx, w.defaultActivityOptionsWithRetry()) 66 startTime := workflow.Now(ctx) 67 err := workflow.ExecuteActivity(ctx, "Fail").Get(ctx, nil) 68 if err == nil { 69 return nil, fmt.Errorf("expected activity to fail but succeeded") 70 } 71 72 elapsed := workflow.Now(ctx).Sub(startTime) 73 if elapsed < 2*time.Second { 74 return nil, fmt.Errorf("expected activity to be retried on failure, but it was not") 75 } 76 77 cerr, ok := err.(*cadence.CustomError) 78 if !ok { 79 return nil, fmt.Errorf("activity failed with unexpected error: %v", err) 80 } 81 if cerr.Reason() != errFailOnPurpose.Reason() { 82 return nil, fmt.Errorf("activity failed with unexpected error reason: %v", cerr.Reason()) 83 } 84 85 return []string{"fail", "fail", "fail"}, nil 86 } 87 88 func (w *Workflows) ActivityRetryOptionsChange(ctx workflow.Context) ([]string, error) { 89 opts := w.defaultActivityOptionsWithRetry() 90 opts.RetryPolicy.MaximumAttempts = 2 91 if workflow.IsReplaying(ctx) { 92 opts.RetryPolicy.MaximumAttempts = 3 93 } 94 ctx = workflow.WithActivityOptions(ctx, opts) 95 err := workflow.ExecuteActivity(ctx, "Fail").Get(ctx, nil) 96 if err == nil { 97 return nil, fmt.Errorf("expected activity to fail but succeeded") 98 } 99 return []string{"fail", "fail"}, nil 100 } 101 102 func (w *Workflows) ActivityRetryOnTimeout(ctx workflow.Context, timeoutType shared.TimeoutType) ([]string, error) { 103 opts := w.defaultActivityOptionsWithRetry() 104 switch timeoutType { 105 case shared.TimeoutTypeScheduleToClose: 106 opts.ScheduleToCloseTimeout = time.Second 107 case shared.TimeoutTypeStartToClose: 108 opts.StartToCloseTimeout = time.Second 109 } 110 111 ctx = workflow.WithActivityOptions(ctx, opts) 112 113 startTime := workflow.Now(ctx) 114 err := workflow.ExecuteActivity(ctx, "Activities_Sleep", 2*time.Second).Get(ctx, nil) 115 if err == nil { 116 return nil, fmt.Errorf("expected activity to fail but succeeded") 117 } 118 119 elapsed := workflow.Now(ctx).Sub(startTime) 120 if elapsed < 5*time.Second { 121 return nil, fmt.Errorf("expected activity to be retried on failure, but it was not: %v", elapsed) 122 } 123 124 terr, ok := err.(*workflow.TimeoutError) 125 if !ok { 126 return nil, fmt.Errorf("activity failed with unexpected error: %v", err) 127 } 128 129 if terr.TimeoutType() != timeoutType { 130 return nil, fmt.Errorf("activity failed due to unexpected timeout %v", terr.TimeoutType()) 131 } 132 133 return []string{"sleep", "sleep", "sleep"}, nil 134 } 135 136 func (w *Workflows) ActivityRetryOnHBTimeout(ctx workflow.Context) ([]string, error) { 137 opts := w.defaultActivityOptionsWithRetry() 138 opts.HeartbeatTimeout = time.Second 139 ctx = workflow.WithActivityOptions(ctx, opts) 140 141 var result int 142 startTime := workflow.Now(ctx) 143 err := workflow.ExecuteActivity(ctx, "Activities_HeartbeatAndSleep", 0, 2*time.Second).Get(ctx, &result) 144 if err == nil { 145 return nil, fmt.Errorf("expected activity to fail but succeeded") 146 } 147 148 elapsed := workflow.Now(ctx).Sub(startTime) 149 if elapsed < 5*time.Second { 150 return nil, fmt.Errorf("expected activity to be retried on failure, but it was not") 151 } 152 153 terr, ok := err.(*workflow.TimeoutError) 154 if !ok { 155 return nil, fmt.Errorf("activity failed with unexpected error: %v", err) 156 } 157 158 if terr.TimeoutType() != shared.TimeoutTypeHeartbeat { 159 return nil, fmt.Errorf("activity failed due to unexpected timeout %v", terr.TimeoutType()) 160 } 161 162 if !terr.HasDetails() { 163 return nil, fmt.Errorf("timeout missing last heartbeat details") 164 } 165 166 if err := terr.Details(&result); err != nil { 167 return nil, err 168 } 169 170 if result != 3 { 171 return nil, fmt.Errorf("invalid heartbeat details: %v", result) 172 } 173 174 return []string{"heartbeatAndSleep", "heartbeatAndSleep", "heartbeatAndSleep"}, nil 175 } 176 177 func (w *Workflows) ActivityAutoHeartbeat(ctx workflow.Context) ([]string, error) { 178 opts := workflow.ActivityOptions{ 179 ScheduleToStartTimeout: time.Second, 180 ScheduleToCloseTimeout: 10 * time.Second, 181 StartToCloseTimeout: 10 * time.Second, 182 HeartbeatTimeout: 2 * time.Second, 183 } 184 ctx = workflow.WithActivityOptions(ctx, opts) 185 186 var result int 187 err := workflow.ExecuteActivity(ctx, "HeartbeatAndSleep", 0, 5*time.Second).Get(ctx, &result) 188 if err != nil { 189 return nil, fmt.Errorf("expected activity to succed but failed:%v", err) 190 } 191 if result != 1 { 192 return nil, fmt.Errorf("activity should only be executed once") 193 } 194 195 return []string{"heartbeatAndSleep"}, nil 196 } 197 198 func (w *Workflows) ContinueAsNew(ctx workflow.Context, count int, taskList string) (int, error) { 199 tl := workflow.GetInfo(ctx).TaskListName 200 if tl != taskList { 201 return -1, fmt.Errorf("invalid taskListName name, expected=%v, got=%v", taskList, tl) 202 } 203 if count == 0 { 204 return 999, nil 205 } 206 ctx = workflow.WithTaskList(ctx, taskList) 207 return -1, workflow.NewContinueAsNewError(ctx, w.ContinueAsNew, count-1, taskList) 208 } 209 210 func (w *Workflows) ContinueAsNewWithOptions(ctx workflow.Context, count int, taskList string) (string, error) { 211 info := workflow.GetInfo(ctx) 212 tl := info.TaskListName 213 if tl != taskList { 214 return "", fmt.Errorf("invalid taskListName name, expected=%v, got=%v", taskList, tl) 215 } 216 217 if info.Memo == nil || info.SearchAttributes == nil { 218 return "", errors.New("memo or search attributes are not carried over") 219 } 220 var memoVal, searchAttrVal string 221 err := client.NewValue(info.Memo.Fields["memoKey"]).Get(&memoVal) 222 if err != nil { 223 return "", errors.New("error when get memo value") 224 } 225 err = client.NewValue(info.SearchAttributes.IndexedFields["CustomKeywordField"]).Get(&searchAttrVal) 226 if err != nil { 227 return "", errors.New("error when get search attributes value") 228 } 229 230 if count == 0 { 231 return memoVal + "," + searchAttrVal, nil 232 } 233 ctx = workflow.WithTaskList(ctx, taskList) 234 235 return "", workflow.NewContinueAsNewError(ctx, w.ContinueAsNewWithOptions, count-1, taskList) 236 } 237 238 func (w *Workflows) IDReusePolicy( 239 ctx workflow.Context, 240 childWFID string, 241 policy client.WorkflowIDReusePolicy, 242 parallel bool, 243 failFirstChild bool) (string, error) { 244 245 ctx = workflow.WithChildOptions(ctx, workflow.ChildWorkflowOptions{ 246 WorkflowID: childWFID, 247 ExecutionStartToCloseTimeout: 9 * time.Second, 248 TaskStartToCloseTimeout: 5 * time.Second, 249 WorkflowIDReusePolicy: policy, 250 }) 251 252 var ans1 string 253 child1 := workflow.ExecuteChildWorkflow(ctx, w.child, "hello", failFirstChild) 254 if !parallel { 255 err := child1.Get(ctx, &ans1) 256 if failFirstChild && err == nil { 257 return "", fmt.Errorf("child1 succeeded when it was expected to fail") 258 } 259 if !failFirstChild && err != nil { 260 return "", fmt.Errorf("child1 failed when it was expected to succeed") 261 } 262 } 263 264 var ans2 string 265 if err := workflow.ExecuteChildWorkflow(ctx, w.child, "world", false).Get(ctx, &ans2); err != nil { 266 return "", err 267 } 268 269 if parallel { 270 err := child1.Get(ctx, &ans1) 271 if failFirstChild && err == nil { 272 return "", fmt.Errorf("child1 succeeded when it was expected to fail") 273 } 274 if !failFirstChild && err != nil { 275 return "", fmt.Errorf("child1 failed when it was expected to succeed") 276 } 277 } 278 279 return ans1 + ans2, nil 280 } 281 282 func (w *Workflows) ChildWorkflowRetryOnError(ctx workflow.Context) error { 283 opts := workflow.ChildWorkflowOptions{ 284 TaskStartToCloseTimeout: 5 * time.Second, 285 ExecutionStartToCloseTimeout: 9 * time.Second, 286 RetryPolicy: &cadence.RetryPolicy{ 287 InitialInterval: time.Second, 288 BackoffCoefficient: 2.0, 289 MaximumInterval: time.Second, 290 ExpirationInterval: 100 * time.Second, 291 MaximumAttempts: 3, 292 }, 293 } 294 ctx = workflow.WithChildOptions(ctx, opts) 295 var result string 296 return workflow.ExecuteChildWorkflow(ctx, w.child, "hello", true).Get(ctx, &result) 297 } 298 299 func (w *Workflows) ChildWorkflowRetryOnTimeout(ctx workflow.Context) error { 300 opts := workflow.ChildWorkflowOptions{ 301 TaskStartToCloseTimeout: time.Second, 302 ExecutionStartToCloseTimeout: time.Second, 303 RetryPolicy: &cadence.RetryPolicy{ 304 InitialInterval: time.Second, 305 BackoffCoefficient: 2.0, 306 MaximumInterval: time.Second, 307 ExpirationInterval: 100 * time.Second, 308 MaximumAttempts: 3, 309 }, 310 } 311 ctx = workflow.WithChildOptions(ctx, opts) 312 return workflow.ExecuteChildWorkflow(ctx, w.sleep, 2*time.Second).Get(ctx, nil) 313 } 314 315 func (w *Workflows) ChildWorkflowSuccess(ctx workflow.Context) (result string, err error) { 316 opts := workflow.ChildWorkflowOptions{ 317 TaskStartToCloseTimeout: 5 * time.Second, 318 ExecutionStartToCloseTimeout: 10 * time.Second, 319 Memo: map[string]interface{}{"memoKey": "memoVal"}, 320 SearchAttributes: map[string]interface{}{"CustomKeywordField": "searchAttrVal"}, 321 } 322 ctx = workflow.WithChildOptions(ctx, opts) 323 err = workflow.ExecuteChildWorkflow(ctx, w.childForMemoAndSearchAttr).Get(ctx, &result) 324 return 325 } 326 327 func (w *Workflows) ChildWorkflowSuccessWithParentClosePolicyTerminate(ctx workflow.Context) (result string, err error) { 328 opts := workflow.ChildWorkflowOptions{ 329 TaskStartToCloseTimeout: 5 * time.Second, 330 ExecutionStartToCloseTimeout: 30 * time.Second, 331 } 332 ctx = workflow.WithChildOptions(ctx, opts) 333 ft := workflow.ExecuteChildWorkflow(ctx, w.sleep, 20*time.Second) 334 err = workflow.Sleep(ctx, 5*time.Second) 335 if err != nil { 336 return "", err 337 } 338 var childWE internal.WorkflowExecution 339 err = ft.GetChildWorkflowExecution().Get(ctx, &childWE) 340 return childWE.ID, err 341 } 342 343 func (w *Workflows) ChildWorkflowSuccessWithParentClosePolicyAbandon(ctx workflow.Context) (result string, err error) { 344 opts := workflow.ChildWorkflowOptions{ 345 TaskStartToCloseTimeout: 5 * time.Second, 346 ExecutionStartToCloseTimeout: 30 * time.Second, 347 ParentClosePolicy: client.ParentClosePolicyAbandon, 348 } 349 ctx = workflow.WithChildOptions(ctx, opts) 350 ft := workflow.ExecuteChildWorkflow(ctx, w.sleep, 20*time.Second) 351 err = workflow.Sleep(ctx, 5*time.Second) 352 if err != nil { 353 return "", err 354 } 355 var childWE internal.WorkflowExecution 356 err = ft.GetChildWorkflowExecution().Get(ctx, &childWE) 357 return childWE.ID, err 358 } 359 360 func (w *Workflows) ChildWorkflowCancel(ctx workflow.Context) (result string, err error) { 361 opts := workflow.ChildWorkflowOptions{ 362 TaskStartToCloseTimeout: 5 * time.Second, 363 ExecutionStartToCloseTimeout: 30 * time.Second, 364 } 365 childCtx := workflow.WithChildOptions(ctx, opts) 366 childCtx, cancel := workflow.WithCancel(childCtx) 367 ft := workflow.ExecuteChildWorkflow(childCtx, w.sleep, 20*time.Second) 368 err = workflow.Sleep(ctx, time.Second) 369 if err != nil { 370 return "", err 371 } 372 cancel() 373 err = workflow.Sleep(ctx, time.Second) 374 if err != nil { 375 return "", err 376 } 377 var childWE internal.WorkflowExecution 378 err = ft.GetChildWorkflowExecution().Get(ctx, &childWE) 379 return childWE.ID, err 380 } 381 382 func (w *Workflows) ActivityCancelRepro(ctx workflow.Context) ([]string, error) { 383 ctx, cancelFunc := workflow.WithCancel(ctx) 384 385 // First go-routine which triggers cancellation on completion of first activity 386 workflow.Go(ctx, func(ctx1 workflow.Context) { 387 activityCtx := workflow.WithActivityOptions(ctx1, workflow.ActivityOptions{ 388 ScheduleToStartTimeout: 10 * time.Second, 389 ScheduleToCloseTimeout: 10 * time.Second, 390 StartToCloseTimeout: 9 * time.Second, 391 }) 392 393 activityF := workflow.ExecuteActivity(activityCtx, "Prefix_ToUpperWithDelay", "hello", 1*time.Second) 394 var ans string 395 err := activityF.Get(activityCtx, &ans) 396 if err != nil { 397 workflow.GetLogger(activityCtx).Sugar().Infof("Activity Failed: Err: %v", err) 398 return 399 } 400 401 // Trigger cancellation of root context 402 cancelFunc() 403 }) 404 405 // Second go-routine which get blocked on ActivitySchedule and not started 406 workflow.Go(ctx, func(ctx1 workflow.Context) { 407 activityCtx := workflow.WithActivityOptions(ctx1, workflow.ActivityOptions{ 408 ScheduleToStartTimeout: 10 * time.Second, 409 ScheduleToCloseTimeout: 10 * time.Second, 410 StartToCloseTimeout: 1 * time.Second, 411 TaskList: "bad_tl", 412 }) 413 414 activityF := workflow.ExecuteActivity(activityCtx, "Prefix_ToUpper", "hello") 415 var ans string 416 err := activityF.Get(activityCtx, &ans) 417 if err != nil { 418 workflow.GetLogger(activityCtx).Sugar().Infof("Activity Failed: Err: %v", err) 419 } 420 }) 421 422 // Third go-routine which get blocked on ActivitySchedule and not started 423 workflow.Go(ctx, func(ctx1 workflow.Context) { 424 activityCtx := workflow.WithActivityOptions(ctx1, workflow.ActivityOptions{ 425 ScheduleToStartTimeout: 10 * time.Second, 426 ScheduleToCloseTimeout: 10 * time.Second, 427 StartToCloseTimeout: 1 * time.Second, 428 TaskList: "bad_tl", 429 }) 430 431 activityF := workflow.ExecuteActivity(activityCtx, "Prefix_ToUpper", "hello") 432 var ans string 433 err := activityF.Get(activityCtx, &ans) 434 if err != nil { 435 workflow.GetLogger(activityCtx).Sugar().Infof("Activity Failed: Err: %v", err) 436 } 437 }) 438 439 // Cause the workflow to block on sleep 440 workflow.Sleep(ctx, 10*time.Second) 441 442 return []string{"toUpperWithDelay"}, nil 443 } 444 445 func (w *Workflows) SimplestWorkflow(ctx workflow.Context) (string, error) { 446 return "hello", nil 447 } 448 449 func (w *Workflows) LargeQueryResultWorkflow(ctx workflow.Context) (string, error) { 450 err := workflow.SetQueryHandler(ctx, "large_query", func() ([]byte, error) { 451 result := make([]byte, 3000000) 452 rand.Read(result) 453 return result, nil 454 }) 455 456 if err != nil { 457 return "", errors.New("failed to register query handler") 458 } 459 460 return "hello", nil 461 } 462 463 func (w *Workflows) ConsistentQueryWorkflow(ctx workflow.Context, delay time.Duration) error { 464 queryResult := "starting-value" 465 err := workflow.SetQueryHandler(ctx, "consistent_query", func() (string, error) { 466 return queryResult, nil 467 }) 468 if err != nil { 469 return errors.New("failed to register query handler") 470 } 471 ch := workflow.GetSignalChannel(ctx, consistentQuerySignalCh) 472 var signalData string 473 ch.Receive(ctx, &signalData) 474 laCtx := workflow.WithLocalActivityOptions(ctx, workflow.LocalActivityOptions{ 475 ScheduleToCloseTimeout: 5 * time.Second, 476 }) 477 478 workflowInfo := internal.GetWorkflowInfo(laCtx) 479 if &workflowInfo.WorkflowType == nil { 480 return errors.New("failed to get work flow type") 481 } 482 483 workflow.ExecuteLocalActivity(laCtx, LocalSleep, delay).Get(laCtx, nil) 484 queryResult = signalData 485 return nil 486 } 487 488 func (w *Workflows) RetryTimeoutStableErrorWorkflow(ctx workflow.Context) ([]string, error) { 489 ao := workflow.ActivityOptions{ 490 ScheduleToStartTimeout: time.Second * 2, 491 StartToCloseTimeout: time.Second * 6, 492 RetryPolicy: &cadence.RetryPolicy{ 493 InitialInterval: time.Second, 494 BackoffCoefficient: 1.0, 495 MaximumInterval: time.Second, 496 ExpirationInterval: time.Second * 5, 497 }, 498 } 499 ctx = workflow.WithActivityOptions(ctx, ao) 500 // Test calling activity by method pointer 501 // As Go allows nil receiver pointers it works fine 502 var a *Activities 503 err := workflow.ExecuteActivity(ctx, a.RetryTimeoutStableErrorActivity).Get(ctx, nil) 504 505 cerr, ok := err.(*cadence.CustomError) 506 if !ok { 507 return []string{}, fmt.Errorf("activity failed with unexpected error: %v", err) 508 } 509 if cerr.Reason() != errFailOnPurpose.Reason() { 510 return []string{}, fmt.Errorf("activity failed with unexpected error reason: %v", cerr.Reason()) 511 } 512 return []string{}, nil 513 } 514 515 func (w *Workflows) child(ctx workflow.Context, arg string, mustFail bool) (string, error) { 516 var result string 517 ctx = workflow.WithActivityOptions(ctx, w.defaultActivityOptions()) 518 err := workflow.ExecuteActivity(ctx, "Prefix_ToUpper", arg).Get(ctx, &result) 519 if mustFail { 520 return "", fmt.Errorf("failing-on-purpose") 521 } 522 return result, err 523 } 524 525 func (w *Workflows) childForMemoAndSearchAttr(ctx workflow.Context) (result string, err error) { 526 info := workflow.GetInfo(ctx) 527 var memo, searchAttr string 528 err = client.NewValue(info.Memo.Fields["memoKey"]).Get(&memo) 529 if err != nil { 530 return 531 } 532 err = client.NewValue(info.SearchAttributes.IndexedFields["CustomKeywordField"]).Get(&searchAttr) 533 if err != nil { 534 return 535 } 536 ctx = workflow.WithActivityOptions(ctx, w.defaultActivityOptions()) 537 err = workflow.ExecuteActivity(ctx, "Activities_GetMemoAndSearchAttr", memo, searchAttr).Get(ctx, &result) 538 return 539 } 540 541 func (w *Workflows) sleep(ctx workflow.Context, d time.Duration) error { 542 ctx = workflow.WithActivityOptions(ctx, w.defaultActivityOptions()) 543 return workflow.ExecuteActivity(ctx, "Activities_Sleep", d).Get(ctx, nil) 544 } 545 546 func (w *Workflows) InspectActivityInfo(ctx workflow.Context) error { 547 info := workflow.GetInfo(ctx) 548 domain := info.Domain 549 wfType := info.WorkflowType.Name 550 taskList := info.TaskListName 551 ctx = workflow.WithActivityOptions(ctx, w.defaultActivityOptions()) 552 return workflow.ExecuteActivity(ctx, "inspectActivityInfo", domain, taskList, wfType).Get(ctx, nil) 553 } 554 555 func (w *Workflows) InspectLocalActivityInfo(ctx workflow.Context) error { 556 info := workflow.GetInfo(ctx) 557 domain := info.Domain 558 wfType := info.WorkflowType.Name 559 taskList := info.TaskListName 560 ctx = workflow.WithLocalActivityOptions(ctx, w.defaultLocalActivityOptions()) 561 activities := Activities{} 562 return workflow.ExecuteLocalActivity( 563 ctx, activities.InspectActivityInfo, domain, taskList, wfType).Get(ctx, nil) 564 } 565 566 func (w *Workflows) WorkflowWithLocalActivityCtxPropagation(ctx workflow.Context) (string, error) { 567 ctx = workflow.WithLocalActivityOptions(ctx, w.defaultLocalActivityOptions()) 568 ctx = workflow.WithValue(ctx, contextKey(testContextKey), "test-data-in-context") 569 activities := Activities{} 570 var result string 571 err := workflow.ExecuteLocalActivity(ctx, activities.DuplicateStringInContext).Get(ctx, &result) 572 if err != nil { 573 return "", err 574 } 575 return result, nil 576 } 577 578 func (w *Workflows) NonDeterminismSimulatorWorkflow(ctx workflow.Context) ([]string, error) { 579 logger := workflow.GetLogger(ctx) 580 ctx = workflow.WithActivityOptions(ctx, w.defaultActivityOptions()) 581 582 workflow.SetQueryHandler(ctx, "custom_query", func() (int, error) { 583 return w.nonDeterminismSimulatorWorkflowCallCount, nil 584 }) 585 586 var res []string 587 588 // Mimic non-deterministic behavior with alternating calls to activity here. 589 // Workflow functon is invoked multiple times at every decision point and each time it will behave differently due to this optional activity call. 590 // This will be considered as non-deterministic error 591 if w.nonDeterminismSimulatorWorkflowCallCount%2 == 0 { 592 var ans string 593 if err := workflow.ExecuteActivity(ctx, "Prefix_ToUpper", "hello").Get(ctx, &ans); err != nil { 594 return nil, err 595 } 596 res = append(res, ans) 597 } 598 599 w.nonDeterminismSimulatorWorkflowCallCount++ 600 601 selector := workflow.NewSelector(ctx) 602 timer := workflow.NewTimer(ctx, 5*time.Second) 603 selector.AddFuture(timer, func(workflow.Future) { 604 logger.Info("Timer future is called") 605 }) 606 607 logger.Info("Workflow will wait on timer") 608 selector.Select(ctx) 609 logger.Info("Timer returned. calling another activity") 610 611 var ans string 612 if err := workflow.ExecuteActivity(ctx, "Prefix_ToUpper", "hello").Get(ctx, &ans); err != nil { 613 return nil, err 614 } 615 res = append(res, ans) 616 617 return res, nil 618 } 619 620 func (w *Workflows) register(worker worker.Worker) { 621 // Kept to verify backward compatibility of workflow registration. 622 workflow.RegisterWithOptions(w.Basic, workflow.RegisterOptions{DisableAlreadyRegisteredCheck: true}) 623 worker.RegisterWorkflow(w.ActivityRetryOnError) 624 worker.RegisterWorkflow(w.ActivityRetryOnHBTimeout) 625 worker.RegisterWorkflow(w.ActivityAutoHeartbeat) 626 worker.RegisterWorkflow(w.ActivityRetryOnTimeout) 627 worker.RegisterWorkflow(w.ActivityRetryOptionsChange) 628 worker.RegisterWorkflow(w.ContinueAsNew) 629 worker.RegisterWorkflow(w.ContinueAsNewWithOptions) 630 worker.RegisterWorkflow(w.IDReusePolicy) 631 worker.RegisterWorkflow(w.ChildWorkflowRetryOnError) 632 worker.RegisterWorkflow(w.ChildWorkflowRetryOnTimeout) 633 worker.RegisterWorkflow(w.ChildWorkflowSuccess) 634 worker.RegisterWorkflow(w.ChildWorkflowSuccessWithParentClosePolicyTerminate) 635 worker.RegisterWorkflow(w.ChildWorkflowSuccessWithParentClosePolicyAbandon) 636 worker.RegisterWorkflow(w.ChildWorkflowCancel) 637 worker.RegisterWorkflow(w.InspectActivityInfo) 638 worker.RegisterWorkflow(w.InspectLocalActivityInfo) 639 worker.RegisterWorkflow(w.sleep) 640 worker.RegisterWorkflow(w.child) 641 worker.RegisterWorkflow(w.childForMemoAndSearchAttr) 642 worker.RegisterWorkflow(w.ActivityCancelRepro) 643 worker.RegisterWorkflow(w.SimplestWorkflow) 644 worker.RegisterWorkflow(w.LargeQueryResultWorkflow) 645 worker.RegisterWorkflow(w.RetryTimeoutStableErrorWorkflow) 646 worker.RegisterWorkflow(w.ConsistentQueryWorkflow) 647 worker.RegisterWorkflow(w.WorkflowWithLocalActivityCtxPropagation) 648 worker.RegisterWorkflow(w.NonDeterminismSimulatorWorkflow) 649 650 } 651 652 func (w *Workflows) defaultActivityOptions() workflow.ActivityOptions { 653 return workflow.ActivityOptions{ 654 ScheduleToStartTimeout: 5 * time.Second, 655 ScheduleToCloseTimeout: 5 * time.Second, 656 StartToCloseTimeout: 9 * time.Second, 657 } 658 } 659 660 func (w *Workflows) defaultLocalActivityOptions() workflow.LocalActivityOptions { 661 return workflow.LocalActivityOptions{ 662 ScheduleToCloseTimeout: 5 * time.Second, 663 } 664 } 665 666 func (w *Workflows) defaultActivityOptionsWithRetry() workflow.ActivityOptions { 667 return workflow.ActivityOptions{ 668 ScheduleToStartTimeout: 5 * time.Second, 669 ScheduleToCloseTimeout: 5 * time.Second, 670 StartToCloseTimeout: 9 * time.Second, 671 RetryPolicy: &cadence.RetryPolicy{ 672 InitialInterval: time.Second, 673 BackoffCoefficient: 2.0, 674 MaximumInterval: time.Second, 675 ExpirationInterval: 100 * time.Second, 676 MaximumAttempts: 3, 677 }, 678 } 679 }