go.uber.org/cadence@v1.2.9/internal/internal_task_handlers_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 internal 23 24 import ( 25 "context" 26 "encoding/json" 27 "errors" 28 "fmt" 29 "reflect" 30 "strings" 31 "sync" 32 "testing" 33 "time" 34 35 "github.com/golang/mock/gomock" 36 "github.com/opentracing/opentracing-go" 37 "github.com/pborman/uuid" 38 "github.com/stretchr/testify/require" 39 "github.com/stretchr/testify/suite" 40 "go.uber.org/goleak" 41 "go.uber.org/zap" 42 "go.uber.org/zap/zapcore" 43 "go.uber.org/zap/zaptest" 44 "go.uber.org/zap/zaptest/observer" 45 46 "go.uber.org/cadence/.gen/go/cadence/workflowservicetest" 47 s "go.uber.org/cadence/.gen/go/shared" 48 "go.uber.org/cadence/internal/common" 49 ) 50 51 const ( 52 testDomain = "test-domain" 53 ) 54 55 type ( 56 TaskHandlersTestSuite struct { 57 suite.Suite 58 logger *zap.Logger 59 service *workflowservicetest.MockClient 60 registry *registry 61 } 62 ) 63 64 func registerWorkflows(r *registry) { 65 r.RegisterWorkflowWithOptions( 66 helloWorldWorkflowFunc, 67 RegisterWorkflowOptions{Name: "HelloWorld_Workflow"}, 68 ) 69 r.RegisterWorkflowWithOptions( 70 helloWorldWorkflowCancelFunc, 71 RegisterWorkflowOptions{Name: "HelloWorld_WorkflowCancel"}, 72 ) 73 r.RegisterWorkflowWithOptions( 74 returnPanicWorkflowFunc, 75 RegisterWorkflowOptions{Name: "ReturnPanicWorkflow"}, 76 ) 77 r.RegisterWorkflowWithOptions( 78 panicWorkflowFunc, 79 RegisterWorkflowOptions{Name: "PanicWorkflow"}, 80 ) 81 r.RegisterWorkflowWithOptions( 82 getWorkflowInfoWorkflowFunc, 83 RegisterWorkflowOptions{Name: "GetWorkflowInfoWorkflow"}, 84 ) 85 r.RegisterWorkflowWithOptions( 86 querySignalWorkflowFunc, 87 RegisterWorkflowOptions{Name: "QuerySignalWorkflow"}, 88 ) 89 r.RegisterActivityWithOptions( 90 greeterActivityFunc, 91 RegisterActivityOptions{Name: "Greeter_Activity"}, 92 ) 93 r.RegisterWorkflowWithOptions( 94 binaryChecksumWorkflowFunc, 95 RegisterWorkflowOptions{Name: "BinaryChecksumWorkflow"}, 96 ) 97 } 98 99 func returnPanicWorkflowFunc(ctx Context, input []byte) error { 100 return newPanicError("panicError", "stackTrace") 101 } 102 103 func panicWorkflowFunc(ctx Context, input []byte) error { 104 panic("panicError") 105 } 106 107 func getWorkflowInfoWorkflowFunc(ctx Context, expectedLastCompletionResult string) (info *WorkflowInfo, err error) { 108 result := GetWorkflowInfo(ctx) 109 var lastCompletionResult string 110 err = getDefaultDataConverter().FromData(result.lastCompletionResult, &lastCompletionResult) 111 if err != nil { 112 return nil, err 113 } 114 if lastCompletionResult != expectedLastCompletionResult { 115 return nil, errors.New("lastCompletionResult is not " + expectedLastCompletionResult) 116 } 117 return result, nil 118 } 119 120 // Test suite. 121 func (t *TaskHandlersTestSuite) SetupTest() { 122 t.logger = zaptest.NewLogger(t.T()) 123 } 124 125 func (t *TaskHandlersTestSuite) SetupSuite() { 126 t.logger = zaptest.NewLogger(t.T()) 127 registerWorkflows(t.registry) 128 } 129 130 func TestTaskHandlersTestSuite(t *testing.T) { 131 suite.Run(t, &TaskHandlersTestSuite{ 132 registry: newRegistry(), 133 }) 134 } 135 136 func createTestEventWorkflowExecutionCompleted(eventID int64, attr *s.WorkflowExecutionCompletedEventAttributes) *s.HistoryEvent { 137 return &s.HistoryEvent{EventId: common.Int64Ptr(eventID), EventType: common.EventTypePtr(s.EventTypeWorkflowExecutionCompleted), WorkflowExecutionCompletedEventAttributes: attr} 138 } 139 140 func createTestEventWorkflowExecutionContinuedAsNew(eventID int64, attr *s.WorkflowExecutionContinuedAsNewEventAttributes) *s.HistoryEvent { 141 return &s.HistoryEvent{EventId: common.Int64Ptr(eventID), EventType: common.EventTypePtr(s.EventTypeWorkflowExecutionContinuedAsNew), WorkflowExecutionContinuedAsNewEventAttributes: attr} 142 } 143 144 func createTestEventWorkflowExecutionStarted(eventID int64, attr *s.WorkflowExecutionStartedEventAttributes) *s.HistoryEvent { 145 return &s.HistoryEvent{EventId: common.Int64Ptr(eventID), EventType: common.EventTypePtr(s.EventTypeWorkflowExecutionStarted), WorkflowExecutionStartedEventAttributes: attr} 146 } 147 148 func createTestEventLocalActivity(eventID int64, attr *s.MarkerRecordedEventAttributes) *s.HistoryEvent { 149 return &s.HistoryEvent{ 150 EventId: common.Int64Ptr(eventID), 151 EventType: common.EventTypePtr(s.EventTypeMarkerRecorded), 152 MarkerRecordedEventAttributes: attr} 153 } 154 155 func createTestEventActivityTaskScheduled(eventID int64, attr *s.ActivityTaskScheduledEventAttributes) *s.HistoryEvent { 156 return &s.HistoryEvent{ 157 EventId: common.Int64Ptr(eventID), 158 EventType: common.EventTypePtr(s.EventTypeActivityTaskScheduled), 159 ActivityTaskScheduledEventAttributes: attr} 160 } 161 162 func createTestEventActivityTaskStarted(eventID int64, attr *s.ActivityTaskStartedEventAttributes) *s.HistoryEvent { 163 return &s.HistoryEvent{ 164 EventId: common.Int64Ptr(eventID), 165 EventType: common.EventTypePtr(s.EventTypeActivityTaskStarted), 166 ActivityTaskStartedEventAttributes: attr} 167 } 168 169 func createTestEventActivityTaskCompleted(eventID int64, attr *s.ActivityTaskCompletedEventAttributes) *s.HistoryEvent { 170 return &s.HistoryEvent{ 171 EventId: common.Int64Ptr(eventID), 172 EventType: common.EventTypePtr(s.EventTypeActivityTaskCompleted), 173 ActivityTaskCompletedEventAttributes: attr} 174 } 175 176 func createTestEventActivityTaskTimedOut(eventID int64, attr *s.ActivityTaskTimedOutEventAttributes) *s.HistoryEvent { 177 return &s.HistoryEvent{ 178 EventId: common.Int64Ptr(eventID), 179 EventType: common.EventTypePtr(s.EventTypeActivityTaskTimedOut), 180 ActivityTaskTimedOutEventAttributes: attr} 181 } 182 183 func createTestEventDecisionTaskScheduled(eventID int64, attr *s.DecisionTaskScheduledEventAttributes) *s.HistoryEvent { 184 return &s.HistoryEvent{ 185 EventId: common.Int64Ptr(eventID), 186 EventType: common.EventTypePtr(s.EventTypeDecisionTaskScheduled), 187 DecisionTaskScheduledEventAttributes: attr} 188 } 189 190 func createTestEventDecisionTaskStarted(eventID int64) *s.HistoryEvent { 191 return &s.HistoryEvent{ 192 EventId: common.Int64Ptr(eventID), 193 EventType: common.EventTypePtr(s.EventTypeDecisionTaskStarted)} 194 } 195 196 func createTestEventWorkflowExecutionSignaled(eventID int64, signalName string) *s.HistoryEvent { 197 return createTestEventWorkflowExecutionSignaledWithPayload(eventID, signalName, nil) 198 } 199 200 func createTestEventWorkflowExecutionSignaledWithPayload(eventID int64, signalName string, payload []byte) *s.HistoryEvent { 201 return &s.HistoryEvent{ 202 EventId: common.Int64Ptr(eventID), 203 EventType: common.EventTypePtr(s.EventTypeWorkflowExecutionSignaled), 204 WorkflowExecutionSignaledEventAttributes: &s.WorkflowExecutionSignaledEventAttributes{ 205 SignalName: common.StringPtr(signalName), 206 Input: payload, 207 Identity: common.StringPtr("test-identity"), 208 }, 209 } 210 } 211 212 func createTestEventDecisionTaskCompleted(eventID int64, attr *s.DecisionTaskCompletedEventAttributes) *s.HistoryEvent { 213 return &s.HistoryEvent{ 214 EventId: common.Int64Ptr(eventID), 215 EventType: common.EventTypePtr(s.EventTypeDecisionTaskCompleted), 216 DecisionTaskCompletedEventAttributes: attr} 217 } 218 219 func createTestEventDecisionTaskFailed(eventID int64, attr *s.DecisionTaskFailedEventAttributes) *s.HistoryEvent { 220 return &s.HistoryEvent{ 221 EventId: common.Int64Ptr(eventID), 222 EventType: common.EventTypePtr(s.EventTypeDecisionTaskFailed), 223 DecisionTaskFailedEventAttributes: attr} 224 } 225 226 func createTestEventSignalExternalWorkflowExecutionFailed(eventID int64, attr *s.SignalExternalWorkflowExecutionFailedEventAttributes) *s.HistoryEvent { 227 return &s.HistoryEvent{ 228 EventId: common.Int64Ptr(eventID), 229 EventType: common.EventTypePtr(s.EventTypeSignalExternalWorkflowExecutionFailed), 230 SignalExternalWorkflowExecutionFailedEventAttributes: attr, 231 } 232 } 233 234 func createWorkflowTask( 235 events []*s.HistoryEvent, 236 previousStartEventID int64, 237 workflowName string, 238 ) *s.PollForDecisionTaskResponse { 239 return createWorkflowTaskWithQueries(events, previousStartEventID, workflowName, nil) 240 } 241 242 func createWorkflowTaskWithQueries( 243 events []*s.HistoryEvent, 244 previousStartEventID int64, 245 workflowName string, 246 queries map[string]*s.WorkflowQuery, 247 ) *s.PollForDecisionTaskResponse { 248 eventsCopy := make([]*s.HistoryEvent, len(events)) 249 copy(eventsCopy, events) 250 return &s.PollForDecisionTaskResponse{ 251 PreviousStartedEventId: common.Int64Ptr(previousStartEventID), 252 WorkflowType: workflowTypePtr(WorkflowType{workflowName}), 253 History: &s.History{Events: eventsCopy}, 254 WorkflowExecution: &s.WorkflowExecution{ 255 WorkflowId: common.StringPtr("fake-workflow-id"), 256 RunId: common.StringPtr(uuid.New()), 257 }, 258 Queries: queries, 259 } 260 } 261 262 func createQueryTask( 263 events []*s.HistoryEvent, 264 previousStartEventID int64, 265 workflowName string, 266 queryType string, 267 ) *s.PollForDecisionTaskResponse { 268 task := createWorkflowTask(events, previousStartEventID, workflowName) 269 task.Query = &s.WorkflowQuery{ 270 QueryType: common.StringPtr(queryType), 271 } 272 return task 273 } 274 275 func createTestEventTimerStarted(eventID int64, id int) *s.HistoryEvent { 276 timerID := fmt.Sprintf("%v", id) 277 attr := &s.TimerStartedEventAttributes{ 278 TimerId: common.StringPtr(timerID), 279 StartToFireTimeoutSeconds: nil, 280 DecisionTaskCompletedEventId: nil, 281 } 282 return &s.HistoryEvent{ 283 EventId: common.Int64Ptr(eventID), 284 EventType: common.EventTypePtr(s.EventTypeTimerStarted), 285 TimerStartedEventAttributes: attr} 286 } 287 288 func createTestEventTimerFired(eventID int64, id int) *s.HistoryEvent { 289 timerID := fmt.Sprintf("%v", id) 290 attr := &s.TimerFiredEventAttributes{ 291 TimerId: common.StringPtr(timerID), 292 } 293 294 return &s.HistoryEvent{ 295 EventId: common.Int64Ptr(eventID), 296 EventType: common.EventTypePtr(s.EventTypeTimerFired), 297 TimerFiredEventAttributes: attr} 298 } 299 300 func findLogField(entry observer.LoggedEntry, fieldName string) *zapcore.Field { 301 for _, field := range entry.Context { 302 if field.Key == fieldName { 303 return &field 304 } 305 } 306 return nil 307 } 308 309 var testWorkflowTaskTasklist = "tl1" 310 311 func (t *TaskHandlersTestSuite) testWorkflowTaskWorkflowExecutionStartedHelper(params workerExecutionParameters) { 312 testEvents := []*s.HistoryEvent{ 313 createTestEventWorkflowExecutionStarted(1, &s.WorkflowExecutionStartedEventAttributes{TaskList: &s.TaskList{Name: &testWorkflowTaskTasklist}}), 314 } 315 task := createWorkflowTask(testEvents, 0, "HelloWorld_Workflow") 316 taskHandler := newWorkflowTaskHandler(testDomain, params, nil, t.registry) 317 request, err := taskHandler.ProcessWorkflowTask(&workflowTask{task: task}, nil) 318 response := request.(*s.RespondDecisionTaskCompletedRequest) 319 t.NoError(err) 320 t.NotNil(response) 321 t.Equal(1, len(response.Decisions)) 322 t.Equal(s.DecisionTypeScheduleActivityTask, response.Decisions[0].GetDecisionType()) 323 t.NotNil(response.Decisions[0].ScheduleActivityTaskDecisionAttributes) 324 } 325 326 func (t *TaskHandlersTestSuite) TestWorkflowTask_WorkflowExecutionStarted() { 327 params := workerExecutionParameters{ 328 TaskList: testWorkflowTaskTasklist, 329 WorkerOptions: WorkerOptions{ 330 Identity: "test-id-1", 331 Logger: t.logger, 332 }, 333 } 334 t.testWorkflowTaskWorkflowExecutionStartedHelper(params) 335 } 336 337 func (t *TaskHandlersTestSuite) TestWorkflowTask_WorkflowExecutionStarted_WithDataConverter() { 338 params := workerExecutionParameters{ 339 TaskList: testWorkflowTaskTasklist, 340 WorkerOptions: WorkerOptions{ 341 Identity: "test-id-1", 342 Logger: t.logger, 343 DataConverter: newTestDataConverter(), 344 }, 345 } 346 t.testWorkflowTaskWorkflowExecutionStartedHelper(params) 347 } 348 349 func (t *TaskHandlersTestSuite) TestWorkflowTask_BinaryChecksum() { 350 taskList := "tl1" 351 checksum1 := "chck1" 352 checksum2 := "chck2" 353 testEvents := []*s.HistoryEvent{ 354 createTestEventWorkflowExecutionStarted(1, &s.WorkflowExecutionStartedEventAttributes{TaskList: &s.TaskList{Name: &taskList}}), 355 createTestEventDecisionTaskScheduled(2, &s.DecisionTaskScheduledEventAttributes{TaskList: &s.TaskList{Name: &taskList}}), 356 createTestEventDecisionTaskStarted(3), 357 createTestEventDecisionTaskCompleted(4, &s.DecisionTaskCompletedEventAttributes{ 358 ScheduledEventId: common.Int64Ptr(2), BinaryChecksum: common.StringPtr(checksum1)}), 359 createTestEventTimerStarted(5, 0), 360 createTestEventTimerFired(6, 0), 361 createTestEventDecisionTaskScheduled(7, &s.DecisionTaskScheduledEventAttributes{TaskList: &s.TaskList{Name: &taskList}}), 362 createTestEventDecisionTaskStarted(8), 363 createTestEventDecisionTaskCompleted(9, &s.DecisionTaskCompletedEventAttributes{ 364 ScheduledEventId: common.Int64Ptr(7), BinaryChecksum: common.StringPtr(checksum2)}), 365 createTestEventTimerStarted(10, 1), 366 createTestEventTimerFired(11, 1), 367 createTestEventDecisionTaskScheduled(12, &s.DecisionTaskScheduledEventAttributes{TaskList: &s.TaskList{Name: &taskList}}), 368 createTestEventDecisionTaskStarted(13), 369 } 370 task := createWorkflowTask(testEvents, 8, "BinaryChecksumWorkflow") 371 params := workerExecutionParameters{ 372 TaskList: taskList, 373 WorkerOptions: WorkerOptions{ 374 Identity: "test-id-1", 375 Logger: t.logger, 376 }, 377 } 378 taskHandler := newWorkflowTaskHandler(testDomain, params, nil, t.registry) 379 request, err := taskHandler.ProcessWorkflowTask(&workflowTask{task: task}, nil) 380 response := request.(*s.RespondDecisionTaskCompletedRequest) 381 382 t.NoError(err) 383 t.NotNil(response) 384 t.Equal(1, len(response.Decisions)) 385 t.Equal(s.DecisionTypeCompleteWorkflowExecution, response.Decisions[0].GetDecisionType()) 386 checksumsBytes := response.Decisions[0].CompleteWorkflowExecutionDecisionAttributes.Result 387 var checksums []string 388 json.Unmarshal(checksumsBytes, &checksums) 389 t.Equal(3, len(checksums)) 390 t.Equal("chck1", checksums[0]) 391 t.Equal("chck2", checksums[1]) 392 t.Equal(getBinaryChecksum(), checksums[2]) 393 } 394 395 func (t *TaskHandlersTestSuite) TestWorkflowTask_ActivityTaskScheduled() { 396 // Schedule an activity and see if we complete workflow. 397 taskList := "tl1" 398 testEvents := []*s.HistoryEvent{ 399 createTestEventWorkflowExecutionStarted(1, &s.WorkflowExecutionStartedEventAttributes{TaskList: &s.TaskList{Name: &taskList}}), 400 createTestEventDecisionTaskScheduled(2, &s.DecisionTaskScheduledEventAttributes{TaskList: &s.TaskList{Name: &taskList}}), 401 createTestEventDecisionTaskStarted(3), 402 createTestEventDecisionTaskCompleted(4, &s.DecisionTaskCompletedEventAttributes{ScheduledEventId: common.Int64Ptr(2)}), 403 createTestEventActivityTaskScheduled(5, &s.ActivityTaskScheduledEventAttributes{ 404 ActivityId: common.StringPtr("0"), 405 ActivityType: &s.ActivityType{Name: common.StringPtr("Greeter_Activity")}, 406 TaskList: &s.TaskList{Name: &taskList}, 407 }), 408 createTestEventActivityTaskStarted(6, &s.ActivityTaskStartedEventAttributes{}), 409 createTestEventActivityTaskCompleted(7, &s.ActivityTaskCompletedEventAttributes{ScheduledEventId: common.Int64Ptr(5)}), 410 createTestEventDecisionTaskStarted(8), 411 } 412 task := createWorkflowTask(testEvents[0:3], 0, "HelloWorld_Workflow") 413 params := workerExecutionParameters{ 414 TaskList: taskList, 415 WorkerOptions: WorkerOptions{ 416 Identity: "test-id-1", 417 Logger: t.logger, 418 }, 419 } 420 taskHandler := newWorkflowTaskHandler(testDomain, params, nil, t.registry) 421 request, err := taskHandler.ProcessWorkflowTask(&workflowTask{task: task}, nil) 422 response := request.(*s.RespondDecisionTaskCompletedRequest) 423 424 t.NoError(err) 425 t.NotNil(response) 426 t.Equal(1, len(response.Decisions)) 427 t.Equal(s.DecisionTypeScheduleActivityTask, response.Decisions[0].GetDecisionType()) 428 t.NotNil(response.Decisions[0].ScheduleActivityTaskDecisionAttributes) 429 430 // Schedule an activity and see if we complete workflow, Having only one last decision. 431 task = createWorkflowTask(testEvents, 3, "HelloWorld_Workflow") 432 request, err = taskHandler.ProcessWorkflowTask(&workflowTask{task: task}, nil) 433 response = request.(*s.RespondDecisionTaskCompletedRequest) 434 t.NoError(err) 435 t.NotNil(response) 436 t.Equal(1, len(response.Decisions)) 437 t.Equal(s.DecisionTypeCompleteWorkflowExecution, response.Decisions[0].GetDecisionType()) 438 t.NotNil(response.Decisions[0].CompleteWorkflowExecutionDecisionAttributes) 439 } 440 441 func (t *TaskHandlersTestSuite) TestWorkflowTask_QueryWorkflow() { 442 // Schedule an activity and see if we complete workflow. 443 taskList := "sticky-tl" 444 execution := &s.WorkflowExecution{ 445 WorkflowId: common.StringPtr("fake-workflow-id"), 446 RunId: common.StringPtr(uuid.New()), 447 } 448 testEvents := []*s.HistoryEvent{ 449 createTestEventWorkflowExecutionStarted(1, &s.WorkflowExecutionStartedEventAttributes{TaskList: &s.TaskList{Name: &taskList}}), 450 createTestEventDecisionTaskScheduled(2, &s.DecisionTaskScheduledEventAttributes{TaskList: &s.TaskList{Name: &taskList}}), 451 createTestEventDecisionTaskStarted(3), 452 createTestEventDecisionTaskCompleted(4, &s.DecisionTaskCompletedEventAttributes{ScheduledEventId: common.Int64Ptr(2)}), 453 createTestEventActivityTaskScheduled(5, &s.ActivityTaskScheduledEventAttributes{ 454 ActivityId: common.StringPtr("0"), 455 ActivityType: &s.ActivityType{Name: common.StringPtr("Greeter_Activity")}, 456 TaskList: &s.TaskList{Name: &taskList}, 457 }), 458 createTestEventActivityTaskStarted(6, &s.ActivityTaskStartedEventAttributes{}), 459 createTestEventActivityTaskCompleted(7, &s.ActivityTaskCompletedEventAttributes{ScheduledEventId: common.Int64Ptr(5)}), 460 } 461 params := workerExecutionParameters{ 462 TaskList: taskList, 463 WorkerOptions: WorkerOptions{ 464 Identity: "test-id-1", 465 Logger: t.logger, 466 }, 467 } 468 taskHandler := newWorkflowTaskHandler(testDomain, params, nil, t.registry) 469 470 // first make progress on the workflow 471 task := createWorkflowTask(testEvents[0:1], 0, "HelloWorld_Workflow") 472 task.StartedEventId = common.Int64Ptr(1) 473 task.WorkflowExecution = execution 474 request, err := taskHandler.ProcessWorkflowTask(&workflowTask{task: task}, nil) 475 response := request.(*s.RespondDecisionTaskCompletedRequest) 476 t.NoError(err) 477 t.NotNil(response) 478 t.Equal(1, len(response.Decisions)) 479 t.Equal(s.DecisionTypeScheduleActivityTask, response.Decisions[0].GetDecisionType()) 480 t.NotNil(response.Decisions[0].ScheduleActivityTaskDecisionAttributes) 481 482 // then check the current state using query task 483 task = createQueryTask([]*s.HistoryEvent{}, 6, "HelloWorld_Workflow", queryType) 484 task.WorkflowExecution = execution 485 queryResp, err := taskHandler.ProcessWorkflowTask(&workflowTask{task: task}, nil) 486 t.NoError(err) 487 t.verifyQueryResult(queryResp, "waiting-activity-result") 488 } 489 490 func (t *TaskHandlersTestSuite) TestWorkflowTask_QueryWorkflow_2() { 491 // Schedule an activity and see if we complete workflow. 492 493 // This test appears to be just a finer-grained version of TestWorkflowTask_QueryWorkflow, though the older names 494 // for them implied entirely different purposes. Likely it can be combined with TestWorkflowTask_QueryWorkflow 495 // without losing anything useful. 496 taskList := "tl1" 497 testEvents := []*s.HistoryEvent{ 498 createTestEventWorkflowExecutionStarted(1, &s.WorkflowExecutionStartedEventAttributes{TaskList: &s.TaskList{Name: &taskList}}), 499 createTestEventDecisionTaskScheduled(2, &s.DecisionTaskScheduledEventAttributes{TaskList: &s.TaskList{Name: &taskList}}), 500 createTestEventDecisionTaskStarted(3), 501 createTestEventDecisionTaskCompleted(4, &s.DecisionTaskCompletedEventAttributes{ScheduledEventId: common.Int64Ptr(2)}), 502 createTestEventActivityTaskScheduled(5, &s.ActivityTaskScheduledEventAttributes{ 503 ActivityId: common.StringPtr("0"), 504 ActivityType: &s.ActivityType{Name: common.StringPtr("Greeter_Activity")}, 505 TaskList: &s.TaskList{Name: &taskList}, 506 }), 507 createTestEventActivityTaskStarted(6, &s.ActivityTaskStartedEventAttributes{}), 508 createTestEventActivityTaskCompleted(7, &s.ActivityTaskCompletedEventAttributes{ScheduledEventId: common.Int64Ptr(5)}), 509 // TODO: below seems irrational. there's a start without a schedule, and this workflow does not respond to signals. 510 // aside from this, the list of tasks is the same as TestWorkflowTask_QueryWorkflow 511 createTestEventDecisionTaskStarted(8), 512 createTestEventWorkflowExecutionSignaled(9, "test-signal"), 513 } 514 params := workerExecutionParameters{ 515 TaskList: taskList, 516 WorkerOptions: WorkerOptions{ 517 Identity: "test-id-1", 518 Logger: t.logger, 519 }, 520 } 521 522 // TODO: the following comment is not true, previousStartEventID is either 0 or points to a valid decisionTaskStartedID 523 // we need to fix the test 524 // 525 // query after first decision task (notice the previousStartEventID is always the last eventID for query task) 526 task := createQueryTask(testEvents[0:3], 3, "HelloWorld_Workflow", queryType) 527 taskHandler := newWorkflowTaskHandler(testDomain, params, nil, t.registry) 528 response, _ := taskHandler.ProcessWorkflowTask(&workflowTask{task: task}, nil) 529 t.verifyQueryResult(response, "waiting-activity-result") 530 531 // query after activity task complete but before second decision task started 532 task = createQueryTask(testEvents[0:7], 7, "HelloWorld_Workflow", queryType) 533 taskHandler = newWorkflowTaskHandler(testDomain, params, nil, t.registry) 534 response, _ = taskHandler.ProcessWorkflowTask(&workflowTask{task: task}, nil) 535 t.verifyQueryResult(response, "waiting-activity-result") 536 537 // query after second decision task 538 task = createQueryTask(testEvents[0:8], 8, "HelloWorld_Workflow", queryType) 539 taskHandler = newWorkflowTaskHandler(testDomain, params, nil, t.registry) 540 response, _ = taskHandler.ProcessWorkflowTask(&workflowTask{task: task}, nil) 541 t.verifyQueryResult(response, "done") 542 543 // query after second decision task with extra events 544 task = createQueryTask(testEvents[0:9], 9, "HelloWorld_Workflow", queryType) 545 taskHandler = newWorkflowTaskHandler(testDomain, params, nil, t.registry) 546 response, _ = taskHandler.ProcessWorkflowTask(&workflowTask{task: task}, nil) 547 t.verifyQueryResult(response, "done") 548 549 task = createQueryTask(testEvents[0:9], 9, "HelloWorld_Workflow", "invalid-query-type") 550 taskHandler = newWorkflowTaskHandler(testDomain, params, nil, t.registry) 551 response, _ = taskHandler.ProcessWorkflowTask(&workflowTask{task: task}, nil) 552 t.NotNil(response) 553 queryResp, ok := response.(*s.RespondQueryTaskCompletedRequest) 554 t.True(ok) 555 t.NotNil(queryResp.ErrorMessage) 556 t.Contains(*queryResp.ErrorMessage, "unknown queryType") 557 } 558 559 func (t *TaskHandlersTestSuite) verifyQueryResult(response interface{}, expectedResult string) { 560 t.NotNil(response) 561 queryResp, ok := response.(*s.RespondQueryTaskCompletedRequest) 562 t.True(ok) 563 t.Nil(queryResp.ErrorMessage) 564 t.NotNil(queryResp.QueryResult) 565 encodedValue := newEncodedValue(queryResp.QueryResult, nil) 566 var queryResult string 567 err := encodedValue.Get(&queryResult) 568 t.NoError(err) 569 t.Equal(expectedResult, queryResult) 570 } 571 572 func (t *TaskHandlersTestSuite) TestCacheEvictionWhenErrorOccurs() { 573 taskList := "taskList" 574 testEvents := []*s.HistoryEvent{ 575 createTestEventWorkflowExecutionStarted(1, &s.WorkflowExecutionStartedEventAttributes{TaskList: &s.TaskList{Name: &taskList}}), 576 createTestEventDecisionTaskScheduled(2, &s.DecisionTaskScheduledEventAttributes{TaskList: &s.TaskList{Name: &taskList}}), 577 createTestEventDecisionTaskStarted(3), 578 createTestEventDecisionTaskCompleted(4, &s.DecisionTaskCompletedEventAttributes{ScheduledEventId: common.Int64Ptr(2)}), 579 createTestEventActivityTaskScheduled(5, &s.ActivityTaskScheduledEventAttributes{ 580 ActivityId: common.StringPtr("0"), 581 ActivityType: &s.ActivityType{Name: common.StringPtr("pkg.Greeter_Activity")}, 582 TaskList: &s.TaskList{Name: &taskList}, 583 }), 584 } 585 params := workerExecutionParameters{ 586 TaskList: taskList, 587 WorkerOptions: WorkerOptions{ 588 Identity: "test-id-1", 589 Logger: zap.NewNop(), 590 NonDeterministicWorkflowPolicy: NonDeterministicWorkflowPolicyBlockWorkflow, 591 }, 592 } 593 594 taskHandler := newWorkflowTaskHandler(testDomain, params, nil, t.registry) 595 // now change the history event so it does not match to decision produced via replay 596 testEvents[4].ActivityTaskScheduledEventAttributes.ActivityType.Name = common.StringPtr("some-other-activity") 597 task := createWorkflowTask(testEvents, 3, "HelloWorld_Workflow") 598 // newWorkflowTaskWorkerInternal will set the laTunnel in taskHandler, without it, ProcessWorkflowTask() 599 // will fail as it can't find laTunnel in getWorkflowCache(). 600 newWorkflowTaskWorkerInternal(taskHandler, t.service, testDomain, params, make(chan struct{}), nil) 601 request, err := taskHandler.ProcessWorkflowTask(&workflowTask{task: task}, nil) 602 603 t.Error(err) 604 t.Nil(request) 605 t.Contains(err.Error(), "nondeterministic") 606 607 // There should be nothing in the cache. 608 t.EqualValues(getWorkflowCache().Size(), 0) 609 } 610 611 func (t *TaskHandlersTestSuite) TestWithMissingHistoryEvents() { 612 taskList := "taskList" 613 testEvents := []*s.HistoryEvent{ 614 createTestEventWorkflowExecutionStarted(1, &s.WorkflowExecutionStartedEventAttributes{TaskList: &s.TaskList{Name: &taskList}}), 615 createTestEventDecisionTaskScheduled(2, &s.DecisionTaskScheduledEventAttributes{TaskList: &s.TaskList{Name: &taskList}}), 616 createTestEventDecisionTaskStarted(3), 617 createTestEventDecisionTaskCompleted(4, &s.DecisionTaskCompletedEventAttributes{ScheduledEventId: common.Int64Ptr(2)}), 618 createTestEventDecisionTaskScheduled(6, &s.DecisionTaskScheduledEventAttributes{TaskList: &s.TaskList{Name: &taskList}}), 619 createTestEventDecisionTaskStarted(7), 620 } 621 params := workerExecutionParameters{ 622 TaskList: taskList, 623 WorkerOptions: WorkerOptions{ 624 Identity: "test-id-1", 625 Logger: zap.NewNop(), 626 NonDeterministicWorkflowPolicy: NonDeterministicWorkflowPolicyBlockWorkflow, 627 }, 628 } 629 630 for _, startEventID := range []int64{0, 3} { 631 taskHandler := newWorkflowTaskHandler(testDomain, params, nil, t.registry) 632 task := createWorkflowTask(testEvents, startEventID, "HelloWorld_Workflow") 633 // newWorkflowTaskWorkerInternal will set the laTunnel in taskHandler, without it, ProcessWorkflowTask() 634 // will fail as it can't find laTunnel in getWorkflowCache(). 635 newWorkflowTaskWorkerInternal(taskHandler, t.service, testDomain, params, make(chan struct{}), nil) 636 request, err := taskHandler.ProcessWorkflowTask(&workflowTask{task: task}, nil) 637 638 t.Error(err) 639 t.Nil(request) 640 t.Contains(err.Error(), "missing history events") 641 642 // There should be nothing in the cache. 643 t.EqualValues(getWorkflowCache().Size(), 0) 644 } 645 } 646 647 func (t *TaskHandlersTestSuite) TestWithTruncatedHistory() { 648 taskList := "taskList" 649 testEvents := []*s.HistoryEvent{ 650 createTestEventWorkflowExecutionStarted(1, &s.WorkflowExecutionStartedEventAttributes{TaskList: &s.TaskList{Name: &taskList}}), 651 createTestEventDecisionTaskScheduled(2, &s.DecisionTaskScheduledEventAttributes{TaskList: &s.TaskList{Name: &taskList}}), 652 createTestEventDecisionTaskStarted(3), 653 createTestEventDecisionTaskFailed(4, &s.DecisionTaskFailedEventAttributes{ScheduledEventId: common.Int64Ptr(2)}), 654 createTestEventDecisionTaskScheduled(5, &s.DecisionTaskScheduledEventAttributes{TaskList: &s.TaskList{Name: &taskList}}), 655 createTestEventDecisionTaskStarted(6), 656 createTestEventDecisionTaskCompleted(7, &s.DecisionTaskCompletedEventAttributes{ScheduledEventId: common.Int64Ptr(5)}), 657 createTestEventActivityTaskScheduled(8, &s.ActivityTaskScheduledEventAttributes{ 658 ActivityId: common.StringPtr("0"), 659 ActivityType: &s.ActivityType{Name: common.StringPtr("pkg.Greeter_Activity")}, 660 TaskList: &s.TaskList{Name: &taskList}, 661 }), 662 } 663 params := workerExecutionParameters{ 664 TaskList: taskList, 665 WorkerOptions: WorkerOptions{ 666 Identity: "test-id-1", 667 Logger: zap.NewNop(), 668 NonDeterministicWorkflowPolicy: NonDeterministicWorkflowPolicyBlockWorkflow, 669 }, 670 } 671 672 testCases := []struct { 673 startedEventID int64 674 previousStartedEventID int64 675 isResultErr bool 676 }{ 677 {0, 6, false}, 678 {10, 0, true}, 679 {10, 6, true}, 680 } 681 682 for i, tc := range testCases { 683 taskHandler := newWorkflowTaskHandler(testDomain, params, nil, t.registry) 684 task := createWorkflowTask(testEvents, tc.previousStartedEventID, "HelloWorld_Workflow") 685 task.StartedEventId = common.Int64Ptr(tc.startedEventID) 686 // newWorkflowTaskWorkerInternal will set the laTunnel in taskHandler, without it, ProcessWorkflowTask() 687 // will fail as it can't find laTunnel in getWorkflowCache(). 688 newWorkflowTaskWorkerInternal(taskHandler, t.service, testDomain, params, make(chan struct{}), nil) 689 request, err := taskHandler.ProcessWorkflowTask(&workflowTask{task: task}, nil) 690 691 if tc.isResultErr { 692 t.Error(err, "testcase %v failed", i) 693 t.Nil(request) 694 t.Contains(err.Error(), "premature end of stream") 695 t.EqualValues(getWorkflowCache().Size(), 1) 696 continue 697 } 698 699 t.NoError(err, "testcase %v failed", i) 700 } 701 } 702 703 func (t *TaskHandlersTestSuite) TestSideEffectDefer_Sticky() { 704 t.testSideEffectDeferHelper(false) 705 } 706 707 func (t *TaskHandlersTestSuite) TestSideEffectDefer_NonSticky() { 708 t.testSideEffectDeferHelper(true) 709 } 710 711 func (t *TaskHandlersTestSuite) testSideEffectDeferHelper(disableSticky bool) { 712 value := "should not be modified" 713 expectedValue := value 714 doneCh := make(chan struct{}) 715 716 workflowFunc := func(ctx Context) error { 717 defer func() { 718 if !IsReplaying(ctx) { 719 // This is an side effect op 720 value = "" 721 } 722 close(doneCh) 723 }() 724 Sleep(ctx, 1*time.Second) 725 return nil 726 } 727 workflowName := fmt.Sprintf("SideEffectDeferWorkflow-Sticky=%v", disableSticky) 728 t.registry.RegisterWorkflowWithOptions( 729 workflowFunc, 730 RegisterWorkflowOptions{Name: workflowName}, 731 ) 732 733 taskList := "taskList" 734 testEvents := []*s.HistoryEvent{ 735 createTestEventWorkflowExecutionStarted(1, &s.WorkflowExecutionStartedEventAttributes{TaskList: &s.TaskList{Name: &taskList}}), 736 createTestEventDecisionTaskScheduled(2, &s.DecisionTaskScheduledEventAttributes{TaskList: &s.TaskList{Name: &taskList}}), 737 createTestEventDecisionTaskStarted(3), 738 } 739 740 params := workerExecutionParameters{ 741 TaskList: taskList, 742 WorkerOptions: WorkerOptions{ 743 Identity: "test-id-1", 744 Logger: zap.NewNop(), 745 DisableStickyExecution: disableSticky, 746 }, 747 } 748 749 taskHandler := newWorkflowTaskHandler(testDomain, params, nil, t.registry) 750 task := createWorkflowTask(testEvents, 0, workflowName) 751 _, err := taskHandler.ProcessWorkflowTask(&workflowTask{task: task}, nil) 752 t.Nil(err) 753 754 if !params.DisableStickyExecution { 755 // 1. We can't set cache size in the test to 1, otherwise other tests will break. 756 // 2. We need to make sure cache is empty when the test is completed, 757 // So manually trigger a delete. 758 getWorkflowCache().Delete(task.WorkflowExecution.GetRunId()) 759 } 760 // Make sure the workflow coroutine has exited. 761 <-doneCh 762 // The side effect op should not be executed. 763 t.Equal(expectedValue, value) 764 765 // There should be nothing in the cache. 766 t.EqualValues(0, getWorkflowCache().Size()) 767 } 768 769 func (t *TaskHandlersTestSuite) TestWorkflowTask_NondeterministicDetection() { 770 taskList := "taskList" 771 testEvents := []*s.HistoryEvent{ 772 createTestEventWorkflowExecutionStarted(1, &s.WorkflowExecutionStartedEventAttributes{TaskList: &s.TaskList{Name: &taskList}}), 773 createTestEventDecisionTaskScheduled(2, &s.DecisionTaskScheduledEventAttributes{TaskList: &s.TaskList{Name: &taskList}}), 774 createTestEventDecisionTaskStarted(3), 775 createTestEventDecisionTaskCompleted(4, &s.DecisionTaskCompletedEventAttributes{ScheduledEventId: common.Int64Ptr(2)}), 776 createTestEventActivityTaskScheduled(5, &s.ActivityTaskScheduledEventAttributes{ 777 ActivityId: common.StringPtr("0"), 778 ActivityType: &s.ActivityType{Name: common.StringPtr("pkg.Greeter_Activity")}, 779 TaskList: &s.TaskList{Name: &taskList}, 780 }), 781 } 782 task := createWorkflowTask(testEvents, 3, "HelloWorld_Workflow") 783 stopC := make(chan struct{}) 784 params := workerExecutionParameters{ 785 TaskList: taskList, 786 WorkerOptions: WorkerOptions{ 787 Identity: "test-id-1", 788 Logger: zap.NewNop(), 789 NonDeterministicWorkflowPolicy: NonDeterministicWorkflowPolicyBlockWorkflow, 790 }, 791 WorkerStopChannel: stopC, 792 } 793 794 taskHandler := newWorkflowTaskHandler(testDomain, params, nil, t.registry) 795 request, err := taskHandler.ProcessWorkflowTask(&workflowTask{task: task}, nil) 796 response := request.(*s.RespondDecisionTaskCompletedRequest) 797 // there should be no error as the history events matched the decisions. 798 t.NoError(err) 799 t.NotNil(response) 800 801 // now change the history event so it does not match to decision produced via replay 802 testEvents[4].ActivityTaskScheduledEventAttributes.ActivityType.Name = common.StringPtr("some-other-activity") 803 task = createWorkflowTask(testEvents, 3, "HelloWorld_Workflow") 804 // newWorkflowTaskWorkerInternal will set the laTunnel in taskHandler, without it, ProcessWorkflowTask() 805 // will fail as it can't find laTunnel in getWorkflowCache(). 806 newWorkflowTaskWorkerInternal(taskHandler, t.service, testDomain, params, stopC, nil) 807 request, err = taskHandler.ProcessWorkflowTask(&workflowTask{task: task}, nil) 808 t.Error(err) 809 t.Nil(request) 810 t.Contains(err.Error(), "nondeterministic") 811 812 // now, create a new task handler with fail nondeterministic workflow policy 813 // and verify that it handles the mismatching history correctly. 814 params.NonDeterministicWorkflowPolicy = NonDeterministicWorkflowPolicyFailWorkflow 815 failOnNondeterminismTaskHandler := newWorkflowTaskHandler(testDomain, params, nil, t.registry) 816 task = createWorkflowTask(testEvents, 3, "HelloWorld_Workflow") 817 request, err = failOnNondeterminismTaskHandler.ProcessWorkflowTask(&workflowTask{task: task}, nil) 818 // When FailWorkflow policy is set, task handler does not return an error, 819 // because it will indicate non determinism in the request. 820 t.NoError(err) 821 // Verify that request is a RespondDecisionTaskCompleteRequest 822 response, ok := request.(*s.RespondDecisionTaskCompletedRequest) 823 t.True(ok) 824 // Verify there's at least 1 decision 825 // and the last last decision is to fail workflow 826 // and contains proper justification.(i.e. nondeterminism). 827 t.True(len(response.Decisions) > 0) 828 closeDecision := response.Decisions[len(response.Decisions)-1] 829 t.Equal(*closeDecision.DecisionType, s.DecisionTypeFailWorkflowExecution) 830 t.Contains(*closeDecision.FailWorkflowExecutionDecisionAttributes.Reason, "NonDeterministicWorkflowPolicyFailWorkflow") 831 832 // now with different package name to activity type 833 testEvents[4].ActivityTaskScheduledEventAttributes.ActivityType.Name = common.StringPtr("new-package.Greeter_Activity") 834 task = createWorkflowTask(testEvents, 3, "HelloWorld_Workflow") 835 request, err = taskHandler.ProcessWorkflowTask(&workflowTask{task: task}, nil) 836 t.NoError(err) 837 t.NotNil(request) 838 } 839 840 func (t *TaskHandlersTestSuite) TestWorkflowTask_NondeterministicLogNonexistingID() { 841 taskList := "taskList" 842 testEvents := []*s.HistoryEvent{ 843 createTestEventWorkflowExecutionStarted(1, &s.WorkflowExecutionStartedEventAttributes{TaskList: &s.TaskList{Name: &taskList}}), 844 createTestEventDecisionTaskScheduled(2, &s.DecisionTaskScheduledEventAttributes{TaskList: &s.TaskList{Name: &taskList}}), 845 createTestEventDecisionTaskStarted(3), 846 createTestEventDecisionTaskCompleted(4, &s.DecisionTaskCompletedEventAttributes{ScheduledEventId: common.Int64Ptr(2)}), 847 createTestEventActivityTaskScheduled(5, &s.ActivityTaskScheduledEventAttributes{ 848 // Insert an ID which does not exist 849 ActivityId: common.StringPtr("NotAnActivityID"), 850 ActivityType: &s.ActivityType{Name: common.StringPtr("pkg.Greeter_Activity")}, 851 TaskList: &s.TaskList{Name: &taskList}, 852 }), 853 } 854 855 obs, logs := observer.New(zap.ErrorLevel) 856 logger := zap.New(obs) 857 858 task := createWorkflowTask(testEvents, 3, "HelloWorld_Workflow") 859 stopC := make(chan struct{}) 860 params := workerExecutionParameters{ 861 TaskList: taskList, 862 WorkerOptions: WorkerOptions{ 863 Identity: "test-id-1", 864 Logger: logger, 865 }, 866 WorkerStopChannel: stopC, 867 } 868 869 taskHandler := newWorkflowTaskHandler(testDomain, params, nil, t.registry) 870 request, err := taskHandler.ProcessWorkflowTask(&workflowTask{task: task}, nil) 871 872 t.Nil(request) 873 t.ErrorContains(err, "nondeterministic workflow") 874 875 // Check that the error was logged 876 illegalPanicLogs := logs.FilterMessage("Illegal state caused panic") 877 require.Len(t.T(), illegalPanicLogs.All(), 1) 878 879 replayErrorField := findLogField(illegalPanicLogs.All()[0], "ReplayError") 880 require.NotNil(t.T(), replayErrorField) 881 require.Equal(t.T(), zapcore.ErrorType, replayErrorField.Type) 882 require.ErrorContains(t.T(), replayErrorField.Interface.(error), 883 "nondeterministic workflow: "+ 884 "history event is ActivityTaskScheduled: (ActivityId:NotAnActivityID, ActivityType:(Name:pkg.Greeter_Activity), TaskList:(Name:taskList), Input:[]), "+ 885 "replay decision is ScheduleActivityTask: (ActivityId:0, ActivityType:(Name:Greeter_Activity), TaskList:(Name:taskList)") 886 } 887 888 func (t *TaskHandlersTestSuite) TestWorkflowTask_WorkflowReturnsPanicError() { 889 taskList := "taskList" 890 testEvents := []*s.HistoryEvent{ 891 createTestEventWorkflowExecutionStarted(1, &s.WorkflowExecutionStartedEventAttributes{TaskList: &s.TaskList{Name: &taskList}}), 892 createTestEventDecisionTaskScheduled(2, &s.DecisionTaskScheduledEventAttributes{TaskList: &s.TaskList{Name: &taskList}}), 893 createTestEventDecisionTaskStarted(3), 894 } 895 task := createWorkflowTask(testEvents, 3, "ReturnPanicWorkflow") 896 params := workerExecutionParameters{ 897 TaskList: taskList, 898 WorkerOptions: WorkerOptions{ 899 Identity: "test-id-1", 900 Logger: zap.NewNop(), 901 NonDeterministicWorkflowPolicy: NonDeterministicWorkflowPolicyBlockWorkflow, 902 }, 903 } 904 905 taskHandler := newWorkflowTaskHandler(testDomain, params, nil, t.registry) 906 request, err := taskHandler.ProcessWorkflowTask(&workflowTask{task: task}, nil) 907 t.NoError(err) 908 t.NotNil(request) 909 r, ok := request.(*s.RespondDecisionTaskCompletedRequest) 910 t.True(ok) 911 t.EqualValues(s.DecisionTypeFailWorkflowExecution, r.Decisions[0].GetDecisionType()) 912 attr := r.Decisions[0].FailWorkflowExecutionDecisionAttributes 913 t.EqualValues("cadenceInternal:Panic", attr.GetReason()) 914 details := string(attr.Details) 915 t.True(strings.HasPrefix(details, "\"panicError"), details) 916 } 917 918 func (t *TaskHandlersTestSuite) TestWorkflowTask_WorkflowPanics() { 919 taskList := "taskList" 920 testEvents := []*s.HistoryEvent{ 921 createTestEventWorkflowExecutionStarted(1, &s.WorkflowExecutionStartedEventAttributes{TaskList: &s.TaskList{Name: &taskList}}), 922 createTestEventDecisionTaskScheduled(2, &s.DecisionTaskScheduledEventAttributes{TaskList: &s.TaskList{Name: &taskList}}), 923 createTestEventDecisionTaskStarted(3), 924 } 925 926 obs, logs := observer.New(zap.ErrorLevel) 927 logger := zap.New(obs) 928 929 task := createWorkflowTask(testEvents, 3, "PanicWorkflow") 930 params := workerExecutionParameters{ 931 TaskList: taskList, 932 WorkerOptions: WorkerOptions{ 933 Identity: "test-id-1", 934 Logger: logger, 935 NonDeterministicWorkflowPolicy: NonDeterministicWorkflowPolicyBlockWorkflow, 936 }, 937 } 938 939 taskHandler := newWorkflowTaskHandler(testDomain, params, nil, t.registry) 940 request, err := taskHandler.ProcessWorkflowTask(&workflowTask{task: task}, nil) 941 t.NoError(err) 942 t.NotNil(request) 943 r, ok := request.(*s.RespondDecisionTaskFailedRequest) 944 t.True(ok) 945 t.EqualValues("WORKFLOW_WORKER_UNHANDLED_FAILURE", r.Cause.String()) 946 t.EqualValues("panicError", string(r.Details)) 947 948 // Check that the error was logged 949 panicLogs := logs.FilterMessage("Workflow panic.") 950 require.Len(t.T(), panicLogs.All(), 1) 951 952 wfTypeField := findLogField(panicLogs.All()[0], tagWorkflowType) 953 require.NotNil(t.T(), wfTypeField) 954 require.Equal(t.T(), "PanicWorkflow", wfTypeField.String) 955 } 956 957 func (t *TaskHandlersTestSuite) TestGetWorkflowInfo() { 958 taskList := "taskList" 959 parentID := "parentID" 960 parentRunID := "parentRun" 961 cronSchedule := "5 4 * * *" 962 continuedRunID := uuid.New() 963 parentExecution := &s.WorkflowExecution{ 964 WorkflowId: &parentID, 965 RunId: &parentRunID, 966 } 967 parentDomain := "parentDomain" 968 var attempt int32 = 123 969 var executionTimeout int32 = 213456 970 var taskTimeout int32 = 21 971 workflowType := "GetWorkflowInfoWorkflow" 972 lastCompletionResult, err := getDefaultDataConverter().ToData("lastCompletionData") 973 t.NoError(err) 974 retryPolicy := &s.RetryPolicy{ 975 InitialIntervalInSeconds: common.Int32Ptr(1), 976 BackoffCoefficient: common.Float64Ptr(1.0), 977 MaximumIntervalInSeconds: common.Int32Ptr(1), 978 MaximumAttempts: common.Int32Ptr(3), 979 } 980 startedEventAttributes := &s.WorkflowExecutionStartedEventAttributes{ 981 Input: lastCompletionResult, 982 TaskList: &s.TaskList{Name: &taskList}, 983 ParentWorkflowExecution: parentExecution, 984 CronSchedule: &cronSchedule, 985 ContinuedExecutionRunId: &continuedRunID, 986 ParentWorkflowDomain: &parentDomain, 987 Attempt: &attempt, 988 ExecutionStartToCloseTimeoutSeconds: &executionTimeout, 989 TaskStartToCloseTimeoutSeconds: &taskTimeout, 990 LastCompletionResult: lastCompletionResult, 991 RetryPolicy: retryPolicy, 992 } 993 testEvents := []*s.HistoryEvent{ 994 createTestEventWorkflowExecutionStarted(1, startedEventAttributes), 995 createTestEventDecisionTaskScheduled(2, &s.DecisionTaskScheduledEventAttributes{TaskList: &s.TaskList{Name: &taskList}}), 996 createTestEventDecisionTaskStarted(3), 997 } 998 task := createWorkflowTask(testEvents, 3, workflowType) 999 params := workerExecutionParameters{ 1000 TaskList: taskList, 1001 WorkerOptions: WorkerOptions{ 1002 Identity: "test-id-1", 1003 Logger: zap.NewNop(), 1004 NonDeterministicWorkflowPolicy: NonDeterministicWorkflowPolicyBlockWorkflow, 1005 }, 1006 } 1007 1008 taskHandler := newWorkflowTaskHandler(testDomain, params, nil, t.registry) 1009 request, err := taskHandler.ProcessWorkflowTask(&workflowTask{task: task}, nil) 1010 t.NoError(err) 1011 t.NotNil(request) 1012 r, ok := request.(*s.RespondDecisionTaskCompletedRequest) 1013 t.True(ok) 1014 t.EqualValues(s.DecisionTypeCompleteWorkflowExecution, r.Decisions[0].GetDecisionType()) 1015 attr := r.Decisions[0].CompleteWorkflowExecutionDecisionAttributes 1016 var result WorkflowInfo 1017 t.NoError(getDefaultDataConverter().FromData(attr.Result, &result)) 1018 t.EqualValues(taskList, result.TaskListName) 1019 t.EqualValues(parentID, result.ParentWorkflowExecution.ID) 1020 t.EqualValues(parentRunID, result.ParentWorkflowExecution.RunID) 1021 t.EqualValues(cronSchedule, *result.CronSchedule) 1022 t.EqualValues(continuedRunID, *result.ContinuedExecutionRunID) 1023 t.EqualValues(parentDomain, *result.ParentWorkflowDomain) 1024 t.EqualValues(attempt, result.Attempt) 1025 t.EqualValues(executionTimeout, result.ExecutionStartToCloseTimeoutSeconds) 1026 t.EqualValues(taskTimeout, result.TaskStartToCloseTimeoutSeconds) 1027 t.EqualValues(workflowType, result.WorkflowType.Name) 1028 t.EqualValues(testDomain, result.Domain) 1029 t.EqualValues(retryPolicy, result.RetryPolicy) 1030 } 1031 1032 func (t *TaskHandlersTestSuite) TestConsistentQuery_InvalidQueryTask() { 1033 taskList := "taskList" 1034 params := workerExecutionParameters{ 1035 TaskList: taskList, 1036 WorkerOptions: WorkerOptions{ 1037 Identity: "test-id-1", 1038 Logger: zap.NewNop(), 1039 NonDeterministicWorkflowPolicy: NonDeterministicWorkflowPolicyBlockWorkflow, 1040 }, 1041 } 1042 1043 taskHandler := newWorkflowTaskHandler(testDomain, params, nil, t.registry) 1044 task := createWorkflowTask(nil, 3, "HelloWorld_Workflow") 1045 task.Query = &s.WorkflowQuery{} 1046 task.Queries = map[string]*s.WorkflowQuery{"query_id": {}} 1047 newWorkflowTaskWorkerInternal(taskHandler, t.service, testDomain, params, make(chan struct{}), nil) 1048 // query and queries are both specified so this is an invalid task 1049 request, err := taskHandler.ProcessWorkflowTask(&workflowTask{task: task}, nil) 1050 1051 t.Error(err) 1052 t.Nil(request) 1053 t.Contains(err.Error(), "invalid query decision task") 1054 1055 // There should be nothing in the cache. 1056 t.EqualValues(getWorkflowCache().Size(), 0) 1057 } 1058 1059 func (t *TaskHandlersTestSuite) TestConsistentQuery_Success() { 1060 taskList := "tl1" 1061 checksum1 := "chck1" 1062 numberOfSignalsToComplete, err := getDefaultDataConverter().ToData(2) 1063 t.NoError(err) 1064 signal, err := getDefaultDataConverter().ToData("signal data") 1065 t.NoError(err) 1066 testEvents := []*s.HistoryEvent{ 1067 createTestEventWorkflowExecutionStarted(1, &s.WorkflowExecutionStartedEventAttributes{ 1068 TaskList: &s.TaskList{Name: &taskList}, 1069 Input: numberOfSignalsToComplete, 1070 }), 1071 createTestEventDecisionTaskScheduled(2, &s.DecisionTaskScheduledEventAttributes{}), 1072 createTestEventDecisionTaskStarted(3), 1073 createTestEventDecisionTaskCompleted(4, &s.DecisionTaskCompletedEventAttributes{ 1074 ScheduledEventId: common.Int64Ptr(2), BinaryChecksum: common.StringPtr(checksum1)}), 1075 createTestEventWorkflowExecutionSignaledWithPayload(5, signalCh, signal), 1076 createTestEventDecisionTaskScheduled(6, &s.DecisionTaskScheduledEventAttributes{}), 1077 createTestEventDecisionTaskStarted(7), 1078 } 1079 1080 queries := map[string]*s.WorkflowQuery{ 1081 "id1": {QueryType: common.StringPtr(queryType)}, 1082 "id2": {QueryType: common.StringPtr(errQueryType)}, 1083 } 1084 task := createWorkflowTaskWithQueries(testEvents[0:3], 0, "QuerySignalWorkflow", queries) 1085 1086 params := workerExecutionParameters{ 1087 TaskList: taskList, 1088 WorkerOptions: WorkerOptions{ 1089 Identity: "test-id-1", 1090 Logger: t.logger, 1091 }, 1092 } 1093 1094 taskHandler := newWorkflowTaskHandler(testDomain, params, nil, t.registry) 1095 request, err := taskHandler.ProcessWorkflowTask(&workflowTask{task: task}, nil) 1096 response := request.(*s.RespondDecisionTaskCompletedRequest) 1097 t.NoError(err) 1098 t.NotNil(response) 1099 t.Len(response.Decisions, 0) 1100 expectedQueryResults := map[string]*s.WorkflowQueryResult{ 1101 "id1": { 1102 ResultType: common.QueryResultTypePtr(s.QueryResultTypeAnswered), 1103 Answer: []byte(fmt.Sprintf("\"%v\"\n", startingQueryValue)), 1104 }, 1105 "id2": { 1106 ResultType: common.QueryResultTypePtr(s.QueryResultTypeFailed), 1107 ErrorMessage: common.StringPtr(queryErr), 1108 }, 1109 } 1110 t.assertQueryResultsEqual(expectedQueryResults, response.QueryResults) 1111 1112 secondTask := createWorkflowTaskWithQueries(testEvents, 3, "QuerySignalWorkflow", queries) 1113 secondTask.WorkflowExecution.RunId = task.WorkflowExecution.RunId 1114 request, err = taskHandler.ProcessWorkflowTask(&workflowTask{task: secondTask}, nil) 1115 response = request.(*s.RespondDecisionTaskCompletedRequest) 1116 t.NoError(err) 1117 t.NotNil(response) 1118 t.Len(response.Decisions, 1) 1119 expectedQueryResults = map[string]*s.WorkflowQueryResult{ 1120 "id1": { 1121 ResultType: common.QueryResultTypePtr(s.QueryResultTypeAnswered), 1122 Answer: []byte(fmt.Sprintf("\"%v\"\n", "signal data")), 1123 }, 1124 "id2": { 1125 ResultType: common.QueryResultTypePtr(s.QueryResultTypeFailed), 1126 ErrorMessage: common.StringPtr(queryErr), 1127 }, 1128 } 1129 t.assertQueryResultsEqual(expectedQueryResults, response.QueryResults) 1130 1131 // clean up workflow left in cache 1132 getWorkflowCache().Delete(*task.WorkflowExecution.RunId) 1133 } 1134 1135 func (t *TaskHandlersTestSuite) assertQueryResultsEqual(expected map[string]*s.WorkflowQueryResult, actual map[string]*s.WorkflowQueryResult) { 1136 t.Equal(len(expected), len(actual)) 1137 for expectedID, expectedResult := range expected { 1138 t.Contains(actual, expectedID) 1139 t.True(expectedResult.Equals(actual[expectedID])) 1140 } 1141 } 1142 1143 func (t *TaskHandlersTestSuite) TestWorkflowTask_CancelActivityBeforeSent() { 1144 // Schedule an activity and see if we complete workflow. 1145 taskList := "tl1" 1146 testEvents := []*s.HistoryEvent{ 1147 createTestEventWorkflowExecutionStarted(1, &s.WorkflowExecutionStartedEventAttributes{TaskList: &s.TaskList{Name: &taskList}}), 1148 createTestEventDecisionTaskScheduled(2, &s.DecisionTaskScheduledEventAttributes{}), 1149 createTestEventDecisionTaskStarted(3), 1150 } 1151 task := createWorkflowTask(testEvents, 0, "HelloWorld_WorkflowCancel") 1152 1153 params := workerExecutionParameters{ 1154 TaskList: taskList, 1155 WorkerOptions: WorkerOptions{ 1156 Identity: "test-id-1", 1157 Logger: t.logger, 1158 }, 1159 } 1160 taskHandler := newWorkflowTaskHandler(testDomain, params, nil, t.registry) 1161 request, err := taskHandler.ProcessWorkflowTask(&workflowTask{task: task}, nil) 1162 response := request.(*s.RespondDecisionTaskCompletedRequest) 1163 t.NoError(err) 1164 t.NotNil(response) 1165 t.Equal(1, len(response.Decisions)) 1166 t.Equal(s.DecisionTypeCompleteWorkflowExecution, response.Decisions[0].GetDecisionType()) 1167 t.NotNil(response.Decisions[0].CompleteWorkflowExecutionDecisionAttributes) 1168 } 1169 1170 func (t *TaskHandlersTestSuite) TestWorkflowTask_PageToken() { 1171 // Schedule a decision activity and see if we complete workflow. 1172 taskList := "tl1" 1173 testEvents := []*s.HistoryEvent{ 1174 createTestEventWorkflowExecutionStarted(1, &s.WorkflowExecutionStartedEventAttributes{TaskList: &s.TaskList{Name: &taskList}}), 1175 createTestEventDecisionTaskScheduled(2, &s.DecisionTaskScheduledEventAttributes{}), 1176 } 1177 task := createWorkflowTask(testEvents, 0, "HelloWorld_Workflow") 1178 task.NextPageToken = []byte("token") 1179 1180 params := workerExecutionParameters{ 1181 TaskList: taskList, 1182 WorkerOptions: WorkerOptions{ 1183 Identity: "test-id-1", 1184 Logger: t.logger, 1185 }, 1186 } 1187 1188 nextEvents := []*s.HistoryEvent{ 1189 createTestEventDecisionTaskStarted(3), 1190 } 1191 1192 historyIterator := &historyIteratorImpl{ 1193 iteratorFunc: func(nextToken []byte) (*s.History, []byte, error) { 1194 return &s.History{nextEvents}, nil, nil 1195 }, 1196 } 1197 taskHandler := newWorkflowTaskHandler(testDomain, params, nil, t.registry) 1198 request, err := taskHandler.ProcessWorkflowTask(&workflowTask{task: task, historyIterator: historyIterator}, nil) 1199 response := request.(*s.RespondDecisionTaskCompletedRequest) 1200 t.NoError(err) 1201 t.NotNil(response) 1202 } 1203 1204 func (t *TaskHandlersTestSuite) TestLocalActivityRetry_DecisionHeartbeatFail() { 1205 backoffIntervalInSeconds := int32(1) 1206 backoffDuration := time.Second * time.Duration(backoffIntervalInSeconds) 1207 workflowComplete := false 1208 1209 retryLocalActivityWorkflowFunc := func(ctx Context, intput []byte) error { 1210 ao := LocalActivityOptions{ 1211 ScheduleToCloseTimeout: time.Minute, 1212 RetryPolicy: &RetryPolicy{ 1213 InitialInterval: backoffDuration, 1214 BackoffCoefficient: 1.1, 1215 MaximumInterval: time.Minute, 1216 ExpirationInterval: time.Minute, 1217 }, 1218 } 1219 ctx = WithLocalActivityOptions(ctx, ao) 1220 1221 err := ExecuteLocalActivity(ctx, func() error { 1222 return errors.New("some random error") 1223 }).Get(ctx, nil) 1224 workflowComplete = true 1225 return err 1226 } 1227 t.registry.RegisterWorkflowWithOptions( 1228 retryLocalActivityWorkflowFunc, 1229 RegisterWorkflowOptions{Name: "RetryLocalActivityWorkflow"}, 1230 ) 1231 1232 decisionTaskStartedEvent := createTestEventDecisionTaskStarted(3) 1233 decisionTaskStartedEvent.Timestamp = common.Int64Ptr(time.Now().UnixNano()) 1234 testEvents := []*s.HistoryEvent{ 1235 createTestEventWorkflowExecutionStarted(1, &s.WorkflowExecutionStartedEventAttributes{ 1236 // make sure the timeout is same as the backoff interval 1237 TaskStartToCloseTimeoutSeconds: common.Int32Ptr(backoffIntervalInSeconds), 1238 TaskList: &s.TaskList{Name: &testWorkflowTaskTasklist}}, 1239 ), 1240 createTestEventDecisionTaskScheduled(2, &s.DecisionTaskScheduledEventAttributes{}), 1241 decisionTaskStartedEvent, 1242 } 1243 1244 task := createWorkflowTask(testEvents, 0, "RetryLocalActivityWorkflow") 1245 stopCh := make(chan struct{}) 1246 params := workerExecutionParameters{ 1247 TaskList: testWorkflowTaskTasklist, 1248 WorkerOptions: WorkerOptions{ 1249 Identity: "test-id-1", 1250 Logger: t.logger, 1251 Tracer: opentracing.NoopTracer{}, 1252 }, 1253 WorkerStopChannel: stopCh, 1254 } 1255 defer close(stopCh) 1256 1257 taskHandler := newWorkflowTaskHandler(testDomain, params, nil, t.registry) 1258 laTunnel := newLocalActivityTunnel(params.WorkerStopChannel) 1259 taskHandlerImpl, ok := taskHandler.(*workflowTaskHandlerImpl) 1260 t.True(ok) 1261 taskHandlerImpl.laTunnel = laTunnel 1262 1263 laTaskPoller := newLocalActivityPoller(params, laTunnel) 1264 doneCh := make(chan struct{}) 1265 go func() { 1266 // laTaskPoller needs to poll the local activity and process it 1267 task, err := laTaskPoller.PollTask() 1268 t.NoError(err) 1269 err = laTaskPoller.ProcessTask(task) 1270 t.NoError(err) 1271 1272 // before clearing workflow state, a reset sticky task will be sent to this chan, 1273 // drain the chan so that workflow state can be cleared 1274 <-laTunnel.resultCh 1275 1276 close(doneCh) 1277 }() 1278 1279 laResultCh := make(chan *localActivityResult) 1280 response, err := taskHandler.ProcessWorkflowTask( 1281 &workflowTask{ 1282 task: task, 1283 laResultCh: laResultCh, 1284 }, 1285 func(response interface{}, startTime time.Time) (*workflowTask, error) { 1286 return nil, &s.EntityNotExistsError{Message: "Decision task not found."} 1287 }) 1288 t.Nil(response) 1289 t.Error(err) 1290 1291 // wait for the retry timer to fire 1292 time.Sleep(backoffDuration) 1293 t.False(workflowComplete) 1294 <-doneCh 1295 } 1296 1297 func (t *TaskHandlersTestSuite) TestHeartBeat_NoError() { 1298 mockCtrl := gomock.NewController(t.T()) 1299 mockService := workflowservicetest.NewMockClient(mockCtrl) 1300 1301 cancelRequested := false 1302 heartbeatResponse := s.RecordActivityTaskHeartbeatResponse{CancelRequested: &cancelRequested} 1303 mockService.EXPECT().RecordActivityTaskHeartbeat(gomock.Any(), gomock.Any(), callOptions()...).Return(&heartbeatResponse, nil) 1304 1305 cadenceInvoker := &cadenceInvoker{ 1306 identity: "Test_Cadence_Invoker", 1307 service: mockService, 1308 taskToken: nil, 1309 } 1310 1311 heartbeatErr := cadenceInvoker.BatchHeartbeat(nil) 1312 t.NoError(heartbeatErr) 1313 } 1314 1315 func newHeartbeatRequestMatcher(details []byte) gomock.Matcher { 1316 return &recordHeartbeatRequestMatcher{details: details} 1317 } 1318 1319 type recordHeartbeatRequestMatcher struct { 1320 details []byte 1321 } 1322 1323 func (r *recordHeartbeatRequestMatcher) String() string { 1324 panic("implement me") 1325 } 1326 1327 func (r *recordHeartbeatRequestMatcher) Matches(x interface{}) bool { 1328 req, ok := x.(*s.RecordActivityTaskHeartbeatRequest) 1329 if !ok { 1330 return false 1331 } 1332 1333 return reflect.DeepEqual(r.details, req.Details) 1334 } 1335 1336 func (t *TaskHandlersTestSuite) TestHeartBeat_Interleaved() { 1337 mockCtrl := gomock.NewController(t.T()) 1338 mockService := workflowservicetest.NewMockClient(mockCtrl) 1339 1340 cancelRequested := false 1341 heartbeatResponse := s.RecordActivityTaskHeartbeatResponse{CancelRequested: &cancelRequested} 1342 mockService.EXPECT().RecordActivityTaskHeartbeat(gomock.Any(), newHeartbeatRequestMatcher([]byte("1")), callOptions()...).Return(&heartbeatResponse, nil).Times(3) 1343 mockService.EXPECT().RecordActivityTaskHeartbeat(gomock.Any(), newHeartbeatRequestMatcher([]byte("2")), callOptions()...).Return(&heartbeatResponse, nil).Times(3) 1344 1345 cadenceInvoker := &cadenceInvoker{ 1346 identity: "Test_Cadence_Invoker", 1347 service: mockService, 1348 taskToken: nil, 1349 heartBeatTimeoutInSec: 3, 1350 } 1351 1352 heartbeatErr := cadenceInvoker.BatchHeartbeat([]byte("1")) 1353 t.NoError(heartbeatErr) 1354 time.Sleep(1000 * time.Millisecond) 1355 1356 for i := 0; i < 4; i++ { 1357 heartbeatErr = cadenceInvoker.BackgroundHeartbeat() 1358 t.NoError(heartbeatErr) 1359 time.Sleep(800 * time.Millisecond) 1360 } 1361 1362 time.Sleep(time.Second) 1363 1364 heartbeatErr = cadenceInvoker.BatchHeartbeat([]byte("2")) 1365 t.NoError(heartbeatErr) 1366 time.Sleep(1000 * time.Millisecond) 1367 1368 for i := 0; i < 4; i++ { 1369 heartbeatErr = cadenceInvoker.BackgroundHeartbeat() 1370 t.NoError(heartbeatErr) 1371 time.Sleep(800 * time.Millisecond) 1372 } 1373 time.Sleep(1 * time.Second) 1374 } 1375 1376 func (t *TaskHandlersTestSuite) TestHeartBeatLogNil() { 1377 core, obs := observer.New(zap.ErrorLevel) 1378 logger := zap.New(core) 1379 1380 cadenceInv := &cadenceInvoker{ 1381 identity: "Test_Cadence_Invoker", 1382 logger: logger, 1383 } 1384 1385 cadenceInv.logFailedHeartBeat(nil) 1386 1387 t.Empty(obs.All()) 1388 } 1389 1390 func (t *TaskHandlersTestSuite) TestHeartBeatLogCanceledError() { 1391 core, obs := observer.New(zap.ErrorLevel) 1392 logger := zap.New(core) 1393 1394 cadenceInv := &cadenceInvoker{ 1395 identity: "Test_Cadence_Invoker", 1396 logger: logger, 1397 } 1398 1399 var workflowCompleatedErr CanceledError 1400 cadenceInv.logFailedHeartBeat(&workflowCompleatedErr) 1401 1402 t.Empty(obs.All()) 1403 } 1404 1405 func (t *TaskHandlersTestSuite) TestHeartBeatLogNotCanceled() { 1406 core, obs := observer.New(zap.ErrorLevel) 1407 logger := zap.New(core) 1408 1409 cadenceInv := &cadenceInvoker{ 1410 identity: "Test_Cadence_Invoker", 1411 logger: logger, 1412 } 1413 1414 var workflowCompleatedErr s.WorkflowExecutionAlreadyCompletedError 1415 cadenceInv.logFailedHeartBeat(&workflowCompleatedErr) 1416 1417 t.Len(obs.All(), 1) 1418 t.Equal("Failed to send heartbeat", obs.All()[0].Message) 1419 } 1420 1421 func (t *TaskHandlersTestSuite) TestHeartBeat_NilResponseWithError() { 1422 mockCtrl := gomock.NewController(t.T()) 1423 mockService := workflowservicetest.NewMockClient(mockCtrl) 1424 logger := zaptest.NewLogger(t.T()) 1425 1426 entityNotExistsError := &s.EntityNotExistsError{} 1427 mockService.EXPECT().RecordActivityTaskHeartbeat(gomock.Any(), gomock.Any(), callOptions()...).Return(nil, entityNotExistsError) 1428 1429 cadenceInvoker := newServiceInvoker( 1430 nil, 1431 "Test_Cadence_Invoker", 1432 mockService, 1433 func() {}, 1434 0, 1435 make(chan struct{}), 1436 FeatureFlags{}, 1437 logger, 1438 testWorkflowType, 1439 testActivityType, 1440 ) 1441 1442 heartbeatErr := cadenceInvoker.BatchHeartbeat(nil) 1443 t.NotNil(heartbeatErr) 1444 _, ok := (heartbeatErr).(*s.EntityNotExistsError) 1445 t.True(ok, "heartbeatErr must be EntityNotExistsError.") 1446 } 1447 1448 func (t *TaskHandlersTestSuite) TestHeartBeat_NilResponseWithDomainNotActiveError() { 1449 mockCtrl := gomock.NewController(t.T()) 1450 mockService := workflowservicetest.NewMockClient(mockCtrl) 1451 logger := zaptest.NewLogger(t.T()) 1452 1453 domainNotActiveError := &s.DomainNotActiveError{} 1454 mockService.EXPECT().RecordActivityTaskHeartbeat(gomock.Any(), gomock.Any(), callOptions()...).Return(nil, domainNotActiveError) 1455 1456 called := false 1457 cancelHandler := func() { called = true } 1458 1459 cadenceInvoker := newServiceInvoker( 1460 nil, 1461 "Test_Cadence_Invoker", 1462 mockService, 1463 cancelHandler, 1464 0, 1465 make(chan struct{}), 1466 FeatureFlags{}, 1467 logger, 1468 testWorkflowType, 1469 testActivityType, 1470 ) 1471 1472 heartbeatErr := cadenceInvoker.BatchHeartbeat(nil) 1473 t.NotNil(heartbeatErr) 1474 _, ok := (heartbeatErr).(*s.DomainNotActiveError) 1475 t.True(ok, "heartbeatErr must be DomainNotActiveError.") 1476 t.True(called) 1477 } 1478 1479 type testActivityDeadline struct { 1480 logger *zap.Logger 1481 d time.Duration 1482 } 1483 1484 func (t *testActivityDeadline) Execute(ctx context.Context, input []byte) ([]byte, error) { 1485 if d, _ := ctx.Deadline(); d.IsZero() { 1486 panic("invalid deadline provided") 1487 } 1488 if t.d != 0 { 1489 // Wait till deadline expires. 1490 <-ctx.Done() 1491 return nil, ctx.Err() 1492 } 1493 return nil, nil 1494 } 1495 1496 func (t *testActivityDeadline) ActivityType() ActivityType { 1497 return ActivityType{Name: "test"} 1498 } 1499 1500 func (t *testActivityDeadline) GetFunction() interface{} { 1501 return t.Execute 1502 } 1503 1504 func (t *testActivityDeadline) GetOptions() RegisterActivityOptions { 1505 return RegisterActivityOptions{} 1506 } 1507 1508 type deadlineTest struct { 1509 actWaitDuration time.Duration 1510 ScheduleTS time.Time 1511 ScheduleDuration int32 1512 StartTS time.Time 1513 StartDuration int32 1514 err error 1515 } 1516 1517 func (t *TaskHandlersTestSuite) TestActivityExecutionDeadline() { 1518 deadlineTests := []deadlineTest{ 1519 {time.Duration(0), time.Now(), 3, time.Now(), 3, nil}, 1520 {time.Duration(0), time.Now(), 4, time.Now(), 3, nil}, 1521 {time.Duration(0), time.Now(), 3, time.Now(), 4, nil}, 1522 {time.Duration(0), time.Now().Add(-1 * time.Second), 1, time.Now(), 1, context.DeadlineExceeded}, 1523 {time.Duration(0), time.Now(), 1, time.Now().Add(-1 * time.Second), 1, context.DeadlineExceeded}, 1524 {time.Duration(0), time.Now().Add(-1 * time.Second), 1, time.Now().Add(-1 * time.Second), 1, context.DeadlineExceeded}, 1525 {time.Duration(1 * time.Second), time.Now(), 1, time.Now(), 1, context.DeadlineExceeded}, 1526 {time.Duration(1 * time.Second), time.Now(), 2, time.Now(), 1, context.DeadlineExceeded}, 1527 {time.Duration(1 * time.Second), time.Now(), 1, time.Now(), 2, context.DeadlineExceeded}, 1528 } 1529 a := &testActivityDeadline{logger: t.logger} 1530 registry := t.registry 1531 registry.addActivityWithLock(a.ActivityType().Name, a) 1532 1533 mockCtrl := gomock.NewController(t.T()) 1534 mockService := workflowservicetest.NewMockClient(mockCtrl) 1535 1536 for i, d := range deadlineTests { 1537 a.d = d.actWaitDuration 1538 wep := workerExecutionParameters{ 1539 WorkerOptions: WorkerOptions{ 1540 Logger: t.logger, 1541 DataConverter: getDefaultDataConverter(), 1542 Tracer: opentracing.NoopTracer{}, 1543 }, 1544 } 1545 activityHandler := newActivityTaskHandler(mockService, wep, registry) 1546 pats := &s.PollForActivityTaskResponse{ 1547 TaskToken: []byte("token"), 1548 WorkflowExecution: &s.WorkflowExecution{ 1549 WorkflowId: common.StringPtr("wID"), 1550 RunId: common.StringPtr("rID")}, 1551 ActivityType: &s.ActivityType{Name: common.StringPtr("test")}, 1552 ActivityId: common.StringPtr(uuid.New()), 1553 ScheduledTimestamp: common.Int64Ptr(d.ScheduleTS.UnixNano()), 1554 ScheduledTimestampOfThisAttempt: common.Int64Ptr(d.ScheduleTS.UnixNano()), 1555 ScheduleToCloseTimeoutSeconds: common.Int32Ptr(d.ScheduleDuration), 1556 StartedTimestamp: common.Int64Ptr(d.StartTS.UnixNano()), 1557 StartToCloseTimeoutSeconds: common.Int32Ptr(d.StartDuration), 1558 WorkflowType: &s.WorkflowType{ 1559 Name: common.StringPtr("wType"), 1560 }, 1561 WorkflowDomain: common.StringPtr("domain"), 1562 } 1563 td := fmt.Sprintf("testIndex: %v, testDetails: %v", i, d) 1564 r, err := activityHandler.Execute(tasklist, pats) 1565 t.logger.Info(fmt.Sprintf("test: %v, result: %v err: %v", td, r, err)) 1566 t.Equal(d.err, err, td) 1567 if err != nil { 1568 t.Nil(r, td) 1569 } 1570 } 1571 } 1572 1573 func activityWithWorkerStop(ctx context.Context) error { 1574 fmt.Println("Executing Activity with worker stop") 1575 workerStopCh := GetWorkerStopChannel(ctx) 1576 1577 select { 1578 case <-workerStopCh: 1579 return nil 1580 case <-time.NewTimer(time.Second * 5).C: 1581 return fmt.Errorf("Activity failed to handle worker stop event") 1582 } 1583 } 1584 1585 func (t *TaskHandlersTestSuite) TestActivityExecutionWorkerStop() { 1586 a := &testActivityDeadline{logger: t.logger} 1587 registry := t.registry 1588 registry.RegisterActivityWithOptions( 1589 activityWithWorkerStop, 1590 RegisterActivityOptions{Name: a.ActivityType().Name, DisableAlreadyRegisteredCheck: true}, 1591 ) 1592 1593 mockCtrl := gomock.NewController(t.T()) 1594 mockService := workflowservicetest.NewMockClient(mockCtrl) 1595 workerStopCh := make(chan struct{}, 1) 1596 ctx, cancel := context.WithCancel(context.Background()) 1597 wep := workerExecutionParameters{ 1598 WorkerOptions: WorkerOptions{ 1599 Logger: t.logger, 1600 DataConverter: getDefaultDataConverter(), 1601 }, 1602 UserContext: ctx, 1603 UserContextCancel: cancel, 1604 WorkerStopChannel: workerStopCh, 1605 } 1606 activityHandler := newActivityTaskHandler(mockService, wep, registry) 1607 pats := &s.PollForActivityTaskResponse{ 1608 TaskToken: []byte("token"), 1609 WorkflowExecution: &s.WorkflowExecution{ 1610 WorkflowId: common.StringPtr("wID"), 1611 RunId: common.StringPtr("rID")}, 1612 ActivityType: &s.ActivityType{Name: common.StringPtr("test")}, 1613 ActivityId: common.StringPtr(uuid.New()), 1614 ScheduledTimestamp: common.Int64Ptr(time.Now().UnixNano()), 1615 ScheduledTimestampOfThisAttempt: common.Int64Ptr(time.Now().UnixNano()), 1616 ScheduleToCloseTimeoutSeconds: common.Int32Ptr(1), 1617 StartedTimestamp: common.Int64Ptr(time.Now().UnixNano()), 1618 StartToCloseTimeoutSeconds: common.Int32Ptr(1), 1619 WorkflowType: &s.WorkflowType{ 1620 Name: common.StringPtr("wType"), 1621 }, 1622 WorkflowDomain: common.StringPtr("domain"), 1623 } 1624 close(workerStopCh) 1625 r, err := activityHandler.Execute(tasklist, pats) 1626 t.NoError(err) 1627 t.NotNil(r) 1628 } 1629 1630 // a regrettably-hacky func to use goleak to count leaking goroutines. 1631 // ideally there will be a structured way to do this in the future, rather than string parsing 1632 func countLeaks(leaks error) int { 1633 if leaks == nil { 1634 return 0 1635 } 1636 // leak messages look something like: 1637 // Goroutine 23 in state chan receive, with go.uber.org/cadence/internal.(*coroutineState).initialYield on top of the stack: 1638 // ... stacktrace ... 1639 // 1640 // Goroutine 28 ... on top of the stack: 1641 // ... stacktrace ... 1642 return strings.Count(leaks.Error(), "on top of the stack") 1643 } 1644 1645 func (t *TaskHandlersTestSuite) TestRegression_QueriesDoNotLeakGoroutines() { 1646 // this test must not be run in parallel with most other tests, as it mutates global vars 1647 var ridsToCleanUp []string 1648 originalLeaks := goleak.Find() 1649 defer func(size int) { 1650 // empty the cache to clear out any newly-introduced leaks 1651 current := getWorkflowCache() 1652 for _, rid := range ridsToCleanUp { 1653 current.Delete(rid) 1654 } 1655 // check the cleanup 1656 currentLeaks := goleak.Find() 1657 if countLeaks(currentLeaks) != countLeaks(originalLeaks) { 1658 t.T().Errorf("failed to clean up goroutines.\nOriginal state:\n%v\n\nCurrent state:\n%v", originalLeaks, currentLeaks) 1659 } 1660 1661 // reset everything to make it "normal". 1662 // this does NOT restore the original workflow cache - that cannot be done correctly, initCacheOnce is not safe to copy (thus restore). 1663 stickyCacheSize = size 1664 workflowCache = nil 1665 initCacheOnce = sync.Once{} 1666 }(stickyCacheSize) 1667 workflowCache = nil 1668 initCacheOnce = sync.Once{} 1669 // cache is intentionally not *disabled*, as that would go down no-cache code paths. 1670 // also, there is an LRU-cache bug where the size allows N to enter, but then removes until N-1 remain, 1671 // so a size of 2 actually means a size of 1. 1672 SetStickyWorkflowCacheSize(2) 1673 1674 taskList := "tl1" 1675 params := workerExecutionParameters{ 1676 TaskList: taskList, 1677 WorkerOptions: WorkerOptions{ 1678 Identity: "test-id-1", 1679 Logger: t.logger, 1680 DisableStickyExecution: false, 1681 }, 1682 } 1683 taskHandler := newWorkflowTaskHandler(testDomain, params, nil, t.registry) 1684 1685 // process a throw-away workflow to fill the cache. this is copied from TestWorkflowTask_QueryWorkflow since it's 1686 // relatively simple, but any should work fine, as long as it can be queried. 1687 testEvents := []*s.HistoryEvent{ 1688 createTestEventWorkflowExecutionStarted(1, &s.WorkflowExecutionStartedEventAttributes{TaskList: &s.TaskList{Name: &taskList}}), 1689 createTestEventDecisionTaskScheduled(2, &s.DecisionTaskScheduledEventAttributes{TaskList: &s.TaskList{Name: &taskList}}), 1690 createTestEventDecisionTaskStarted(3), 1691 createTestEventDecisionTaskCompleted(4, &s.DecisionTaskCompletedEventAttributes{ScheduledEventId: common.Int64Ptr(2)}), 1692 createTestEventActivityTaskScheduled(5, &s.ActivityTaskScheduledEventAttributes{ 1693 ActivityId: common.StringPtr("0"), 1694 ActivityType: &s.ActivityType{Name: common.StringPtr("Greeter_Activity")}, 1695 TaskList: &s.TaskList{Name: &taskList}, 1696 }), 1697 } 1698 cachedTask := createWorkflowTask(testEvents[0:1], 1, "HelloWorld_Workflow") 1699 cachedTask.WorkflowExecution.WorkflowId = common.StringPtr("cache-filling workflow id") 1700 ridsToCleanUp = append(ridsToCleanUp, *cachedTask.WorkflowExecution.RunId) 1701 _, err := taskHandler.ProcessWorkflowTask(&workflowTask{task: cachedTask}, nil) 1702 1703 // sanity check that the cache was indeed filled, and that it has created a goroutine 1704 require.NoError(t.T(), err, "cache-filling must succeed") 1705 require.Equal(t.T(), 1, getWorkflowCache().Size(), "workflow should be cached, but was not") 1706 oneCachedLeak := goleak.Find() 1707 require.Error(t.T(), oneCachedLeak, "expected at least one leaking goroutine") 1708 require.Equal(t.T(), countLeaks(originalLeaks)+1, countLeaks(oneCachedLeak), // ideally == 1, but currently there are other leaks 1709 "expected the cached workflow to leak one goroutine. original leaks:\n%v\n\nleaks after one workflow:\n%v", originalLeaks, oneCachedLeak) 1710 1711 // now query a different workflow ID / run ID 1712 uncachedTask := createQueryTask(testEvents, 5, "HelloWorld_Workflow", queryType) 1713 uncachedTask.WorkflowExecution.WorkflowId = common.StringPtr("should not leak this workflow id") 1714 ridsToCleanUp = append(ridsToCleanUp, *uncachedTask.WorkflowExecution.RunId) // only necessary if the test fails 1715 result, err := taskHandler.ProcessWorkflowTask(&workflowTask{task: uncachedTask}, nil) 1716 require.NoError(t.T(), err) 1717 t.verifyQueryResult(result, "waiting-activity-result") // largely a sanity check 1718 1719 // and finally the purpose of this test: 1720 // verify that the cache has not been modified, and that there is no new leak 1721 t.Equal(1, getWorkflowCache().Size(), "workflow cache should be the same size") 1722 t.True(getWorkflowCache().Exist(cachedTask.WorkflowExecution.GetRunId()), "originally-cached workflow should still be cached") 1723 t.False(getWorkflowCache().Exist(uncachedTask.WorkflowExecution.GetRunId()), "queried workflow should not be cached") 1724 newLeaks := goleak.Find() 1725 t.Error(newLeaks, "expected at least one leaking goroutine") 1726 t.Equal(countLeaks(oneCachedLeak), countLeaks(newLeaks), 1727 "expected the query to leak no new goroutines. before query:\n%v\n\nafter query:\n%v", oneCachedLeak, newLeaks) 1728 } 1729 1730 func Test_NonDeterministicCheck(t *testing.T) { 1731 decisionTypes := s.DecisionType_Values() 1732 require.Equal(t, 13, len(decisionTypes), "If you see this error, you are adding new decision type. "+ 1733 "Before updating the number to make this test pass, please make sure you update isDecisionMatchEvent() method "+ 1734 "to check the new decision type. Otherwise the replay will fail on the new decision event.") 1735 1736 eventTypes := s.EventType_Values() 1737 decisionEventTypeCount := 0 1738 for _, et := range eventTypes { 1739 if isDecisionEvent(et) { 1740 decisionEventTypeCount++ 1741 } 1742 } 1743 // CancelTimer has 2 corresponding events. 1744 require.Equal(t, len(decisionTypes)+1, decisionEventTypeCount, "Every decision type must have one matching event type. "+ 1745 "If you add new decision type, you need to update isDecisionEvent() method to include that new event type as well.") 1746 } 1747 1748 func Test_IsDecisionMatchEvent_UpsertWorkflowSearchAttributes(t *testing.T) { 1749 diType := s.DecisionTypeUpsertWorkflowSearchAttributes 1750 eType := s.EventTypeUpsertWorkflowSearchAttributes 1751 strictMode := false 1752 1753 testCases := []struct { 1754 name string 1755 decision *s.Decision 1756 event *s.HistoryEvent 1757 expected bool 1758 }{ 1759 { 1760 name: "event type not match", 1761 decision: &s.Decision{ 1762 DecisionType: &diType, 1763 UpsertWorkflowSearchAttributesDecisionAttributes: &s.UpsertWorkflowSearchAttributesDecisionAttributes{ 1764 SearchAttributes: &s.SearchAttributes{}, 1765 }, 1766 }, 1767 event: &s.HistoryEvent{}, 1768 expected: false, 1769 }, 1770 { 1771 name: "attributes not match", 1772 decision: &s.Decision{ 1773 DecisionType: &diType, 1774 UpsertWorkflowSearchAttributesDecisionAttributes: &s.UpsertWorkflowSearchAttributesDecisionAttributes{ 1775 SearchAttributes: &s.SearchAttributes{}, 1776 }, 1777 }, 1778 event: &s.HistoryEvent{ 1779 EventType: &eType, 1780 UpsertWorkflowSearchAttributesEventAttributes: &s.UpsertWorkflowSearchAttributesEventAttributes{}, 1781 }, 1782 expected: true, 1783 }, 1784 { 1785 name: "attributes match", 1786 decision: &s.Decision{ 1787 DecisionType: &diType, 1788 UpsertWorkflowSearchAttributesDecisionAttributes: &s.UpsertWorkflowSearchAttributesDecisionAttributes{ 1789 SearchAttributes: &s.SearchAttributes{}, 1790 }, 1791 }, 1792 event: &s.HistoryEvent{ 1793 EventType: &eType, 1794 UpsertWorkflowSearchAttributesEventAttributes: &s.UpsertWorkflowSearchAttributesEventAttributes{ 1795 SearchAttributes: &s.SearchAttributes{}, 1796 }, 1797 }, 1798 expected: true, 1799 }, 1800 } 1801 1802 for _, testCase := range testCases { 1803 t.Run(testCase.name, func(t *testing.T) { 1804 require.Equal(t, testCase.expected, isDecisionMatchEvent(testCase.decision, testCase.event, strictMode)) 1805 }) 1806 } 1807 1808 strictMode = true 1809 1810 testCases = []struct { 1811 name string 1812 decision *s.Decision 1813 event *s.HistoryEvent 1814 expected bool 1815 }{ 1816 { 1817 name: "attributes not match", 1818 decision: &s.Decision{ 1819 DecisionType: &diType, 1820 UpsertWorkflowSearchAttributesDecisionAttributes: &s.UpsertWorkflowSearchAttributesDecisionAttributes{ 1821 SearchAttributes: &s.SearchAttributes{}, 1822 }, 1823 }, 1824 event: &s.HistoryEvent{ 1825 EventType: &eType, 1826 UpsertWorkflowSearchAttributesEventAttributes: &s.UpsertWorkflowSearchAttributesEventAttributes{}, 1827 }, 1828 expected: false, 1829 }, 1830 } 1831 1832 for _, testCase := range testCases { 1833 t.Run(testCase.name, func(t *testing.T) { 1834 require.Equal(t, testCase.expected, isDecisionMatchEvent(testCase.decision, testCase.event, strictMode)) 1835 }) 1836 } 1837 } 1838 1839 func Test_IsSearchAttributesMatched(t *testing.T) { 1840 testCases := []struct { 1841 name string 1842 lhs *s.SearchAttributes 1843 rhs *s.SearchAttributes 1844 expected bool 1845 }{ 1846 { 1847 name: "both nil", 1848 lhs: nil, 1849 rhs: nil, 1850 expected: true, 1851 }, 1852 { 1853 name: "left nil", 1854 lhs: nil, 1855 rhs: &s.SearchAttributes{}, 1856 expected: false, 1857 }, 1858 { 1859 name: "right nil", 1860 lhs: &s.SearchAttributes{}, 1861 rhs: nil, 1862 expected: false, 1863 }, 1864 { 1865 name: "not match", 1866 lhs: &s.SearchAttributes{ 1867 IndexedFields: map[string][]byte{ 1868 "key1": []byte("1"), 1869 "key2": []byte("abc"), 1870 }, 1871 }, 1872 rhs: &s.SearchAttributes{}, 1873 expected: false, 1874 }, 1875 { 1876 name: "match", 1877 lhs: &s.SearchAttributes{ 1878 IndexedFields: map[string][]byte{ 1879 "key1": []byte("1"), 1880 "key2": []byte("abc"), 1881 }, 1882 }, 1883 rhs: &s.SearchAttributes{ 1884 IndexedFields: map[string][]byte{ 1885 "key2": []byte("abc"), 1886 "key1": []byte("1"), 1887 }, 1888 }, 1889 expected: true, 1890 }, 1891 } 1892 1893 for _, testCase := range testCases { 1894 t.Run(testCase.name, func(t *testing.T) { 1895 require.Equal(t, testCase.expected, isSearchAttributesMatched(testCase.lhs, testCase.rhs)) 1896 }) 1897 } 1898 }