go.uber.org/cadence@v1.2.9/internal/internal_workers_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 "testing" 27 "time" 28 29 "github.com/golang/mock/gomock" 30 "github.com/pborman/uuid" 31 "github.com/stretchr/testify/suite" 32 "go.uber.org/atomic" 33 "go.uber.org/yarpc" 34 "go.uber.org/zap" 35 "go.uber.org/zap/zaptest" 36 37 "go.uber.org/cadence/.gen/go/cadence/workflowservicetest" 38 m "go.uber.org/cadence/.gen/go/shared" 39 "go.uber.org/cadence/internal/common" 40 ) 41 42 // ActivityTaskHandler never returns response 43 type noResponseActivityTaskHandler struct { 44 isExecuteCalled chan struct{} 45 } 46 47 func newNoResponseActivityTaskHandler() *noResponseActivityTaskHandler { 48 return &noResponseActivityTaskHandler{isExecuteCalled: make(chan struct{})} 49 } 50 51 func (ath noResponseActivityTaskHandler) Execute(taskList string, task *m.PollForActivityTaskResponse) (interface{}, error) { 52 close(ath.isExecuteCalled) 53 c := make(chan struct{}) 54 <-c 55 return nil, nil 56 } 57 58 func (ath noResponseActivityTaskHandler) BlockedOnExecuteCalled() error { 59 <-ath.isExecuteCalled 60 return nil 61 } 62 63 type ( 64 WorkersTestSuite struct { 65 suite.Suite 66 mockCtrl *gomock.Controller 67 service *workflowservicetest.MockClient 68 } 69 ) 70 71 // Test suite. 72 func (s *WorkersTestSuite) SetupTest() { 73 s.mockCtrl = gomock.NewController(s.T()) 74 s.service = workflowservicetest.NewMockClient(s.mockCtrl) 75 } 76 77 func (s *WorkersTestSuite) TearDownTest() { 78 s.mockCtrl.Finish() // assert mock’s expectations 79 } 80 81 func TestWorkersTestSuite(t *testing.T) { 82 suite.Run(t, new(WorkersTestSuite)) 83 } 84 85 func (s *WorkersTestSuite) TestWorkflowWorker() { 86 domain := "testDomain" 87 logger, _ := zap.NewDevelopment() 88 89 s.service.EXPECT().DescribeDomain(gomock.Any(), gomock.Any(), callOptions()...).Return(nil, nil) 90 s.service.EXPECT().PollForDecisionTask(gomock.Any(), gomock.Any(), callOptions()...).Return(&m.PollForDecisionTaskResponse{}, nil).AnyTimes() 91 s.service.EXPECT().RespondDecisionTaskCompleted(gomock.Any(), gomock.Any(), callOptions()...).Return(nil, nil).AnyTimes() 92 93 ctx, cancel := context.WithCancel(context.Background()) 94 executionParameters := workerExecutionParameters{ 95 TaskList: "testTaskList", 96 WorkerOptions: WorkerOptions{ 97 MaxConcurrentDecisionTaskPollers: 5, 98 Logger: logger}, 99 UserContext: ctx, 100 UserContextCancel: cancel, 101 } 102 overrides := &workerOverrides{workflowTaskHandler: newSampleWorkflowTaskHandler()} 103 workflowWorker := newWorkflowWorkerInternal( 104 s.service, domain, executionParameters, nil, overrides, newRegistry(), nil, 105 ) 106 workflowWorker.Start() 107 workflowWorker.Stop() 108 109 s.Nil(ctx.Err()) 110 } 111 112 func (s *WorkersTestSuite) TestActivityWorker() { 113 s.testActivityWorker(false) 114 } 115 116 func (s *WorkersTestSuite) TestActivityWorkerWithLocalActivityDispatch() { 117 s.testActivityWorker(true) 118 } 119 120 func (s *WorkersTestSuite) testActivityWorker(useLocallyDispatched bool) { 121 domain := "testDomain" 122 s.service.EXPECT().DescribeDomain(gomock.Any(), gomock.Any(), callOptions()...).Return(nil, nil) 123 s.service.EXPECT().PollForActivityTask(gomock.Any(), gomock.Any(), callOptions()...).Return(&m.PollForActivityTaskResponse{}, nil).AnyTimes() 124 s.service.EXPECT().RespondActivityTaskCompleted(gomock.Any(), gomock.Any(), callOptions()...).Return(nil).AnyTimes() 125 126 executionParameters := workerExecutionParameters{ 127 TaskList: "testTaskList", 128 WorkerOptions: WorkerOptions{ 129 MaxConcurrentActivityTaskPollers: 5, 130 Logger: zaptest.NewLogger(s.T())}, 131 } 132 overrides := &workerOverrides{activityTaskHandler: newSampleActivityTaskHandler(), useLocallyDispatchedActivityPoller: useLocallyDispatched} 133 a := &greeterActivity{} 134 registry := newRegistry() 135 registry.addActivityWithLock(a.ActivityType().Name, a) 136 activityWorker := newActivityWorker( 137 s.service, domain, executionParameters, overrides, registry, nil, 138 ) 139 activityWorker.Start() 140 activityWorker.Stop() 141 } 142 143 func (s *WorkersTestSuite) TestActivityWorkerStop() { 144 domain := "testDomain" 145 146 pats := &m.PollForActivityTaskResponse{ 147 TaskToken: []byte("token"), 148 WorkflowExecution: &m.WorkflowExecution{ 149 WorkflowId: common.StringPtr("wID"), 150 RunId: common.StringPtr("rID")}, 151 ActivityType: &m.ActivityType{Name: common.StringPtr("test")}, 152 ActivityId: common.StringPtr(uuid.New()), 153 ScheduledTimestamp: common.Int64Ptr(time.Now().UnixNano()), 154 ScheduledTimestampOfThisAttempt: common.Int64Ptr(time.Now().UnixNano()), 155 ScheduleToCloseTimeoutSeconds: common.Int32Ptr(1), 156 StartedTimestamp: common.Int64Ptr(time.Now().UnixNano()), 157 StartToCloseTimeoutSeconds: common.Int32Ptr(1), 158 WorkflowType: &m.WorkflowType{ 159 Name: common.StringPtr("wType"), 160 }, 161 WorkflowDomain: common.StringPtr("domain"), 162 } 163 164 s.service.EXPECT().DescribeDomain(gomock.Any(), gomock.Any(), callOptions()...).Return(nil, nil) 165 s.service.EXPECT().PollForActivityTask(gomock.Any(), gomock.Any(), callOptions()...).Return(pats, nil).AnyTimes() 166 s.service.EXPECT().RespondActivityTaskCompleted(gomock.Any(), gomock.Any(), callOptions()...).Return(nil).AnyTimes() 167 168 stopC := make(chan struct{}) 169 ctx, cancel := context.WithCancel(context.Background()) 170 executionParameters := workerExecutionParameters{ 171 TaskList: "testTaskList", 172 WorkerOptions: AugmentWorkerOptions( 173 WorkerOptions{ 174 MaxConcurrentActivityTaskPollers: 5, 175 MaxConcurrentActivityExecutionSize: 2, 176 Logger: zaptest.NewLogger(s.T()), 177 }, 178 ), 179 UserContext: ctx, 180 UserContextCancel: cancel, 181 WorkerStopTimeout: time.Second * 2, 182 WorkerStopChannel: stopC, 183 } 184 activityTaskHandler := newNoResponseActivityTaskHandler() 185 overrides := &workerOverrides{activityTaskHandler: activityTaskHandler} 186 a := &greeterActivity{} 187 registry := newRegistry() 188 registry.addActivityWithLock(a.ActivityType().Name, a) 189 worker := newActivityWorker( 190 s.service, domain, executionParameters, overrides, registry, nil, 191 ) 192 worker.Start() 193 activityTaskHandler.BlockedOnExecuteCalled() 194 go worker.Stop() 195 196 <-worker.worker.shutdownCh 197 err := ctx.Err() 198 s.NoError(err) 199 200 <-ctx.Done() 201 err = ctx.Err() 202 s.Error(err) 203 } 204 205 func (s *WorkersTestSuite) TestPollForDecisionTask_InternalServiceError() { 206 domain := "testDomain" 207 208 s.service.EXPECT().DescribeDomain(gomock.Any(), gomock.Any(), callOptions()...).Return(nil, nil) 209 s.service.EXPECT().PollForDecisionTask(gomock.Any(), gomock.Any(), callOptions()...).Return(&m.PollForDecisionTaskResponse{}, &m.InternalServiceError{}).AnyTimes() 210 211 executionParameters := workerExecutionParameters{ 212 TaskList: "testDecisionTaskList", 213 WorkerOptions: WorkerOptions{ 214 MaxConcurrentDecisionTaskPollers: 5, 215 Logger: zaptest.NewLogger(s.T())}, 216 } 217 overrides := &workerOverrides{workflowTaskHandler: newSampleWorkflowTaskHandler()} 218 workflowWorker := newWorkflowWorkerInternal( 219 s.service, domain, executionParameters, nil, overrides, newRegistry(), nil, 220 ) 221 workflowWorker.Start() 222 workflowWorker.Stop() 223 } 224 225 func (s *WorkersTestSuite) TestLongRunningDecisionTask() { 226 localActivityCalledCount := 0 227 localActivitySleep := func(duration time.Duration) error { 228 time.Sleep(duration) 229 localActivityCalledCount++ 230 return nil 231 } 232 233 doneCh := make(chan struct{}) 234 235 isWorkflowCompleted := false 236 longDecisionWorkflowFn := func(ctx Context, input []byte) error { 237 lao := LocalActivityOptions{ 238 ScheduleToCloseTimeout: time.Second * 2, 239 } 240 ctx = WithLocalActivityOptions(ctx, lao) 241 err := ExecuteLocalActivity(ctx, localActivitySleep, time.Second).Get(ctx, nil) 242 243 if err != nil { 244 return err 245 } 246 247 err = ExecuteLocalActivity(ctx, localActivitySleep, time.Second).Get(ctx, nil) 248 isWorkflowCompleted = true 249 return err 250 } 251 252 domain := "testDomain" 253 taskList := "long-running-decision-tl" 254 testEvents := []*m.HistoryEvent{ 255 { 256 EventId: common.Int64Ptr(1), 257 EventType: common.EventTypePtr(m.EventTypeWorkflowExecutionStarted), 258 WorkflowExecutionStartedEventAttributes: &m.WorkflowExecutionStartedEventAttributes{ 259 TaskList: &m.TaskList{Name: &taskList}, 260 ExecutionStartToCloseTimeoutSeconds: common.Int32Ptr(10), 261 TaskStartToCloseTimeoutSeconds: common.Int32Ptr(2), 262 WorkflowType: &m.WorkflowType{Name: common.StringPtr("long-running-decision-workflow-type")}, 263 }, 264 }, 265 createTestEventDecisionTaskScheduled(2, &m.DecisionTaskScheduledEventAttributes{TaskList: &m.TaskList{Name: &taskList}}), 266 createTestEventDecisionTaskStarted(3), 267 createTestEventDecisionTaskCompleted(4, &m.DecisionTaskCompletedEventAttributes{ScheduledEventId: common.Int64Ptr(2)}), 268 { 269 EventId: common.Int64Ptr(5), 270 EventType: common.EventTypePtr(m.EventTypeMarkerRecorded), 271 MarkerRecordedEventAttributes: &m.MarkerRecordedEventAttributes{ 272 MarkerName: common.StringPtr(localActivityMarkerName), 273 Details: s.createLocalActivityMarkerDataForTest("0"), 274 DecisionTaskCompletedEventId: common.Int64Ptr(4), 275 }, 276 }, 277 createTestEventDecisionTaskScheduled(6, &m.DecisionTaskScheduledEventAttributes{TaskList: &m.TaskList{Name: &taskList}}), 278 createTestEventDecisionTaskStarted(7), 279 createTestEventDecisionTaskCompleted(8, &m.DecisionTaskCompletedEventAttributes{ScheduledEventId: common.Int64Ptr(2)}), 280 { 281 EventId: common.Int64Ptr(9), 282 EventType: common.EventTypePtr(m.EventTypeMarkerRecorded), 283 MarkerRecordedEventAttributes: &m.MarkerRecordedEventAttributes{ 284 MarkerName: common.StringPtr(localActivityMarkerName), 285 Details: s.createLocalActivityMarkerDataForTest("1"), 286 DecisionTaskCompletedEventId: common.Int64Ptr(8), 287 }, 288 }, 289 createTestEventDecisionTaskScheduled(10, &m.DecisionTaskScheduledEventAttributes{TaskList: &m.TaskList{Name: &taskList}}), 290 createTestEventDecisionTaskStarted(11), 291 } 292 293 s.service.EXPECT().DescribeDomain(gomock.Any(), gomock.Any(), callOptions()...).Return(nil, nil).AnyTimes() 294 task := &m.PollForDecisionTaskResponse{ 295 TaskToken: []byte("test-token"), 296 WorkflowExecution: &m.WorkflowExecution{ 297 WorkflowId: common.StringPtr("long-running-decision-workflow-id"), 298 RunId: common.StringPtr("long-running-decision-workflow-run-id"), 299 }, 300 WorkflowType: &m.WorkflowType{ 301 Name: common.StringPtr("long-running-decision-workflow-type"), 302 }, 303 PreviousStartedEventId: common.Int64Ptr(0), 304 StartedEventId: common.Int64Ptr(3), 305 History: &m.History{Events: testEvents[0:3]}, 306 NextPageToken: nil, 307 NextEventId: common.Int64Ptr(4), 308 } 309 s.service.EXPECT().PollForDecisionTask(gomock.Any(), gomock.Any(), callOptions()...).Return(task, nil).Times(1) 310 s.service.EXPECT().PollForDecisionTask(gomock.Any(), gomock.Any(), callOptions()...).Return(&m.PollForDecisionTaskResponse{}, &m.InternalServiceError{}).AnyTimes() 311 312 respondCounter := 0 313 s.service.EXPECT().RespondDecisionTaskCompleted(gomock.Any(), gomock.Any(), callOptions()...).DoAndReturn(func(ctx context.Context, request *m.RespondDecisionTaskCompletedRequest, opts ...yarpc.CallOption, 314 ) (success *m.RespondDecisionTaskCompletedResponse, err error) { 315 respondCounter++ 316 switch respondCounter { 317 case 1: 318 s.Equal(1, len(request.Decisions)) 319 s.Equal(m.DecisionTypeRecordMarker, request.Decisions[0].GetDecisionType()) 320 *task.PreviousStartedEventId = 3 321 *task.StartedEventId = 7 322 task.History.Events = testEvents[3:7] 323 return &m.RespondDecisionTaskCompletedResponse{DecisionTask: task}, nil 324 case 2: 325 s.Equal(2, len(request.Decisions)) 326 s.Equal(m.DecisionTypeRecordMarker, request.Decisions[0].GetDecisionType()) 327 s.Equal(m.DecisionTypeCompleteWorkflowExecution, request.Decisions[1].GetDecisionType()) 328 *task.PreviousStartedEventId = 7 329 *task.StartedEventId = 11 330 task.History.Events = testEvents[7:11] 331 close(doneCh) 332 return nil, nil 333 default: 334 panic("unexpected RespondDecisionTaskCompleted") 335 } 336 }).Times(2) 337 338 options := WorkerOptions{ 339 Logger: zaptest.NewLogger(s.T()), 340 DisableActivityWorker: true, 341 Identity: "test-worker-identity", 342 } 343 worker := newAggregatedWorker(s.service, domain, taskList, options) 344 worker.RegisterWorkflowWithOptions( 345 longDecisionWorkflowFn, 346 RegisterWorkflowOptions{Name: "long-running-decision-workflow-type"}, 347 ) 348 worker.RegisterActivity(localActivitySleep) 349 350 startWorkerAndWait(s, worker, &doneCh) 351 352 s.True(isWorkflowCompleted) 353 s.Equal(2, localActivityCalledCount) 354 } 355 356 func (s *WorkersTestSuite) TestQueryTask_WorkflowCacheEvicted() { 357 domain := "testDomain" 358 taskList := "query-task-cache-evicted-tl" 359 workflowType := "query-task-cache-evicted-workflow" 360 workflowID := "query-task-cache-evicted-workflow-id" 361 runID := "query-task-cache-evicted-workflow-run-id" 362 activityType := "query-task-cache-evicted-activity" 363 queryType := "state" 364 doneCh := make(chan struct{}) 365 366 activityFn := func(ctx context.Context) error { 367 return nil 368 } 369 370 queryWorkflowFn := func(ctx Context) error { 371 queryResult := "started" 372 // setup query handler for query type "state" 373 if err := SetQueryHandler(ctx, queryType, func(input []byte) (string, error) { 374 return queryResult, nil 375 }); err != nil { 376 return err 377 } 378 379 queryResult = "waiting on timer" 380 NewTimer(ctx, time.Minute*2).Get(ctx, nil) 381 382 queryResult = "waiting on activity" 383 ctx = WithActivityOptions(ctx, ActivityOptions{ 384 ScheduleToStartTimeout: 10 * time.Second, 385 StartToCloseTimeout: 10 * time.Second, 386 }) 387 if err := ExecuteActivity(ctx, activityFn).Get(ctx, nil); err != nil { 388 return err 389 } 390 queryResult = "done" 391 return nil 392 } 393 394 testEvents := []*m.HistoryEvent{ 395 { 396 EventId: common.Int64Ptr(1), 397 EventType: common.EventTypePtr(m.EventTypeWorkflowExecutionStarted), 398 WorkflowExecutionStartedEventAttributes: &m.WorkflowExecutionStartedEventAttributes{ 399 TaskList: &m.TaskList{Name: &taskList}, 400 ExecutionStartToCloseTimeoutSeconds: common.Int32Ptr(180), 401 TaskStartToCloseTimeoutSeconds: common.Int32Ptr(2), 402 WorkflowType: &m.WorkflowType{Name: common.StringPtr(workflowType)}, 403 }, 404 }, 405 createTestEventDecisionTaskScheduled(2, &m.DecisionTaskScheduledEventAttributes{TaskList: &m.TaskList{Name: &taskList}}), 406 createTestEventDecisionTaskStarted(3), 407 createTestEventDecisionTaskCompleted(4, &m.DecisionTaskCompletedEventAttributes{ScheduledEventId: common.Int64Ptr(2)}), 408 { 409 EventId: common.Int64Ptr(5), 410 EventType: common.EventTypePtr(m.EventTypeTimerStarted), 411 TimerStartedEventAttributes: &m.TimerStartedEventAttributes{ 412 TimerId: common.StringPtr("0"), 413 StartToFireTimeoutSeconds: common.Int64Ptr(120), 414 DecisionTaskCompletedEventId: common.Int64Ptr(4), 415 }, 416 }, 417 { 418 EventId: common.Int64Ptr(6), 419 EventType: common.EventTypePtr(m.EventTypeTimerFired), 420 TimerFiredEventAttributes: &m.TimerFiredEventAttributes{ 421 TimerId: common.StringPtr("0"), 422 StartedEventId: common.Int64Ptr(5), 423 }, 424 }, 425 createTestEventDecisionTaskScheduled(7, &m.DecisionTaskScheduledEventAttributes{TaskList: &m.TaskList{Name: &taskList}}), 426 createTestEventDecisionTaskStarted(8), 427 createTestEventDecisionTaskCompleted(9, &m.DecisionTaskCompletedEventAttributes{ScheduledEventId: common.Int64Ptr(2)}), 428 createTestEventActivityTaskScheduled(10, &m.ActivityTaskScheduledEventAttributes{ 429 ActivityId: common.StringPtr("1"), 430 ActivityType: &m.ActivityType{ 431 Name: common.StringPtr(activityType), 432 }, 433 Domain: common.StringPtr(domain), 434 TaskList: &m.TaskList{ 435 Name: common.StringPtr(taskList), 436 }, 437 ScheduleToStartTimeoutSeconds: common.Int32Ptr(10), 438 StartToCloseTimeoutSeconds: common.Int32Ptr(10), 439 DecisionTaskCompletedEventId: common.Int64Ptr(9), 440 }), 441 } 442 443 s.service.EXPECT().DescribeDomain(gomock.Any(), gomock.Any(), callOptions()...).Return(nil, nil).AnyTimes() 444 task := &m.PollForDecisionTaskResponse{ 445 TaskToken: []byte("test-token"), 446 WorkflowExecution: &m.WorkflowExecution{ 447 WorkflowId: common.StringPtr(workflowID), 448 RunId: common.StringPtr(runID), 449 }, 450 WorkflowType: &m.WorkflowType{ 451 Name: common.StringPtr(workflowType), 452 }, 453 PreviousStartedEventId: common.Int64Ptr(0), 454 StartedEventId: common.Int64Ptr(3), 455 History: &m.History{Events: testEvents[0:3]}, 456 NextPageToken: nil, 457 NextEventId: common.Int64Ptr(4), 458 } 459 s.service.EXPECT().PollForDecisionTask(gomock.Any(), gomock.Any(), callOptions()...).Return(task, nil).Times(1) 460 s.service.EXPECT().RespondDecisionTaskCompleted(gomock.Any(), gomock.Any(), callOptions()...).DoAndReturn(func(ctx context.Context, request *m.RespondDecisionTaskCompletedRequest, opts ...yarpc.CallOption, 461 ) (success *m.RespondDecisionTaskCompletedResponse, err error) { 462 s.Equal(1, len(request.Decisions)) 463 s.Equal(m.DecisionTypeStartTimer, request.Decisions[0].GetDecisionType()) 464 return &m.RespondDecisionTaskCompletedResponse{}, nil 465 }).Times(1) 466 queryTask := &m.PollForDecisionTaskResponse{ 467 TaskToken: []byte("test-token"), 468 WorkflowExecution: &m.WorkflowExecution{ 469 WorkflowId: common.StringPtr(workflowID), 470 RunId: common.StringPtr(runID), 471 }, 472 WorkflowType: &m.WorkflowType{ 473 Name: common.StringPtr(workflowType), 474 }, 475 PreviousStartedEventId: common.Int64Ptr(3), 476 History: &m.History{}, // sticky query, so there's no history 477 NextPageToken: nil, 478 NextEventId: common.Int64Ptr(5), 479 Query: &m.WorkflowQuery{ 480 QueryType: common.StringPtr(queryType), 481 }, 482 } 483 s.service.EXPECT().PollForDecisionTask(gomock.Any(), gomock.Any(), callOptions()...).DoAndReturn(func(ctx context.Context, request *m.PollForDecisionTaskRequest, opts ...yarpc.CallOption, 484 ) (success *m.PollForDecisionTaskResponse, err error) { 485 getWorkflowCache().Delete(runID) // force remove the workflow state 486 return queryTask, nil 487 }).Times(1) 488 s.service.EXPECT().ResetStickyTaskList(gomock.Any(), gomock.Any(), callOptions()...).Return(&m.ResetStickyTaskListResponse{}, nil).AnyTimes() 489 s.service.EXPECT().GetWorkflowExecutionHistory(gomock.Any(), gomock.Any(), callOptions()...).Return(&m.GetWorkflowExecutionHistoryResponse{ 490 History: &m.History{Events: testEvents}, // workflow has made progress, return all available events 491 }, nil).Times(1) 492 dc := getDefaultDataConverter() 493 expectedResult, err := dc.ToData("waiting on timer") 494 s.NoError(err) 495 s.service.EXPECT().RespondQueryTaskCompleted(gomock.Any(), gomock.Any(), callOptions()...).DoAndReturn(func(ctx context.Context, request *m.RespondQueryTaskCompletedRequest, opts ...yarpc.CallOption) error { 496 s.Equal(m.QueryTaskCompletedTypeCompleted, request.GetCompletedType()) 497 s.Equal(expectedResult, request.GetQueryResult()) 498 close(doneCh) 499 return nil 500 }).Times(1) 501 s.service.EXPECT().PollForDecisionTask(gomock.Any(), gomock.Any(), callOptions()...).Return(&m.PollForDecisionTaskResponse{}, &m.InternalServiceError{}).AnyTimes() 502 503 options := WorkerOptions{ 504 Logger: zaptest.NewLogger(s.T()), 505 DisableActivityWorker: true, 506 Identity: "test-worker-identity", 507 DataConverter: dc, 508 // set concurrent decision task execution to 1, 509 // otherwise query task may be polled and start execution 510 // before decision task put created workflowContext into the cache, 511 // resulting in a cache hit for query 512 // by setting concurrent execution size to 1, we ensure when polling 513 // query task, cache already contains the workflowContext for this workflow, 514 // and we can force clear the cache when polling the query task. 515 // See the mock function for the second PollForDecisionTask call above. 516 MaxConcurrentDecisionTaskExecutionSize: 1, 517 } 518 worker := newAggregatedWorker(s.service, domain, taskList, options) 519 worker.RegisterWorkflowWithOptions( 520 queryWorkflowFn, 521 RegisterWorkflowOptions{Name: workflowType}, 522 ) 523 worker.RegisterActivityWithOptions( 524 activityFn, 525 RegisterActivityOptions{Name: activityType}, 526 ) 527 528 startWorkerAndWait(s, worker, &doneCh) 529 } 530 531 func (s *WorkersTestSuite) TestMultipleLocalActivities() { 532 localActivityCalledCount := 0 533 localActivitySleep := func(duration time.Duration) error { 534 time.Sleep(duration) 535 localActivityCalledCount++ 536 return nil 537 } 538 539 doneCh := make(chan struct{}) 540 541 isWorkflowCompleted := false 542 longDecisionWorkflowFn := func(ctx Context, input []byte) error { 543 lao := LocalActivityOptions{ 544 ScheduleToCloseTimeout: time.Second * 2, 545 } 546 ctx = WithLocalActivityOptions(ctx, lao) 547 err := ExecuteLocalActivity(ctx, localActivitySleep, time.Second).Get(ctx, nil) 548 549 if err != nil { 550 return err 551 } 552 553 err = ExecuteLocalActivity(ctx, localActivitySleep, time.Second).Get(ctx, nil) 554 isWorkflowCompleted = true 555 return err 556 } 557 558 domain := "testDomain" 559 taskList := "multiple-local-activities-tl" 560 testEvents := []*m.HistoryEvent{ 561 { 562 EventId: common.Int64Ptr(1), 563 EventType: common.EventTypePtr(m.EventTypeWorkflowExecutionStarted), 564 WorkflowExecutionStartedEventAttributes: &m.WorkflowExecutionStartedEventAttributes{ 565 TaskList: &m.TaskList{Name: &taskList}, 566 ExecutionStartToCloseTimeoutSeconds: common.Int32Ptr(10), 567 TaskStartToCloseTimeoutSeconds: common.Int32Ptr(3), 568 WorkflowType: &m.WorkflowType{Name: common.StringPtr("multiple-local-activities-workflow-type")}, 569 }, 570 }, 571 createTestEventDecisionTaskScheduled(2, &m.DecisionTaskScheduledEventAttributes{TaskList: &m.TaskList{Name: &taskList}}), 572 createTestEventDecisionTaskStarted(3), 573 createTestEventDecisionTaskCompleted(4, &m.DecisionTaskCompletedEventAttributes{ScheduledEventId: common.Int64Ptr(2)}), 574 { 575 EventId: common.Int64Ptr(5), 576 EventType: common.EventTypePtr(m.EventTypeMarkerRecorded), 577 MarkerRecordedEventAttributes: &m.MarkerRecordedEventAttributes{ 578 MarkerName: common.StringPtr(localActivityMarkerName), 579 Details: s.createLocalActivityMarkerDataForTest("0"), 580 DecisionTaskCompletedEventId: common.Int64Ptr(4), 581 }, 582 }, 583 createTestEventDecisionTaskScheduled(6, &m.DecisionTaskScheduledEventAttributes{TaskList: &m.TaskList{Name: &taskList}}), 584 createTestEventDecisionTaskStarted(7), 585 createTestEventDecisionTaskCompleted(8, &m.DecisionTaskCompletedEventAttributes{ScheduledEventId: common.Int64Ptr(2)}), 586 { 587 EventId: common.Int64Ptr(9), 588 EventType: common.EventTypePtr(m.EventTypeMarkerRecorded), 589 MarkerRecordedEventAttributes: &m.MarkerRecordedEventAttributes{ 590 MarkerName: common.StringPtr(localActivityMarkerName), 591 Details: s.createLocalActivityMarkerDataForTest("1"), 592 DecisionTaskCompletedEventId: common.Int64Ptr(8), 593 }, 594 }, 595 createTestEventDecisionTaskScheduled(10, &m.DecisionTaskScheduledEventAttributes{TaskList: &m.TaskList{Name: &taskList}}), 596 createTestEventDecisionTaskStarted(11), 597 } 598 599 s.service.EXPECT().DescribeDomain(gomock.Any(), gomock.Any(), callOptions()...).Return(nil, nil).AnyTimes() 600 task := &m.PollForDecisionTaskResponse{ 601 TaskToken: []byte("test-token"), 602 WorkflowExecution: &m.WorkflowExecution{ 603 WorkflowId: common.StringPtr("multiple-local-activities-workflow-id"), 604 RunId: common.StringPtr("multiple-local-activities-workflow-run-id"), 605 }, 606 WorkflowType: &m.WorkflowType{ 607 Name: common.StringPtr("multiple-local-activities-workflow-type"), 608 }, 609 PreviousStartedEventId: common.Int64Ptr(0), 610 StartedEventId: common.Int64Ptr(3), 611 History: &m.History{Events: testEvents[0:3]}, 612 NextPageToken: nil, 613 NextEventId: common.Int64Ptr(4), 614 } 615 s.service.EXPECT().PollForDecisionTask(gomock.Any(), gomock.Any(), callOptions()...).Return(task, nil).Times(1) 616 s.service.EXPECT().PollForDecisionTask(gomock.Any(), gomock.Any(), callOptions()...).Return(&m.PollForDecisionTaskResponse{}, &m.InternalServiceError{}).AnyTimes() 617 618 respondCounter := 0 619 s.service.EXPECT().RespondDecisionTaskCompleted(gomock.Any(), gomock.Any(), callOptions()...).DoAndReturn(func(ctx context.Context, request *m.RespondDecisionTaskCompletedRequest, opts ...yarpc.CallOption, 620 ) (success *m.RespondDecisionTaskCompletedResponse, err error) { 621 respondCounter++ 622 switch respondCounter { 623 case 1: 624 s.Equal(3, len(request.Decisions)) 625 s.Equal(m.DecisionTypeRecordMarker, request.Decisions[0].GetDecisionType()) 626 *task.PreviousStartedEventId = 3 627 *task.StartedEventId = 7 628 task.History.Events = testEvents[3:11] 629 close(doneCh) 630 return nil, nil 631 default: 632 panic("unexpected RespondDecisionTaskCompleted") 633 } 634 }).Times(1) 635 636 options := WorkerOptions{ 637 Logger: zaptest.NewLogger(s.T()), 638 DisableActivityWorker: true, 639 Identity: "test-worker-identity", 640 } 641 worker := newAggregatedWorker(s.service, domain, taskList, options) 642 worker.RegisterWorkflowWithOptions( 643 longDecisionWorkflowFn, 644 RegisterWorkflowOptions{Name: "multiple-local-activities-workflow-type"}, 645 ) 646 worker.RegisterActivity(localActivitySleep) 647 648 startWorkerAndWait(s, worker, &doneCh) 649 650 s.True(isWorkflowCompleted) 651 s.Equal(2, localActivityCalledCount) 652 } 653 654 func (s *WorkersTestSuite) createLocalActivityMarkerDataForTest(activityID string) []byte { 655 lamd := localActivityMarkerData{ 656 ActivityID: activityID, 657 ReplayTime: time.Now(), 658 } 659 660 // encode marker data 661 markerData, err := encodeArg(nil, lamd) 662 s.NoError(err) 663 return markerData 664 } 665 666 func (s *WorkersTestSuite) TestLocallyDispatchedActivity() { 667 activityCalledCount := atomic.NewInt32(0) // must be accessed with atomics, worker uses goroutines to run activities 668 activitySleep := func(duration time.Duration) error { 669 time.Sleep(duration) 670 activityCalledCount.Add(1) 671 return nil 672 } 673 674 doneCh := make(chan struct{}) 675 676 workflowFn := func(ctx Context, input []byte) error { 677 ao := ActivityOptions{ 678 ScheduleToCloseTimeout: 1 * time.Second, 679 ScheduleToStartTimeout: 1 * time.Second, 680 StartToCloseTimeout: 1 * time.Second, 681 } 682 ctx = WithActivityOptions(ctx, ao) 683 err := ExecuteActivity(ctx, activitySleep, 500*time.Millisecond).Get(ctx, nil) 684 return err 685 } 686 687 domain := "testDomain" 688 workflowType := "locally-dispatched-activity-workflow-type" 689 taskList := "locally-dispatched-activity-tl" 690 testEvents := []*m.HistoryEvent{ 691 { 692 EventId: common.Int64Ptr(1), 693 EventType: common.EventTypePtr(m.EventTypeWorkflowExecutionStarted), 694 WorkflowExecutionStartedEventAttributes: &m.WorkflowExecutionStartedEventAttributes{ 695 TaskList: &m.TaskList{Name: &taskList}, 696 ExecutionStartToCloseTimeoutSeconds: common.Int32Ptr(10), 697 TaskStartToCloseTimeoutSeconds: common.Int32Ptr(2), 698 WorkflowType: &m.WorkflowType{Name: common.StringPtr(workflowType)}, 699 }, 700 }, 701 createTestEventDecisionTaskScheduled(2, &m.DecisionTaskScheduledEventAttributes{TaskList: &m.TaskList{Name: &taskList}}), 702 createTestEventDecisionTaskStarted(3), 703 } 704 705 s.service.EXPECT().DescribeDomain(gomock.Any(), gomock.Any(), callOptions()...).Return(nil, nil).AnyTimes() 706 task := &m.PollForDecisionTaskResponse{ 707 TaskToken: []byte("test-token"), 708 WorkflowExecution: &m.WorkflowExecution{ 709 WorkflowId: common.StringPtr("locally-dispatched-activity-workflow-id"), 710 RunId: common.StringPtr("locally-dispatched-activity-workflow-run-id"), 711 }, 712 WorkflowType: &m.WorkflowType{ 713 Name: common.StringPtr(workflowType), 714 }, 715 PreviousStartedEventId: common.Int64Ptr(0), 716 StartedEventId: common.Int64Ptr(3), 717 History: &m.History{Events: testEvents[0:3]}, 718 NextPageToken: nil, 719 NextEventId: common.Int64Ptr(4), 720 } 721 s.service.EXPECT().PollForDecisionTask(gomock.Any(), gomock.Any(), callOptions()...).Return(task, nil).Times(1) 722 s.service.EXPECT().PollForDecisionTask(gomock.Any(), gomock.Any(), callOptions()...).Return(&m.PollForDecisionTaskResponse{}, &m.InternalServiceError{}).AnyTimes() 723 s.service.EXPECT().PollForActivityTask(gomock.Any(), gomock.Any(), callOptions()...).Return(nil, nil).AnyTimes() 724 s.service.EXPECT().RespondDecisionTaskCompleted(gomock.Any(), gomock.Any(), callOptions()...).DoAndReturn(func(ctx context.Context, request *m.RespondDecisionTaskCompletedRequest, opts ...yarpc.CallOption, 725 ) (success *m.RespondDecisionTaskCompletedResponse, err error) { 726 s.Equal(1, len(request.Decisions)) 727 activitiesToDispatchLocally := make(map[string]*m.ActivityLocalDispatchInfo) 728 d := request.Decisions[0] 729 s.Equal(m.DecisionTypeScheduleActivityTask, d.GetDecisionType()) 730 activitiesToDispatchLocally[*d.ScheduleActivityTaskDecisionAttributes.ActivityId] = 731 &m.ActivityLocalDispatchInfo{ 732 ActivityId: d.ScheduleActivityTaskDecisionAttributes.ActivityId, 733 ScheduledTimestamp: common.Int64Ptr(time.Now().UnixNano()), 734 ScheduledTimestampOfThisAttempt: common.Int64Ptr(time.Now().UnixNano()), 735 StartedTimestamp: common.Int64Ptr(time.Now().UnixNano()), 736 TaskToken: []byte("test-token")} 737 return &m.RespondDecisionTaskCompletedResponse{ActivitiesToDispatchLocally: activitiesToDispatchLocally}, nil 738 }).Times(1) 739 isActivityResponseCompleted := atomic.NewBool(false) 740 s.service.EXPECT().RespondActivityTaskCompleted(gomock.Any(), gomock.Any(), callOptions()...).DoAndReturn(func(ctx context.Context, request *m.RespondActivityTaskCompletedRequest, opts ...yarpc.CallOption, 741 ) error { 742 defer close(doneCh) 743 isActivityResponseCompleted.Swap(true) 744 return nil 745 }).Times(1) 746 747 options := WorkerOptions{ 748 Logger: zaptest.NewLogger(s.T()), 749 Identity: "test-worker-identity", 750 } 751 worker := newAggregatedWorker(s.service, domain, taskList, options) 752 worker.RegisterWorkflowWithOptions( 753 workflowFn, 754 RegisterWorkflowOptions{Name: workflowType}, 755 ) 756 worker.RegisterActivityWithOptions(activitySleep, RegisterActivityOptions{Name: "activitySleep"}) 757 758 startWorkerAndWait(s, worker, &doneCh) 759 760 s.True(isActivityResponseCompleted.Load()) 761 s.Equal(int32(1), activityCalledCount.Load()) 762 } 763 764 func (s *WorkersTestSuite) TestMultipleLocallyDispatchedActivity() { 765 activityCalledCount := atomic.NewInt32(0) 766 activitySleep := func(duration time.Duration) error { 767 time.Sleep(duration) 768 activityCalledCount.Add(1) 769 return nil 770 } 771 772 doneCh := make(chan struct{}) 773 774 var activityCount int32 = 5 775 workflowFn := func(ctx Context, input []byte) error { 776 ao := ActivityOptions{ 777 ScheduleToCloseTimeout: 1 * time.Second, 778 ScheduleToStartTimeout: 1 * time.Second, 779 StartToCloseTimeout: 1 * time.Second, 780 } 781 ctx = WithActivityOptions(ctx, ao) 782 783 // start all activities in parallel, and wait for them all to complete. 784 var all []Future 785 for i := 0; i < int(activityCount); i++ { 786 all = append(all, ExecuteActivity(ctx, activitySleep, 500*time.Millisecond)) 787 } 788 for i, f := range all { 789 s.NoError(f.Get(ctx, nil), "activity %v should not have failed", i) 790 } 791 return nil 792 } 793 794 domain := "testDomain" 795 workflowType := "locally-dispatched-multiple-activity-workflow-type" 796 taskList := "locally-dispatched-multiple-activity-tl" 797 testEvents := []*m.HistoryEvent{ 798 { 799 EventId: common.Int64Ptr(1), 800 EventType: common.EventTypePtr(m.EventTypeWorkflowExecutionStarted), 801 WorkflowExecutionStartedEventAttributes: &m.WorkflowExecutionStartedEventAttributes{ 802 TaskList: &m.TaskList{Name: &taskList}, 803 ExecutionStartToCloseTimeoutSeconds: common.Int32Ptr(10), 804 TaskStartToCloseTimeoutSeconds: common.Int32Ptr(2), 805 WorkflowType: &m.WorkflowType{Name: common.StringPtr(workflowType)}, 806 }, 807 }, 808 createTestEventDecisionTaskScheduled(2, &m.DecisionTaskScheduledEventAttributes{TaskList: &m.TaskList{Name: &taskList}}), 809 createTestEventDecisionTaskStarted(3), 810 } 811 812 options := WorkerOptions{ 813 Logger: zaptest.NewLogger(s.T()), 814 Identity: "test-worker-identity", 815 } 816 817 s.service.EXPECT().DescribeDomain(gomock.Any(), gomock.Any(), callOptions()...).Return(nil, nil).AnyTimes() 818 task := &m.PollForDecisionTaskResponse{ 819 TaskToken: []byte("test-token"), 820 WorkflowExecution: &m.WorkflowExecution{ 821 WorkflowId: common.StringPtr("locally-dispatched-multiple-activity-workflow-id"), 822 RunId: common.StringPtr("locally-dispatched-multiple-activity-workflow-run-id"), 823 }, 824 WorkflowType: &m.WorkflowType{ 825 Name: common.StringPtr(workflowType), 826 }, 827 PreviousStartedEventId: common.Int64Ptr(0), 828 StartedEventId: common.Int64Ptr(3), 829 History: &m.History{Events: testEvents[0:3]}, 830 NextPageToken: nil, 831 NextEventId: common.Int64Ptr(4), 832 } 833 s.service.EXPECT().PollForDecisionTask(gomock.Any(), gomock.Any(), callOptions()...).Return(task, nil).Times(1) 834 s.service.EXPECT().PollForDecisionTask(gomock.Any(), gomock.Any(), callOptions()...).Return(&m.PollForDecisionTaskResponse{}, &m.InternalServiceError{}).AnyTimes() 835 s.service.EXPECT().PollForActivityTask(gomock.Any(), gomock.Any(), callOptions()...).Return(nil, nil).AnyTimes() 836 s.service.EXPECT().RespondDecisionTaskCompleted(gomock.Any(), gomock.Any(), callOptions()...).DoAndReturn(func(ctx context.Context, request *m.RespondDecisionTaskCompletedRequest, opts ...yarpc.CallOption, 837 ) (success *m.RespondDecisionTaskCompletedResponse, err error) { 838 s.Equal(int(activityCount), len(request.Decisions)) 839 activitiesToDispatchLocally := make(map[string]*m.ActivityLocalDispatchInfo) 840 for _, d := range request.Decisions { 841 s.Equal(m.DecisionTypeScheduleActivityTask, d.GetDecisionType()) 842 activitiesToDispatchLocally[*d.ScheduleActivityTaskDecisionAttributes.ActivityId] = 843 &m.ActivityLocalDispatchInfo{ 844 ActivityId: d.ScheduleActivityTaskDecisionAttributes.ActivityId, 845 ScheduledTimestamp: common.Int64Ptr(time.Now().UnixNano()), 846 ScheduledTimestampOfThisAttempt: common.Int64Ptr(time.Now().UnixNano()), 847 StartedTimestamp: common.Int64Ptr(time.Now().UnixNano()), 848 TaskToken: []byte("test-token")} 849 } 850 return &m.RespondDecisionTaskCompletedResponse{ActivitiesToDispatchLocally: activitiesToDispatchLocally}, nil 851 }).Times(1) 852 activityResponseCompletedCount := atomic.NewInt32(0) 853 s.service.EXPECT().RespondActivityTaskCompleted(gomock.Any(), gomock.Any(), callOptions()...).DoAndReturn(func(ctx context.Context, request *m.RespondActivityTaskCompletedRequest, opts ...yarpc.CallOption, 854 ) error { 855 counted := activityResponseCompletedCount.Add(1) 856 if counted == activityCount { 857 close(doneCh) 858 } 859 return nil 860 }).MinTimes(1) 861 862 worker := newAggregatedWorker(s.service, domain, taskList, options) 863 worker.RegisterWorkflowWithOptions( 864 workflowFn, 865 RegisterWorkflowOptions{Name: workflowType}, 866 ) 867 worker.RegisterActivityWithOptions(activitySleep, RegisterActivityOptions{Name: "activitySleep"}) 868 s.NotNil(worker.locallyDispatchedActivityWorker) 869 err := worker.Start() 870 s.NoError(err, "worker failed to start") 871 872 // wait for test to complete 873 // This test currently never completes, however after the timeout the asserts are true 874 // so the test passes, I believe this is an error. 875 select { 876 case <-doneCh: 877 s.T().Log("completed") 878 case <-time.After(1 * time.Second): 879 s.T().Log("timed out") 880 } 881 worker.Stop() 882 883 // for currently unbuffered channel at least one activity should be sent 884 s.True(activityResponseCompletedCount.Load() > 0) 885 s.True(activityCalledCount.Load() > 0) 886 } 887 888 // wait for test to complete - timeout and fail after 10 seconds to not block execution of other tests 889 func startWorkerAndWait(s *WorkersTestSuite, worker *aggregatedWorker, doneCh *chan struct{}) { 890 s.T().Helper() 891 err := worker.Start() 892 s.NoError(err, "worker failed to start") 893 // wait for test to complete 894 select { 895 case <-*doneCh: 896 case <-time.After(10 * time.Second): 897 s.Fail("Test timed out") 898 } 899 worker.Stop() 900 }