go.uber.org/cadence@v1.2.9/internal/internal_workflow_client.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 "time" 31 32 "go.uber.org/cadence/internal/common/serializer" 33 34 "github.com/opentracing/opentracing-go" 35 "github.com/pborman/uuid" 36 "github.com/uber-go/tally" 37 38 "go.uber.org/cadence/.gen/go/cadence/workflowserviceclient" 39 s "go.uber.org/cadence/.gen/go/shared" 40 "go.uber.org/cadence/internal/common" 41 "go.uber.org/cadence/internal/common/backoff" 42 "go.uber.org/cadence/internal/common/metrics" 43 ) 44 45 // Assert that structs do indeed implement the interfaces 46 var _ Client = (*workflowClient)(nil) 47 var _ DomainClient = (*domainClient)(nil) 48 49 const ( 50 defaultDecisionTaskTimeoutInSecs = 10 51 defaultGetHistoryTimeoutInSecs = 25 52 ) 53 54 var ( 55 maxListArchivedWorkflowTimeout = time.Minute * 3 56 ) 57 58 type ( 59 // workflowClient is the client for starting a workflow execution. 60 workflowClient struct { 61 workflowService workflowserviceclient.Interface 62 domain string 63 registry *registry 64 metricsScope *metrics.TaggedScope 65 identity string 66 dataConverter DataConverter 67 contextPropagators []ContextPropagator 68 tracer opentracing.Tracer 69 featureFlags FeatureFlags 70 } 71 72 // domainClient is the client for managing domains. 73 domainClient struct { 74 workflowService workflowserviceclient.Interface 75 metricsScope tally.Scope 76 identity string 77 featureFlags FeatureFlags 78 } 79 80 // WorkflowRun represents a started non child workflow 81 WorkflowRun interface { 82 // GetID return workflow ID, which will be same as StartWorkflowOptions.ID if provided. 83 GetID() string 84 85 // GetRunID return the first started workflow run ID (please see below) 86 GetRunID() string 87 88 // Get will fill the workflow execution result to valuePtr, 89 // if workflow execution is a success, or return corresponding, 90 // error. This is a blocking API. 91 Get(ctx context.Context, valuePtr interface{}) error 92 93 // NOTE: if the started workflow return ContinueAsNewError during the workflow execution, the 94 // return result of GetRunID() will be the started workflow run ID, not the new run ID caused by ContinueAsNewError, 95 // however, Get(ctx context.Context, valuePtr interface{}) will return result from the run which did not return ContinueAsNewError. 96 // Say ExecuteWorkflow started a workflow, in its first run, has run ID "run ID 1", and returned ContinueAsNewError, 97 // the second run has run ID "run ID 2" and return some result other than ContinueAsNewError: 98 // GetRunID() will always return "run ID 1" and Get(ctx context.Context, valuePtr interface{}) will return the result of second run. 99 // NOTE: DO NOT USE client.ExecuteWorkflow API INSIDE A WORKFLOW, USE workflow.ExecuteChildWorkflow instead 100 } 101 102 // workflowRunImpl is an implementation of WorkflowRun 103 workflowRunImpl struct { 104 workflowFn interface{} 105 workflowID string 106 firstRunID string 107 currentRunID string 108 iterFn func(ctx context.Context, runID string) HistoryEventIterator 109 dataConverter DataConverter 110 registry *registry 111 } 112 113 // HistoryEventIterator represents the interface for 114 // history event iterator 115 HistoryEventIterator interface { 116 // HasNext return whether this iterator has next value 117 HasNext() bool 118 // Next returns the next history events and error 119 // The errors it can return: 120 // - EntityNotExistsError 121 // - BadRequestError 122 // - InternalServiceError 123 Next() (*s.HistoryEvent, error) 124 } 125 126 // historyEventIteratorImpl is the implementation of HistoryEventIterator 127 historyEventIteratorImpl struct { 128 // whether this iterator is initialized 129 initialized bool 130 // local cached history events and corresponding consuming index 131 nextEventIndex int 132 events []*s.HistoryEvent 133 // token to get next page of history events 134 nexttoken []byte 135 // err when getting next page of history events 136 err error 137 // func which use a next token to get next page of history events 138 paginate func(nexttoken []byte) (*s.GetWorkflowExecutionHistoryResponse, error) 139 } 140 ) 141 142 // StartWorkflow starts a workflow execution 143 // The user can use this to start using a functor like. 144 // Either by 145 // 146 // StartWorkflow(options, "workflowTypeName", arg1, arg2, arg3) 147 // or 148 // StartWorkflow(options, workflowExecuteFn, arg1, arg2, arg3) 149 // 150 // The current timeout resolution implementation is in seconds and uses math.Ceil(d.Seconds()) as the duration. But is 151 // subjected to change in the future. 152 func (wc *workflowClient) StartWorkflow( 153 ctx context.Context, 154 options StartWorkflowOptions, 155 workflowFunc interface{}, 156 args ...interface{}, 157 ) (*WorkflowExecution, error) { 158 workflowID := options.ID 159 if len(workflowID) == 0 { 160 workflowID = uuid.NewRandom().String() 161 } 162 163 if options.TaskList == "" { 164 return nil, errors.New("missing TaskList") 165 } 166 167 executionTimeout := common.Int32Ceil(options.ExecutionStartToCloseTimeout.Seconds()) 168 if executionTimeout <= 0 { 169 return nil, errors.New("missing or invalid ExecutionStartToCloseTimeout") 170 } 171 172 decisionTaskTimeout := common.Int32Ceil(options.DecisionTaskStartToCloseTimeout.Seconds()) 173 if decisionTaskTimeout < 0 { 174 return nil, errors.New("negative DecisionTaskStartToCloseTimeout provided") 175 } 176 if decisionTaskTimeout == 0 { 177 decisionTaskTimeout = defaultDecisionTaskTimeoutInSecs 178 } 179 180 // Validate type and its arguments. 181 workflowType, input, err := getValidatedWorkflowFunction(workflowFunc, args, wc.dataConverter, wc.registry) 182 if err != nil { 183 return nil, err 184 } 185 186 memo, err := getWorkflowMemo(options.Memo, wc.dataConverter) 187 if err != nil { 188 return nil, err 189 } 190 191 searchAttr, err := serializeSearchAttributes(options.SearchAttributes) 192 if err != nil { 193 return nil, err 194 } 195 196 delayStartSeconds := common.Int32Ceil(options.DelayStart.Seconds()) 197 if delayStartSeconds < 0 { 198 return nil, errors.New("Invalid DelayStart option") 199 } 200 201 jitterStartSeconds := common.Int32Ceil(options.JitterStart.Seconds()) 202 if jitterStartSeconds < 0 { 203 return nil, errors.New("Invalid JitterStart option") 204 } 205 206 // create a workflow start span and attach it to the context object. 207 // N.B. we need to finish this immediately as jaeger does not give us a way 208 // to recreate a span given a span context - which means we will run into 209 // issues during replay. we work around this by creating and ending the 210 // workflow start span and passing in that context to the workflow. So 211 // everything beginning with the StartWorkflowExecutionRequest will be 212 // parented by the created start workflow span. 213 ctx, span := createOpenTracingWorkflowSpan(ctx, wc.tracer, time.Now(), fmt.Sprintf("StartWorkflow-%s", workflowType.Name), workflowID) 214 span.Finish() 215 216 // get workflow headers from the context 217 header := wc.getWorkflowHeader(ctx) 218 219 // run propagators to extract information about tracing and other stuff, store in headers field 220 startRequest := &s.StartWorkflowExecutionRequest{ 221 Domain: common.StringPtr(wc.domain), 222 RequestId: common.StringPtr(uuid.New()), 223 WorkflowId: common.StringPtr(workflowID), 224 WorkflowType: workflowTypePtr(*workflowType), 225 TaskList: common.TaskListPtr(s.TaskList{Name: common.StringPtr(options.TaskList)}), 226 Input: input, 227 ExecutionStartToCloseTimeoutSeconds: common.Int32Ptr(executionTimeout), 228 TaskStartToCloseTimeoutSeconds: common.Int32Ptr(decisionTaskTimeout), 229 Identity: common.StringPtr(wc.identity), 230 WorkflowIdReusePolicy: options.WorkflowIDReusePolicy.toThriftPtr(), 231 RetryPolicy: convertRetryPolicy(options.RetryPolicy), 232 CronSchedule: common.StringPtr(options.CronSchedule), 233 Memo: memo, 234 SearchAttributes: searchAttr, 235 Header: header, 236 DelayStartSeconds: common.Int32Ptr(delayStartSeconds), 237 JitterStartSeconds: common.Int32Ptr(jitterStartSeconds), 238 } 239 240 var response *s.StartWorkflowExecutionResponse 241 242 // Start creating workflow request. 243 err = backoff.Retry(ctx, 244 func() error { 245 tchCtx, cancel, opt := newChannelContext(ctx, wc.featureFlags) 246 defer cancel() 247 248 var err1 error 249 response, err1 = wc.workflowService.StartWorkflowExecution(tchCtx, startRequest, opt...) 250 return err1 251 }, createDynamicServiceRetryPolicy(ctx), isServiceTransientError) 252 253 if err != nil { 254 return nil, err 255 } 256 257 if wc.metricsScope != nil { 258 scope := wc.metricsScope.GetTaggedScope(tagTaskList, options.TaskList, tagWorkflowType, workflowType.Name) 259 scope.Counter(metrics.WorkflowStartCounter).Inc(1) 260 } 261 262 executionInfo := &WorkflowExecution{ 263 ID: workflowID, 264 RunID: response.GetRunId()} 265 return executionInfo, nil 266 } 267 268 // ExecuteWorkflow starts a workflow execution and returns a WorkflowRun that will allow you to wait until this workflow 269 // reaches the end state, such as workflow finished successfully or timeout. 270 // The user can use this to start using a functor like below and get the workflow execution result, as Value 271 // Either by 272 // 273 // ExecuteWorkflow(options, "workflowTypeName", arg1, arg2, arg3) 274 // or 275 // ExecuteWorkflow(options, workflowExecuteFn, arg1, arg2, arg3) 276 // 277 // The current timeout resolution implementation is in seconds and uses math.Ceil(d.Seconds()) as the duration. But is 278 // subjected to change in the future. 279 // NOTE: the context.Context should have a fairly large timeout, since workflow execution may take a while to be finished 280 func (wc *workflowClient) ExecuteWorkflow(ctx context.Context, options StartWorkflowOptions, workflow interface{}, args ...interface{}) (WorkflowRun, error) { 281 282 // start the workflow execution 283 var runID string 284 var workflowID string 285 executionInfo, err := wc.StartWorkflow(ctx, options, workflow, args...) 286 if err != nil { 287 if alreadyStartedErr, ok := err.(*s.WorkflowExecutionAlreadyStartedError); ok { 288 runID = alreadyStartedErr.GetRunId() 289 // Assumption is that AlreadyStarted is never returned when options.ID is empty as UUID generated by 290 // StartWorkflow is not going to collide ever. 291 workflowID = options.ID 292 } else { 293 return nil, err 294 } 295 } else { 296 runID = executionInfo.RunID 297 workflowID = executionInfo.ID 298 } 299 300 iterFn := func(fnCtx context.Context, fnRunID string) HistoryEventIterator { 301 return wc.GetWorkflowHistory(fnCtx, workflowID, fnRunID, true, s.HistoryEventFilterTypeCloseEvent) 302 } 303 304 return &workflowRunImpl{ 305 workflowFn: workflow, 306 workflowID: workflowID, 307 firstRunID: runID, 308 currentRunID: runID, 309 iterFn: iterFn, 310 dataConverter: wc.dataConverter, 311 registry: wc.registry, 312 }, nil 313 } 314 315 // GetWorkflow gets a workflow execution and returns a WorkflowRun that will allow you to wait until this workflow 316 // reaches the end state, such as workflow finished successfully or timeout. 317 // The current timeout resolution implementation is in seconds and uses math.Ceil(d.Seconds()) as the duration. But is 318 // subjected to change in the future. 319 func (wc *workflowClient) GetWorkflow(ctx context.Context, workflowID string, runID string) WorkflowRun { 320 321 iterFn := func(fnCtx context.Context, fnRunID string) HistoryEventIterator { 322 return wc.GetWorkflowHistory(fnCtx, workflowID, fnRunID, true, s.HistoryEventFilterTypeCloseEvent) 323 } 324 325 return &workflowRunImpl{ 326 workflowID: workflowID, 327 firstRunID: runID, 328 currentRunID: runID, 329 iterFn: iterFn, 330 dataConverter: wc.dataConverter, 331 registry: wc.registry, 332 } 333 } 334 335 // SignalWorkflow signals a workflow in execution. 336 func (wc *workflowClient) SignalWorkflow(ctx context.Context, workflowID string, runID string, signalName string, arg interface{}) error { 337 input, err := encodeArg(wc.dataConverter, arg) 338 if err != nil { 339 return err 340 } 341 return signalWorkflow(ctx, wc.workflowService, wc.identity, wc.domain, workflowID, runID, signalName, input, wc.featureFlags) 342 } 343 344 // SignalWithStartWorkflow sends a signal to a running workflow. 345 // If the workflow is not running or not found, it starts the workflow and then sends the signal in transaction. 346 func (wc *workflowClient) SignalWithStartWorkflow(ctx context.Context, workflowID string, signalName string, signalArg interface{}, 347 options StartWorkflowOptions, workflowFunc interface{}, workflowArgs ...interface{}) (*WorkflowExecution, error) { 348 349 signalInput, err := encodeArg(wc.dataConverter, signalArg) 350 if err != nil { 351 return nil, err 352 } 353 354 if workflowID == "" { 355 workflowID = uuid.NewRandom().String() 356 } 357 358 if options.TaskList == "" { 359 return nil, errors.New("missing TaskList") 360 } 361 362 executionTimeout := common.Int32Ceil(options.ExecutionStartToCloseTimeout.Seconds()) 363 if executionTimeout <= 0 { 364 return nil, errors.New("missing or invalid ExecutionStartToCloseTimeout") 365 } 366 367 decisionTaskTimeout := common.Int32Ceil(options.DecisionTaskStartToCloseTimeout.Seconds()) 368 if decisionTaskTimeout < 0 { 369 return nil, errors.New("negative DecisionTaskStartToCloseTimeout provided") 370 } 371 if decisionTaskTimeout == 0 { 372 decisionTaskTimeout = defaultDecisionTaskTimeoutInSecs 373 } 374 375 // Validate type and its arguments. 376 workflowType, input, err := getValidatedWorkflowFunction(workflowFunc, workflowArgs, wc.dataConverter, wc.registry) 377 if err != nil { 378 return nil, err 379 } 380 381 memo, err := getWorkflowMemo(options.Memo, wc.dataConverter) 382 if err != nil { 383 return nil, err 384 } 385 386 searchAttr, err := serializeSearchAttributes(options.SearchAttributes) 387 if err != nil { 388 return nil, err 389 } 390 391 delayStartSeconds := common.Int32Ceil(options.DelayStart.Seconds()) 392 if delayStartSeconds < 0 { 393 return nil, errors.New("Invalid DelayStart option") 394 } 395 396 jitterStartSeconds := common.Int32Ceil(options.JitterStart.Seconds()) 397 if jitterStartSeconds < 0 { 398 return nil, errors.New("Invalid JitterStart option") 399 } 400 401 // create a workflow start span and attach it to the context object. finish it immediately 402 ctx, span := createOpenTracingWorkflowSpan(ctx, wc.tracer, time.Now(), fmt.Sprintf("SignalWithStartWorkflow-%s", workflowType.Name), workflowID) 403 span.Finish() 404 405 // get workflow headers from the context 406 header := wc.getWorkflowHeader(ctx) 407 408 signalWithStartRequest := &s.SignalWithStartWorkflowExecutionRequest{ 409 Domain: common.StringPtr(wc.domain), 410 RequestId: common.StringPtr(uuid.New()), 411 WorkflowId: common.StringPtr(workflowID), 412 WorkflowType: workflowTypePtr(*workflowType), 413 TaskList: common.TaskListPtr(s.TaskList{Name: common.StringPtr(options.TaskList)}), 414 Input: input, 415 ExecutionStartToCloseTimeoutSeconds: common.Int32Ptr(executionTimeout), 416 TaskStartToCloseTimeoutSeconds: common.Int32Ptr(decisionTaskTimeout), 417 SignalName: common.StringPtr(signalName), 418 SignalInput: signalInput, 419 Identity: common.StringPtr(wc.identity), 420 RetryPolicy: convertRetryPolicy(options.RetryPolicy), 421 CronSchedule: common.StringPtr(options.CronSchedule), 422 Memo: memo, 423 SearchAttributes: searchAttr, 424 WorkflowIdReusePolicy: options.WorkflowIDReusePolicy.toThriftPtr(), 425 Header: header, 426 DelayStartSeconds: common.Int32Ptr(delayStartSeconds), 427 JitterStartSeconds: common.Int32Ptr(jitterStartSeconds), 428 } 429 430 var response *s.StartWorkflowExecutionResponse 431 432 // Start creating workflow request. 433 err = backoff.Retry(ctx, 434 func() error { 435 tchCtx, cancel, opt := newChannelContext(ctx, wc.featureFlags) 436 defer cancel() 437 438 var err1 error 439 response, err1 = wc.workflowService.SignalWithStartWorkflowExecution(tchCtx, signalWithStartRequest, opt...) 440 return err1 441 }, createDynamicServiceRetryPolicy(ctx), isServiceTransientError) 442 443 if err != nil { 444 return nil, err 445 } 446 447 if wc.metricsScope != nil { 448 scope := wc.metricsScope.GetTaggedScope(tagTaskList, options.TaskList, tagWorkflowType, workflowType.Name) 449 scope.Counter(metrics.WorkflowSignalWithStartCounter).Inc(1) 450 } 451 452 executionInfo := &WorkflowExecution{ 453 ID: options.ID, 454 RunID: response.GetRunId()} 455 return executionInfo, nil 456 } 457 458 // CancelWorkflow cancels a workflow in execution. It allows workflow to properly clean up and gracefully close. 459 // workflowID is required, other parameters are optional. 460 // If runID is omit, it will terminate currently running workflow (if there is one) based on the workflowID. 461 func (wc *workflowClient) CancelWorkflow(ctx context.Context, workflowID string, runID string, opts ...Option) error { 462 request := &s.RequestCancelWorkflowExecutionRequest{ 463 Domain: common.StringPtr(wc.domain), 464 WorkflowExecution: &s.WorkflowExecution{ 465 WorkflowId: common.StringPtr(workflowID), 466 RunId: getRunID(runID), 467 }, 468 Identity: common.StringPtr(wc.identity), 469 } 470 471 for _, opt := range opts { 472 switch o := opt.(type) { 473 case CancelReason: 474 cause := string(o) 475 request.Cause = &cause 476 } 477 } 478 479 return backoff.Retry(ctx, 480 func() error { 481 tchCtx, cancel, opt := newChannelContext(ctx, wc.featureFlags) 482 defer cancel() 483 return wc.workflowService.RequestCancelWorkflowExecution(tchCtx, request, opt...) 484 }, createDynamicServiceRetryPolicy(ctx), isServiceTransientError) 485 } 486 487 // TerminateWorkflow terminates a workflow execution. 488 // workflowID is required, other parameters are optional. 489 // If runID is omit, it will terminate currently running workflow (if there is one) based on the workflowID. 490 func (wc *workflowClient) TerminateWorkflow(ctx context.Context, workflowID string, runID string, reason string, details []byte) error { 491 request := &s.TerminateWorkflowExecutionRequest{ 492 Domain: common.StringPtr(wc.domain), 493 WorkflowExecution: &s.WorkflowExecution{ 494 WorkflowId: common.StringPtr(workflowID), 495 RunId: getRunID(runID), 496 }, 497 Reason: common.StringPtr(reason), 498 Details: details, 499 Identity: common.StringPtr(wc.identity), 500 } 501 502 err := backoff.Retry(ctx, 503 func() error { 504 tchCtx, cancel, opt := newChannelContext(ctx, wc.featureFlags) 505 defer cancel() 506 return wc.workflowService.TerminateWorkflowExecution(tchCtx, request, opt...) 507 }, createDynamicServiceRetryPolicy(ctx), isServiceTransientError) 508 509 return err 510 } 511 512 // GetWorkflowHistory return a channel which contains the history events of a given workflow 513 func (wc *workflowClient) GetWorkflowHistory( 514 ctx context.Context, 515 workflowID string, 516 runID string, 517 isLongPoll bool, 518 filterType s.HistoryEventFilterType, 519 ) HistoryEventIterator { 520 521 domain := wc.domain 522 paginate := func(nextToken []byte) (*s.GetWorkflowExecutionHistoryResponse, error) { 523 request := &s.GetWorkflowExecutionHistoryRequest{ 524 Domain: common.StringPtr(domain), 525 Execution: &s.WorkflowExecution{ 526 WorkflowId: common.StringPtr(workflowID), 527 RunId: getRunID(runID), 528 }, 529 WaitForNewEvent: common.BoolPtr(isLongPoll), 530 HistoryEventFilterType: &filterType, 531 NextPageToken: nextToken, 532 SkipArchival: common.BoolPtr(isLongPoll), 533 } 534 535 var response *s.GetWorkflowExecutionHistoryResponse 536 var err error 537 Loop: 538 for { 539 var isFinalLongPoll bool 540 err = backoff.Retry(ctx, 541 func() error { 542 var err1 error 543 tchCtx, cancel, opt := newChannelContext(ctx, wc.featureFlags, func(builder *contextBuilder) { 544 if isLongPoll { 545 builder.Timeout = defaultGetHistoryTimeoutInSecs * time.Second 546 deadline, ok := ctx.Deadline() 547 if ok && deadline.Before(time.Now().Add(builder.Timeout)) { 548 // insufficient time for another poll, so this needs to be the last attempt 549 isFinalLongPoll = true 550 } 551 } 552 }) 553 defer cancel() 554 response, err1 = wc.workflowService.GetWorkflowExecutionHistory(tchCtx, request, opt...) 555 556 if err1 != nil { 557 return err1 558 } 559 560 if response.RawHistory != nil { 561 history, err := serializer.DeserializeBlobDataToHistoryEvents(response.RawHistory, filterType) 562 if err != nil { 563 return err 564 } 565 response.History = history 566 } 567 return err1 568 }, 569 createDynamicServiceRetryPolicy(ctx), 570 func(err error) bool { 571 return isServiceTransientError(err) || isEntityNonExistFromPassive(err) 572 }, 573 ) 574 575 if err != nil { 576 return nil, err 577 } 578 if isLongPoll && len(response.History.Events) == 0 && len(response.NextPageToken) != 0 { 579 if isFinalLongPoll { 580 // essentially a deadline exceeded, the last attempt did not get a result. 581 // this is necessary because the server does not know if we are able to try again, 582 // so it returns an empty result slightly before a timeout occurs, so the next 583 // attempt's token can be returned if it wishes to retry. 584 return nil, fmt.Errorf("timed out waiting for the workflow to finish: %w", context.DeadlineExceeded) 585 } 586 request.NextPageToken = response.NextPageToken 587 continue Loop 588 } 589 break Loop 590 } 591 return response, nil 592 } 593 594 return &historyEventIteratorImpl{ 595 paginate: paginate, 596 } 597 } 598 599 func isEntityNonExistFromPassive(err error) bool { 600 if nonExistError, ok := err.(*s.EntityNotExistsError); ok { 601 return nonExistError.GetActiveCluster() != "" && 602 nonExistError.GetCurrentCluster() != "" && 603 nonExistError.GetActiveCluster() != nonExistError.GetCurrentCluster() 604 } 605 606 return false 607 } 608 609 // CompleteActivity reports activity completed. activity Execute method can return activity.ErrResultPending to 610 // indicate the activity is not completed when it's Execute method returns. In that case, this CompleteActivity() method 611 // should be called when that activity is completed with the actual result and error. If err is nil, activity task 612 // completed event will be reported; if err is CanceledError, activity task cancelled event will be reported; otherwise, 613 // activity task failed event will be reported. 614 func (wc *workflowClient) CompleteActivity(ctx context.Context, taskToken []byte, result interface{}, err error) error { 615 if taskToken == nil { 616 return errors.New("invalid task token provided") 617 } 618 619 var data []byte 620 if result != nil { 621 var err0 error 622 data, err0 = encodeArg(wc.dataConverter, result) 623 if err0 != nil { 624 return err0 625 } 626 } 627 request := convertActivityResultToRespondRequest(wc.identity, taskToken, data, err, wc.dataConverter) 628 return reportActivityComplete(ctx, wc.workflowService, request, wc.metricsScope, wc.featureFlags) 629 } 630 631 // CompleteActivityById reports activity completed. Similar to CompleteActivity 632 // It takes domain name, workflowID, runID, activityID as arguments. 633 func (wc *workflowClient) CompleteActivityByID(ctx context.Context, domain, workflowID, runID, activityID string, 634 result interface{}, err error) error { 635 636 if activityID == "" || workflowID == "" || domain == "" { 637 return errors.New("empty activity or workflow id or domainName") 638 } 639 640 var data []byte 641 if result != nil { 642 var err0 error 643 data, err0 = encodeArg(wc.dataConverter, result) 644 if err0 != nil { 645 return err0 646 } 647 } 648 649 request := convertActivityResultToRespondRequestByID(wc.identity, domain, workflowID, runID, activityID, data, err, wc.dataConverter) 650 return reportActivityCompleteByID(ctx, wc.workflowService, request, wc.metricsScope, wc.featureFlags) 651 } 652 653 // RecordActivityHeartbeat records heartbeat for an activity. 654 func (wc *workflowClient) RecordActivityHeartbeat(ctx context.Context, taskToken []byte, details ...interface{}) error { 655 data, err := encodeArgs(wc.dataConverter, details) 656 if err != nil { 657 return err 658 } 659 return recordActivityHeartbeat(ctx, wc.workflowService, wc.identity, taskToken, data, wc.featureFlags) 660 } 661 662 // RecordActivityHeartbeatByID records heartbeat for an activity. 663 func (wc *workflowClient) RecordActivityHeartbeatByID(ctx context.Context, 664 domain, workflowID, runID, activityID string, details ...interface{}) error { 665 data, err := encodeArgs(wc.dataConverter, details) 666 if err != nil { 667 return err 668 } 669 return recordActivityHeartbeatByID(ctx, wc.workflowService, wc.identity, domain, workflowID, runID, activityID, data, wc.featureFlags) 670 } 671 672 // ListClosedWorkflow gets closed workflow executions based on request filters 673 // The errors it can throw: 674 // - BadRequestError 675 // - InternalServiceError 676 // - EntityNotExistError 677 func (wc *workflowClient) ListClosedWorkflow(ctx context.Context, request *s.ListClosedWorkflowExecutionsRequest) (*s.ListClosedWorkflowExecutionsResponse, error) { 678 if len(request.GetDomain()) == 0 { 679 request.Domain = common.StringPtr(wc.domain) 680 } 681 var response *s.ListClosedWorkflowExecutionsResponse 682 err := backoff.Retry(ctx, 683 func() error { 684 var err1 error 685 tchCtx, cancel, opt := newChannelContext(ctx, wc.featureFlags) 686 defer cancel() 687 response, err1 = wc.workflowService.ListClosedWorkflowExecutions(tchCtx, request, opt...) 688 return err1 689 }, createDynamicServiceRetryPolicy(ctx), isServiceTransientError) 690 if err != nil { 691 return nil, err 692 } 693 return response, nil 694 } 695 696 // ListOpenWorkflow gets open workflow executions based on request filters 697 // The errors it can throw: 698 // - BadRequestError 699 // - InternalServiceError 700 // - EntityNotExistError 701 func (wc *workflowClient) ListOpenWorkflow(ctx context.Context, request *s.ListOpenWorkflowExecutionsRequest) (*s.ListOpenWorkflowExecutionsResponse, error) { 702 if len(request.GetDomain()) == 0 { 703 request.Domain = common.StringPtr(wc.domain) 704 } 705 var response *s.ListOpenWorkflowExecutionsResponse 706 err := backoff.Retry(ctx, 707 func() error { 708 var err1 error 709 tchCtx, cancel, opt := newChannelContext(ctx, wc.featureFlags) 710 defer cancel() 711 response, err1 = wc.workflowService.ListOpenWorkflowExecutions(tchCtx, request, opt...) 712 return err1 713 }, createDynamicServiceRetryPolicy(ctx), isServiceTransientError) 714 if err != nil { 715 return nil, err 716 } 717 return response, nil 718 } 719 720 // ListWorkflow implementation 721 func (wc *workflowClient) ListWorkflow(ctx context.Context, request *s.ListWorkflowExecutionsRequest) (*s.ListWorkflowExecutionsResponse, error) { 722 if len(request.GetDomain()) == 0 { 723 request.Domain = common.StringPtr(wc.domain) 724 } 725 var response *s.ListWorkflowExecutionsResponse 726 err := backoff.Retry(ctx, 727 func() error { 728 var err1 error 729 tchCtx, cancel, opt := newChannelContext(ctx, wc.featureFlags) 730 defer cancel() 731 response, err1 = wc.workflowService.ListWorkflowExecutions(tchCtx, request, opt...) 732 return err1 733 }, createDynamicServiceRetryPolicy(ctx), isServiceTransientError) 734 if err != nil { 735 return nil, err 736 } 737 return response, nil 738 } 739 740 // ListArchivedWorkflow implementation 741 func (wc *workflowClient) ListArchivedWorkflow(ctx context.Context, request *s.ListArchivedWorkflowExecutionsRequest) (*s.ListArchivedWorkflowExecutionsResponse, error) { 742 if len(request.GetDomain()) == 0 { 743 request.Domain = common.StringPtr(wc.domain) 744 } 745 var response *s.ListArchivedWorkflowExecutionsResponse 746 err := backoff.Retry(ctx, 747 func() error { 748 var err1 error 749 timeout := maxListArchivedWorkflowTimeout 750 now := time.Now() 751 if ctx != nil { 752 if expiration, ok := ctx.Deadline(); ok && expiration.After(now) { 753 timeout = expiration.Sub(now) 754 if timeout > maxListArchivedWorkflowTimeout { 755 timeout = maxListArchivedWorkflowTimeout 756 } else if timeout < minRPCTimeout { 757 timeout = minRPCTimeout 758 } 759 } 760 } 761 tchCtx, cancel, opt := newChannelContext(ctx, wc.featureFlags, chanTimeout(timeout)) 762 defer cancel() 763 response, err1 = wc.workflowService.ListArchivedWorkflowExecutions(tchCtx, request, opt...) 764 return err1 765 }, createDynamicServiceRetryPolicy(ctx), isServiceTransientError) 766 if err != nil { 767 return nil, err 768 } 769 return response, nil 770 } 771 772 // ScanWorkflow implementation 773 func (wc *workflowClient) ScanWorkflow(ctx context.Context, request *s.ListWorkflowExecutionsRequest) (*s.ListWorkflowExecutionsResponse, error) { 774 if len(request.GetDomain()) == 0 { 775 request.Domain = common.StringPtr(wc.domain) 776 } 777 var response *s.ListWorkflowExecutionsResponse 778 err := backoff.Retry(ctx, 779 func() error { 780 var err1 error 781 tchCtx, cancel, opt := newChannelContext(ctx, wc.featureFlags) 782 defer cancel() 783 response, err1 = wc.workflowService.ScanWorkflowExecutions(tchCtx, request, opt...) 784 return err1 785 }, createDynamicServiceRetryPolicy(ctx), isServiceTransientError) 786 if err != nil { 787 return nil, err 788 } 789 return response, nil 790 } 791 792 // CountWorkflow implementation 793 func (wc *workflowClient) CountWorkflow(ctx context.Context, request *s.CountWorkflowExecutionsRequest) (*s.CountWorkflowExecutionsResponse, error) { 794 if len(request.GetDomain()) == 0 { 795 request.Domain = common.StringPtr(wc.domain) 796 } 797 var response *s.CountWorkflowExecutionsResponse 798 err := backoff.Retry(ctx, 799 func() error { 800 var err1 error 801 tchCtx, cancel, opt := newChannelContext(ctx, wc.featureFlags) 802 defer cancel() 803 response, err1 = wc.workflowService.CountWorkflowExecutions(tchCtx, request, opt...) 804 return err1 805 }, createDynamicServiceRetryPolicy(ctx), isServiceTransientError) 806 if err != nil { 807 return nil, err 808 } 809 return response, nil 810 } 811 812 // ResetWorkflow implementation 813 func (wc *workflowClient) ResetWorkflow(ctx context.Context, request *s.ResetWorkflowExecutionRequest) (*s.ResetWorkflowExecutionResponse, error) { 814 if len(request.GetDomain()) == 0 { 815 request.Domain = common.StringPtr(wc.domain) 816 } 817 var response *s.ResetWorkflowExecutionResponse 818 err := backoff.Retry(ctx, 819 func() error { 820 var err1 error 821 tchCtx, cancel, opt := newChannelContext(ctx, wc.featureFlags) 822 defer cancel() 823 response, err1 = wc.workflowService.ResetWorkflowExecution(tchCtx, request, opt...) 824 return err1 825 }, createDynamicServiceRetryPolicy(ctx), isServiceTransientError) 826 if err != nil { 827 return nil, err 828 } 829 return response, nil 830 } 831 832 // GetSearchAttributes implementation 833 func (wc *workflowClient) GetSearchAttributes(ctx context.Context) (*s.GetSearchAttributesResponse, error) { 834 var response *s.GetSearchAttributesResponse 835 err := backoff.Retry(ctx, 836 func() error { 837 var err1 error 838 tchCtx, cancel, opt := newChannelContext(ctx, wc.featureFlags) 839 defer cancel() 840 response, err1 = wc.workflowService.GetSearchAttributes(tchCtx, opt...) 841 return err1 842 }, createDynamicServiceRetryPolicy(ctx), isServiceTransientError) 843 if err != nil { 844 return nil, err 845 } 846 return response, nil 847 } 848 849 // DescribeWorkflowExecution returns information about the specified workflow execution. 850 // The errors it can return: 851 // - BadRequestError 852 // - InternalServiceError 853 // - EntityNotExistError 854 func (wc *workflowClient) DescribeWorkflowExecution(ctx context.Context, workflowID, runID string) (*s.DescribeWorkflowExecutionResponse, error) { 855 request := &s.DescribeWorkflowExecutionRequest{ 856 Domain: common.StringPtr(wc.domain), 857 Execution: &s.WorkflowExecution{ 858 WorkflowId: common.StringPtr(workflowID), 859 RunId: common.StringPtr(runID), 860 }, 861 } 862 var response *s.DescribeWorkflowExecutionResponse 863 err := backoff.Retry(ctx, 864 func() error { 865 var err1 error 866 tchCtx, cancel, opt := newChannelContext(ctx, wc.featureFlags) 867 defer cancel() 868 response, err1 = wc.workflowService.DescribeWorkflowExecution(tchCtx, request, opt...) 869 return err1 870 }, createDynamicServiceRetryPolicy(ctx), isServiceTransientError) 871 if err != nil { 872 return nil, err 873 } 874 return response, nil 875 } 876 877 // QueryWorkflow queries a given workflow execution 878 // workflowID and queryType are required, other parameters are optional. 879 // - workflow ID of the workflow. 880 // - runID can be default(empty string). if empty string then it will pick the running execution of that workflow ID. 881 // - taskList can be default(empty string). If empty string then it will pick the taskList of the running execution of that workflow ID. 882 // - queryType is the type of the query. 883 // - args... are the optional query parameters. 884 // The errors it can return: 885 // - BadRequestError 886 // - InternalServiceError 887 // - EntityNotExistError 888 // - QueryFailError 889 func (wc *workflowClient) QueryWorkflow(ctx context.Context, workflowID string, runID string, queryType string, args ...interface{}) (Value, error) { 890 queryWorkflowWithOptionsRequest := &QueryWorkflowWithOptionsRequest{ 891 WorkflowID: workflowID, 892 RunID: runID, 893 QueryType: queryType, 894 Args: args, 895 } 896 result, err := wc.QueryWorkflowWithOptions(ctx, queryWorkflowWithOptionsRequest) 897 if err != nil { 898 return nil, err 899 } 900 return result.QueryResult, nil 901 } 902 903 // QueryWorkflowWithOptionsRequest is the request to QueryWorkflowWithOptions 904 type QueryWorkflowWithOptionsRequest struct { 905 // WorkflowID is a required field indicating the workflow which should be queried. 906 WorkflowID string 907 908 // RunID is an optional field used to identify a specific run of the queried workflow. 909 // If RunID is not provided the latest run will be used. 910 RunID string 911 912 // QueryType is a required field which specifies the query you want to run. 913 // By default, cadence supports "__stack_trace" as a standard query type, which will return string value 914 // representing the call stack of the target workflow. The target workflow could also setup different query handler to handle custom query types. 915 // See comments at workflow.SetQueryHandler(ctx Context, queryType string, handler interface{}) for more details on how to setup query handler within the target workflow. 916 QueryType string 917 918 // Args is an optional field used to identify the arguments passed to the query. 919 Args []interface{} 920 921 // QueryRejectCondition is an optional field used to reject queries based on workflow state. 922 // QueryRejectConditionNotOpen will reject queries to workflows which are not open 923 // QueryRejectConditionNotCompletedCleanly will reject queries to workflows which completed in any state other than completed (e.g. terminated, canceled timeout etc...) 924 QueryRejectCondition *s.QueryRejectCondition 925 926 // QueryConsistencyLevel is an optional field used to control the consistency level. 927 // QueryConsistencyLevelEventual means that query will eventually reflect up to date state of a workflow. 928 // QueryConsistencyLevelStrong means that query will reflect a workflow state of having applied all events which came before the query. 929 QueryConsistencyLevel *s.QueryConsistencyLevel 930 } 931 932 // QueryWorkflowWithOptionsResponse is the response to QueryWorkflowWithOptions 933 type QueryWorkflowWithOptionsResponse struct { 934 // QueryResult contains the result of executing the query. 935 // This will only be set if the query was completed successfully and not rejected. 936 QueryResult Value 937 938 // QueryRejected contains information about the query rejection. 939 QueryRejected *s.QueryRejected 940 } 941 942 // QueryWorkflowWithOptions queries a given workflow execution and returns the query result synchronously. 943 // See QueryWorkflowWithOptionsRequest and QueryWorkflowWithOptionsResult for more information. 944 // The errors it can return: 945 // - BadRequestError 946 // - InternalServiceError 947 // - EntityNotExistError 948 // - QueryFailError 949 func (wc *workflowClient) QueryWorkflowWithOptions(ctx context.Context, request *QueryWorkflowWithOptionsRequest) (*QueryWorkflowWithOptionsResponse, error) { 950 var input []byte 951 if len(request.Args) > 0 { 952 var err error 953 if input, err = encodeArgs(wc.dataConverter, request.Args); err != nil { 954 return nil, err 955 } 956 } 957 req := &s.QueryWorkflowRequest{ 958 Domain: common.StringPtr(wc.domain), 959 Execution: &s.WorkflowExecution{ 960 WorkflowId: common.StringPtr(request.WorkflowID), 961 RunId: getRunID(request.RunID), 962 }, 963 Query: &s.WorkflowQuery{ 964 QueryType: common.StringPtr(request.QueryType), 965 QueryArgs: input, 966 }, 967 QueryRejectCondition: request.QueryRejectCondition, 968 QueryConsistencyLevel: request.QueryConsistencyLevel, 969 } 970 971 var resp *s.QueryWorkflowResponse 972 err := backoff.Retry(ctx, 973 func() error { 974 tchCtx, cancel, opt := newChannelContextForQuery(ctx, wc.featureFlags) 975 defer cancel() 976 var err error 977 resp, err = wc.workflowService.QueryWorkflow(tchCtx, req, opt...) 978 return err 979 }, createDynamicServiceRetryPolicy(ctx), isServiceTransientError) 980 if err != nil { 981 return nil, err 982 } 983 984 if resp.QueryRejected != nil { 985 return &QueryWorkflowWithOptionsResponse{ 986 QueryRejected: resp.QueryRejected, 987 QueryResult: nil, 988 }, nil 989 } 990 return &QueryWorkflowWithOptionsResponse{ 991 QueryRejected: nil, 992 QueryResult: newEncodedValue(resp.QueryResult, wc.dataConverter), 993 }, nil 994 } 995 996 // DescribeTaskList returns information about the target tasklist, right now this API returns the 997 // pollers which polled this tasklist in last few minutes. 998 // - tasklist name of tasklist 999 // - tasklistType type of tasklist, can be decision or activity 1000 // The errors it can return: 1001 // - BadRequestError 1002 // - InternalServiceError 1003 // - EntityNotExistError 1004 func (wc *workflowClient) DescribeTaskList(ctx context.Context, tasklist string, tasklistType s.TaskListType) (*s.DescribeTaskListResponse, error) { 1005 request := &s.DescribeTaskListRequest{ 1006 Domain: common.StringPtr(wc.domain), 1007 TaskList: &s.TaskList{Name: common.StringPtr(tasklist)}, 1008 TaskListType: &tasklistType, 1009 } 1010 1011 var resp *s.DescribeTaskListResponse 1012 err := backoff.Retry(ctx, 1013 func() error { 1014 tchCtx, cancel, opt := newChannelContext(ctx, wc.featureFlags) 1015 defer cancel() 1016 var err error 1017 resp, err = wc.workflowService.DescribeTaskList(tchCtx, request, opt...) 1018 return err 1019 }, createDynamicServiceRetryPolicy(ctx), isServiceTransientError) 1020 if err != nil { 1021 return nil, err 1022 } 1023 1024 return resp, nil 1025 } 1026 1027 // RefreshWorkflowTasks refreshes all the tasks of a given workflow. 1028 // - workflow ID of the workflow. 1029 // - runID can be default(empty string). if empty string then it will pick the running execution of that workflow ID. 1030 // The errors it can return: 1031 // - BadRequestError 1032 // - DomainNotActiveError 1033 // - ServiceBusyError 1034 // - EntityNotExistError 1035 func (wc *workflowClient) RefreshWorkflowTasks(ctx context.Context, workflowID, runID string) error { 1036 request := &s.RefreshWorkflowTasksRequest{ 1037 Domain: common.StringPtr(wc.domain), 1038 Execution: &s.WorkflowExecution{ 1039 WorkflowId: common.StringPtr(workflowID), 1040 RunId: getRunID(runID), 1041 }, 1042 } 1043 1044 return backoff.Retry(ctx, 1045 func() error { 1046 tchCtx, cancel, opt := newChannelContext(ctx, wc.featureFlags) 1047 defer cancel() 1048 return wc.workflowService.RefreshWorkflowTasks(tchCtx, request, opt...) 1049 }, createDynamicServiceRetryPolicy(ctx), isServiceTransientError) 1050 } 1051 1052 func (wc *workflowClient) getWorkflowHeader(ctx context.Context) *s.Header { 1053 header := &s.Header{ 1054 Fields: make(map[string][]byte), 1055 } 1056 writer := NewHeaderWriter(header) 1057 for _, ctxProp := range wc.contextPropagators { 1058 ctxProp.Inject(ctx, writer) 1059 } 1060 return header 1061 } 1062 1063 // Register a domain with cadence server 1064 // The errors it can throw: 1065 // - DomainAlreadyExistsError 1066 // - BadRequestError 1067 // - InternalServiceError 1068 func (dc *domainClient) Register(ctx context.Context, request *s.RegisterDomainRequest) error { 1069 return backoff.Retry(ctx, 1070 func() error { 1071 tchCtx, cancel, opt := newChannelContext(ctx, dc.featureFlags) 1072 defer cancel() 1073 return dc.workflowService.RegisterDomain(tchCtx, request, opt...) 1074 }, createDynamicServiceRetryPolicy(ctx), isServiceTransientError) 1075 } 1076 1077 // Describe a domain. The domain has 3 part of information 1078 // DomainInfo - Which has Name, Status, Description, Owner Email 1079 // DomainConfiguration - Configuration like Workflow Execution Retention Period In Days, Whether to emit metrics. 1080 // ReplicationConfiguration - replication config like clusters and active cluster name 1081 // The errors it can throw: 1082 // - EntityNotExistsError 1083 // - BadRequestError 1084 // - InternalServiceError 1085 func (dc *domainClient) Describe(ctx context.Context, name string) (*s.DescribeDomainResponse, error) { 1086 request := &s.DescribeDomainRequest{ 1087 Name: common.StringPtr(name), 1088 } 1089 1090 var response *s.DescribeDomainResponse 1091 err := backoff.Retry(ctx, 1092 func() error { 1093 tchCtx, cancel, opt := newChannelContext(ctx, dc.featureFlags) 1094 defer cancel() 1095 var err error 1096 response, err = dc.workflowService.DescribeDomain(tchCtx, request, opt...) 1097 return err 1098 }, createDynamicServiceRetryPolicy(ctx), isServiceTransientError) 1099 if err != nil { 1100 return nil, err 1101 } 1102 return response, nil 1103 } 1104 1105 // Update a domain. 1106 // The errors it can throw: 1107 // - EntityNotExistsError 1108 // - BadRequestError 1109 // - InternalServiceError 1110 func (dc *domainClient) Update(ctx context.Context, request *s.UpdateDomainRequest) error { 1111 return backoff.Retry(ctx, 1112 func() error { 1113 tchCtx, cancel, opt := newChannelContext(ctx, dc.featureFlags) 1114 defer cancel() 1115 _, err := dc.workflowService.UpdateDomain(tchCtx, request, opt...) 1116 return err 1117 }, createDynamicServiceRetryPolicy(ctx), isServiceTransientError) 1118 } 1119 1120 func getRunID(runID string) *string { 1121 if runID == "" { 1122 // Cadence Server will pick current runID if provided empty. 1123 return nil 1124 } 1125 return common.StringPtr(runID) 1126 } 1127 1128 func (iter *historyEventIteratorImpl) HasNext() bool { 1129 if iter.nextEventIndex < len(iter.events) || iter.err != nil { 1130 return true 1131 } else if !iter.initialized || len(iter.nexttoken) != 0 { 1132 iter.initialized = true 1133 response, err := iter.paginate(iter.nexttoken) 1134 iter.nextEventIndex = 0 1135 if err == nil { 1136 iter.events = response.History.Events 1137 iter.nexttoken = response.NextPageToken 1138 iter.err = nil 1139 } else { 1140 iter.events = nil 1141 iter.nexttoken = nil 1142 iter.err = err 1143 } 1144 1145 if iter.nextEventIndex < len(iter.events) || iter.err != nil { 1146 return true 1147 } 1148 return false 1149 } 1150 1151 return false 1152 } 1153 1154 func (iter *historyEventIteratorImpl) Next() (*s.HistoryEvent, error) { 1155 // if caller call the Next() when iteration is over, just return nil, nil 1156 if !iter.HasNext() { 1157 panic("HistoryEventIterator Next() called without checking HasNext()") 1158 } 1159 1160 // we have cached events 1161 if iter.nextEventIndex < len(iter.events) { 1162 index := iter.nextEventIndex 1163 iter.nextEventIndex++ 1164 return iter.events[index], nil 1165 } else if iter.err != nil { 1166 // we have err, clear that iter.err and return err 1167 err := iter.err 1168 iter.err = nil 1169 return nil, err 1170 } 1171 1172 panic("HistoryEventIterator Next() should return either a history event or a err") 1173 } 1174 1175 func (workflowRun *workflowRunImpl) GetRunID() string { 1176 return workflowRun.firstRunID 1177 } 1178 1179 func (workflowRun *workflowRunImpl) GetID() string { 1180 return workflowRun.workflowID 1181 } 1182 1183 func (workflowRun *workflowRunImpl) Get(ctx context.Context, valuePtr interface{}) error { 1184 1185 iter := workflowRun.iterFn(ctx, workflowRun.currentRunID) 1186 if !iter.HasNext() { 1187 panic("could not get last history event for workflow") 1188 } 1189 closeEvent, err := iter.Next() 1190 if err != nil { 1191 return err 1192 } 1193 1194 switch closeEvent.GetEventType() { 1195 case s.EventTypeWorkflowExecutionCompleted: 1196 attributes := closeEvent.WorkflowExecutionCompletedEventAttributes 1197 if valuePtr == nil || attributes.Result == nil { 1198 return nil 1199 } 1200 rf := reflect.ValueOf(valuePtr) 1201 if rf.Type().Kind() != reflect.Ptr { 1202 return errors.New("value parameter is not a pointer") 1203 } 1204 err = deSerializeFunctionResult(workflowRun.workflowFn, attributes.Result, valuePtr, workflowRun.dataConverter, workflowRun.registry) 1205 case s.EventTypeWorkflowExecutionFailed: 1206 attributes := closeEvent.WorkflowExecutionFailedEventAttributes 1207 err = constructError(attributes.GetReason(), attributes.Details, workflowRun.dataConverter) 1208 case s.EventTypeWorkflowExecutionCanceled: 1209 attributes := closeEvent.WorkflowExecutionCanceledEventAttributes 1210 details := newEncodedValues(attributes.Details, workflowRun.dataConverter) 1211 err = NewCanceledError(details) 1212 case s.EventTypeWorkflowExecutionTerminated: 1213 err = newTerminatedError() 1214 case s.EventTypeWorkflowExecutionTimedOut: 1215 attributes := closeEvent.WorkflowExecutionTimedOutEventAttributes 1216 err = NewTimeoutError(attributes.GetTimeoutType()) 1217 case s.EventTypeWorkflowExecutionContinuedAsNew: 1218 attributes := closeEvent.WorkflowExecutionContinuedAsNewEventAttributes 1219 workflowRun.currentRunID = attributes.GetNewExecutionRunId() 1220 return workflowRun.Get(ctx, valuePtr) 1221 default: 1222 err = fmt.Errorf("Unexpected event type %s when handling workflow execution result", closeEvent.GetEventType()) 1223 } 1224 return err 1225 } 1226 1227 func getWorkflowMemo(input map[string]interface{}, dc DataConverter) (*s.Memo, error) { 1228 if input == nil { 1229 return nil, nil 1230 } 1231 1232 memo := make(map[string][]byte) 1233 for k, v := range input { 1234 memoBytes, err := encodeArg(dc, v) 1235 if err != nil { 1236 return nil, fmt.Errorf("encode workflow memo error: %v", err.Error()) 1237 } 1238 memo[k] = memoBytes 1239 } 1240 return &s.Memo{Fields: memo}, nil 1241 } 1242 1243 func serializeSearchAttributes(input map[string]interface{}) (*s.SearchAttributes, error) { 1244 if input == nil { 1245 return nil, nil 1246 } 1247 1248 attr := make(map[string][]byte) 1249 for k, v := range input { 1250 attrBytes, err := json.Marshal(v) 1251 if err != nil { 1252 return nil, fmt.Errorf("encode search attribute [%s] error: %v", k, err) 1253 } 1254 attr[k] = attrBytes 1255 } 1256 return &s.SearchAttributes{IndexedFields: attr}, nil 1257 }