mirror of
https://github.com/rocky-linux/peridot.git
synced 2024-10-18 23:45:08 +00:00
1266 lines
45 KiB
Go
1266 lines
45 KiB
Go
|
// The MIT License
|
||
|
//
|
||
|
// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved.
|
||
|
//
|
||
|
// Copyright (c) 2020 Uber Technologies, Inc.
|
||
|
//
|
||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||
|
// of this software and associated documentation files (the "Software"), to deal
|
||
|
// in the Software without restriction, including without limitation the rights
|
||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||
|
// copies of the Software, and to permit persons to whom the Software is
|
||
|
// furnished to do so, subject to the following conditions:
|
||
|
//
|
||
|
// The above copyright notice and this permission notice shall be included in
|
||
|
// all copies or substantial portions of the Software.
|
||
|
//
|
||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||
|
// THE SOFTWARE.
|
||
|
|
||
|
package internal
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"reflect"
|
||
|
"time"
|
||
|
|
||
|
"github.com/pborman/uuid"
|
||
|
commonpb "go.temporal.io/api/common/v1"
|
||
|
enumspb "go.temporal.io/api/enums/v1"
|
||
|
historypb "go.temporal.io/api/history/v1"
|
||
|
querypb "go.temporal.io/api/query/v1"
|
||
|
"go.temporal.io/api/serviceerror"
|
||
|
taskqueuepb "go.temporal.io/api/taskqueue/v1"
|
||
|
"go.temporal.io/api/workflowservice/v1"
|
||
|
|
||
|
"go.temporal.io/sdk/converter"
|
||
|
"go.temporal.io/sdk/internal/common/metrics"
|
||
|
"go.temporal.io/sdk/internal/common/serializer"
|
||
|
"go.temporal.io/sdk/internal/common/util"
|
||
|
"go.temporal.io/sdk/log"
|
||
|
)
|
||
|
|
||
|
// Assert that structs do indeed implement the interfaces
|
||
|
var _ Client = (*WorkflowClient)(nil)
|
||
|
var _ NamespaceClient = (*namespaceClient)(nil)
|
||
|
|
||
|
const (
|
||
|
defaultGetHistoryTimeout = 65 * time.Second
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
maxListArchivedWorkflowTimeout = time.Minute * 3
|
||
|
)
|
||
|
|
||
|
type (
|
||
|
// WorkflowClient is the client for starting a workflow execution.
|
||
|
WorkflowClient struct {
|
||
|
workflowService workflowservice.WorkflowServiceClient
|
||
|
connectionCloser io.Closer
|
||
|
namespace string
|
||
|
registry *registry
|
||
|
logger log.Logger
|
||
|
metricsHandler metrics.Handler
|
||
|
identity string
|
||
|
dataConverter converter.DataConverter
|
||
|
contextPropagators []ContextPropagator
|
||
|
workerInterceptors []WorkerInterceptor
|
||
|
interceptor ClientOutboundInterceptor
|
||
|
}
|
||
|
|
||
|
// namespaceClient is the client for managing namespaces.
|
||
|
namespaceClient struct {
|
||
|
workflowService workflowservice.WorkflowServiceClient
|
||
|
connectionCloser io.Closer
|
||
|
metricsHandler metrics.Handler
|
||
|
logger log.Logger
|
||
|
identity string
|
||
|
}
|
||
|
|
||
|
// WorkflowRun represents a started non child workflow
|
||
|
WorkflowRun interface {
|
||
|
// GetID return workflow ID, which will be same as StartWorkflowOptions.ID if provided.
|
||
|
GetID() string
|
||
|
|
||
|
// GetRunID return the first started workflow run ID (please see below) - empty string if no such run
|
||
|
GetRunID() string
|
||
|
|
||
|
// Get will fill the workflow execution result to valuePtr,
|
||
|
// if workflow execution is a success, or return corresponding,
|
||
|
// error. This is a blocking API.
|
||
|
Get(ctx context.Context, valuePtr interface{}) error
|
||
|
|
||
|
// NOTE: if the started workflow return ContinueAsNewError during the workflow execution, the
|
||
|
// return result of GetRunID() will be the started workflow run ID, not the new run ID caused by ContinueAsNewError,
|
||
|
// however, Get(ctx context.Context, valuePtr interface{}) will return result from the run which did not return ContinueAsNewError.
|
||
|
// Say ExecuteWorkflow started a workflow, in its first run, has run ID "run ID 1", and returned ContinueAsNewError,
|
||
|
// the second run has run ID "run ID 2" and return some result other than ContinueAsNewError:
|
||
|
// GetRunID() will always return "run ID 1" and Get(ctx context.Context, valuePtr interface{}) will return the result of second run.
|
||
|
// NOTE: DO NOT USE client.ExecuteWorkflow API INSIDE A WORKFLOW, USE workflow.ExecuteChildWorkflow instead
|
||
|
}
|
||
|
|
||
|
// workflowRunImpl is an implementation of WorkflowRun
|
||
|
workflowRunImpl struct {
|
||
|
workflowType string
|
||
|
workflowID string
|
||
|
firstRunID string
|
||
|
currentRunID *util.OnceCell
|
||
|
iterFn func(ctx context.Context, runID string) HistoryEventIterator
|
||
|
dataConverter converter.DataConverter
|
||
|
registry *registry
|
||
|
}
|
||
|
|
||
|
// HistoryEventIterator represents the interface for
|
||
|
// history event iterator
|
||
|
HistoryEventIterator interface {
|
||
|
// HasNext return whether this iterator has next value
|
||
|
HasNext() bool
|
||
|
// Next returns the next history events and error
|
||
|
// The errors it can return:
|
||
|
// - serviceerror.NotFound
|
||
|
// - serviceerror.InvalidArgument
|
||
|
// - serviceerror.Internal
|
||
|
// - serviceerror.Unavailable
|
||
|
Next() (*historypb.HistoryEvent, error)
|
||
|
}
|
||
|
|
||
|
// historyEventIteratorImpl is the implementation of HistoryEventIterator
|
||
|
historyEventIteratorImpl struct {
|
||
|
// whether this iterator is initialized
|
||
|
initialized bool
|
||
|
// local cached history events and corresponding consuming index
|
||
|
nextEventIndex int
|
||
|
events []*historypb.HistoryEvent
|
||
|
// token to get next page of history events
|
||
|
nexttoken []byte
|
||
|
// err when getting next page of history events
|
||
|
err error
|
||
|
// func which use a next token to get next page of history events
|
||
|
paginate func(nexttoken []byte) (*workflowservice.GetWorkflowExecutionHistoryResponse, error)
|
||
|
}
|
||
|
)
|
||
|
|
||
|
// ExecuteWorkflow starts a workflow execution and returns a WorkflowRun that will allow you to wait until this workflow
|
||
|
// reaches the end state, such as workflow finished successfully or timeout.
|
||
|
// The user can use this to start using a functor like below and get the workflow execution result, as EncodedValue
|
||
|
// Either by
|
||
|
// ExecuteWorkflow(options, "workflowTypeName", arg1, arg2, arg3)
|
||
|
// or
|
||
|
// ExecuteWorkflow(options, workflowExecuteFn, arg1, arg2, arg3)
|
||
|
// The current timeout resolution implementation is in seconds and uses math.Ceil(d.Seconds()) as the duration. But is
|
||
|
// subjected to change in the future.
|
||
|
// NOTE: the context.Context should have a fairly large timeout, since workflow execution may take a while to be finished
|
||
|
func (wc *WorkflowClient) ExecuteWorkflow(ctx context.Context, options StartWorkflowOptions, workflow interface{}, args ...interface{}) (WorkflowRun, error) {
|
||
|
// Default workflow ID
|
||
|
if options.ID == "" {
|
||
|
options.ID = uuid.New()
|
||
|
}
|
||
|
|
||
|
// Validate function and get name
|
||
|
if err := validateFunctionArgs(workflow, args, true); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
workflowType, err := getWorkflowFunctionName(wc.registry, workflow)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
// Set header before interceptor run
|
||
|
ctx = contextWithNewHeader(ctx)
|
||
|
|
||
|
// Run via interceptor
|
||
|
return wc.interceptor.ExecuteWorkflow(ctx, &ClientExecuteWorkflowInput{
|
||
|
Options: &options,
|
||
|
WorkflowType: workflowType,
|
||
|
Args: args,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// GetWorkflow gets a workflow execution and returns a WorkflowRun that will allow you to wait until this workflow
|
||
|
// reaches the end state, such as workflow finished successfully or timeout.
|
||
|
// The current timeout resolution implementation is in seconds and uses math.Ceil(d.Seconds()) as the duration. But is
|
||
|
// subjected to change in the future.
|
||
|
func (wc *WorkflowClient) GetWorkflow(ctx context.Context, workflowID string, runID string) WorkflowRun {
|
||
|
iterFn := func(fnCtx context.Context, fnRunID string) HistoryEventIterator {
|
||
|
return wc.GetWorkflowHistory(fnCtx, workflowID, fnRunID, true, enumspb.HISTORY_EVENT_FILTER_TYPE_CLOSE_EVENT)
|
||
|
}
|
||
|
|
||
|
// The ID may not actually have been set - if not, we have to (lazily) ask the server for info about the workflow
|
||
|
// execution and extract run id from there. This is definitely less efficient than it could be if there was a more
|
||
|
// specific rpc method for this, or if there were more granular history filters - in which case it could be
|
||
|
// extracted from the `iterFn` inside of `workflowRunImpl`
|
||
|
var runIDCell util.OnceCell
|
||
|
if runID == "" {
|
||
|
fetcher := func() string {
|
||
|
execData, _ := wc.DescribeWorkflowExecution(ctx, workflowID, runID)
|
||
|
wei := execData.GetWorkflowExecutionInfo()
|
||
|
if wei != nil {
|
||
|
execution := wei.GetExecution()
|
||
|
if execution != nil {
|
||
|
return execution.RunId
|
||
|
}
|
||
|
}
|
||
|
return ""
|
||
|
}
|
||
|
runIDCell = util.LazyOnceCell(fetcher)
|
||
|
} else {
|
||
|
runIDCell = util.PopulatedOnceCell(runID)
|
||
|
}
|
||
|
|
||
|
return &workflowRunImpl{
|
||
|
workflowID: workflowID,
|
||
|
firstRunID: runID,
|
||
|
currentRunID: &runIDCell,
|
||
|
iterFn: iterFn,
|
||
|
dataConverter: wc.dataConverter,
|
||
|
registry: wc.registry,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// SignalWorkflow signals a workflow in execution.
|
||
|
func (wc *WorkflowClient) SignalWorkflow(ctx context.Context, workflowID string, runID string, signalName string, arg interface{}) error {
|
||
|
// Set header before interceptor run
|
||
|
ctx = contextWithNewHeader(ctx)
|
||
|
|
||
|
return wc.interceptor.SignalWorkflow(ctx, &ClientSignalWorkflowInput{
|
||
|
WorkflowID: workflowID,
|
||
|
RunID: runID,
|
||
|
SignalName: signalName,
|
||
|
Arg: arg,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// SignalWithStartWorkflow sends a signal to a running workflow.
|
||
|
// If the workflow is not running or not found, it starts the workflow and then sends the signal in transaction.
|
||
|
func (wc *WorkflowClient) SignalWithStartWorkflow(ctx context.Context, workflowID string, signalName string, signalArg interface{},
|
||
|
options StartWorkflowOptions, workflowFunc interface{}, workflowArgs ...interface{}) (WorkflowRun, error) {
|
||
|
|
||
|
// Due to the ambiguous way to provide workflow IDs, if options contains an
|
||
|
// ID, it must match the parameter
|
||
|
if options.ID != "" && options.ID != workflowID {
|
||
|
return nil, fmt.Errorf("workflow ID from options not used, must be unset or match workflow ID parameter")
|
||
|
}
|
||
|
|
||
|
// Default workflow ID to UUID
|
||
|
options.ID = workflowID
|
||
|
if options.ID == "" {
|
||
|
options.ID = uuid.New()
|
||
|
}
|
||
|
|
||
|
// Validate function and get name
|
||
|
if err := validateFunctionArgs(workflowFunc, workflowArgs, true); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
workflowType, err := getWorkflowFunctionName(wc.registry, workflowFunc)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
// Set header before interceptor run
|
||
|
ctx = contextWithNewHeader(ctx)
|
||
|
|
||
|
// Run via interceptor
|
||
|
return wc.interceptor.SignalWithStartWorkflow(ctx, &ClientSignalWithStartWorkflowInput{
|
||
|
SignalName: signalName,
|
||
|
SignalArg: signalArg,
|
||
|
Options: &options,
|
||
|
WorkflowType: workflowType,
|
||
|
Args: workflowArgs,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// CancelWorkflow cancels a workflow in execution. It allows workflow to properly clean up and gracefully close.
|
||
|
// workflowID is required, other parameters are optional.
|
||
|
// If runID is omit, it will terminate currently running workflow (if there is one) based on the workflowID.
|
||
|
func (wc *WorkflowClient) CancelWorkflow(ctx context.Context, workflowID string, runID string) error {
|
||
|
return wc.interceptor.CancelWorkflow(ctx, &ClientCancelWorkflowInput{WorkflowID: workflowID, RunID: runID})
|
||
|
}
|
||
|
|
||
|
// TerminateWorkflow terminates a workflow execution.
|
||
|
// workflowID is required, other parameters are optional.
|
||
|
// If runID is omit, it will terminate currently running workflow (if there is one) based on the workflowID.
|
||
|
func (wc *WorkflowClient) TerminateWorkflow(ctx context.Context, workflowID string, runID string, reason string, details ...interface{}) error {
|
||
|
return wc.interceptor.TerminateWorkflow(ctx, &ClientTerminateWorkflowInput{
|
||
|
WorkflowID: workflowID,
|
||
|
RunID: runID,
|
||
|
Reason: reason,
|
||
|
Details: details,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// GetWorkflowHistory return a channel which contains the history events of a given workflow
|
||
|
func (wc *WorkflowClient) GetWorkflowHistory(
|
||
|
ctx context.Context,
|
||
|
workflowID string,
|
||
|
runID string,
|
||
|
isLongPoll bool,
|
||
|
filterType enumspb.HistoryEventFilterType,
|
||
|
) HistoryEventIterator {
|
||
|
return wc.getWorkflowHistory(ctx, workflowID, runID, isLongPoll, filterType, wc.metricsHandler)
|
||
|
}
|
||
|
|
||
|
func (wc *WorkflowClient) getWorkflowHistory(
|
||
|
ctx context.Context,
|
||
|
workflowID string,
|
||
|
runID string,
|
||
|
isLongPoll bool,
|
||
|
filterType enumspb.HistoryEventFilterType,
|
||
|
rpcMetricsHandler metrics.Handler,
|
||
|
) HistoryEventIterator {
|
||
|
namespace := wc.namespace
|
||
|
paginate := func(nextToken []byte) (*workflowservice.GetWorkflowExecutionHistoryResponse, error) {
|
||
|
request := &workflowservice.GetWorkflowExecutionHistoryRequest{
|
||
|
Namespace: namespace,
|
||
|
Execution: &commonpb.WorkflowExecution{
|
||
|
WorkflowId: workflowID,
|
||
|
RunId: runID,
|
||
|
},
|
||
|
WaitNewEvent: isLongPoll,
|
||
|
HistoryEventFilterType: filterType,
|
||
|
NextPageToken: nextToken,
|
||
|
SkipArchival: isLongPoll,
|
||
|
}
|
||
|
|
||
|
var response *workflowservice.GetWorkflowExecutionHistoryResponse
|
||
|
var err error
|
||
|
Loop:
|
||
|
for {
|
||
|
response, err = wc.getWorkflowExecutionHistory(ctx, rpcMetricsHandler, isLongPoll, request, filterType)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
if isLongPoll && len(response.History.Events) == 0 && len(response.NextPageToken) != 0 {
|
||
|
request.NextPageToken = response.NextPageToken
|
||
|
continue Loop
|
||
|
}
|
||
|
break Loop
|
||
|
}
|
||
|
return response, nil
|
||
|
}
|
||
|
|
||
|
return &historyEventIteratorImpl{
|
||
|
paginate: paginate,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (wc *WorkflowClient) getWorkflowExecutionHistory(ctx context.Context, rpcMetricsHandler metrics.Handler, isLongPoll bool,
|
||
|
request *workflowservice.GetWorkflowExecutionHistoryRequest, filterType enumspb.HistoryEventFilterType) (*workflowservice.GetWorkflowExecutionHistoryResponse, error) {
|
||
|
grpcCtx, cancel := newGRPCContext(ctx, grpcMetricsHandler(rpcMetricsHandler), grpcLongPoll(isLongPoll), defaultGrpcRetryParameters(ctx), func(builder *grpcContextBuilder) {
|
||
|
if isLongPoll {
|
||
|
builder.Timeout = defaultGetHistoryTimeout
|
||
|
}
|
||
|
})
|
||
|
|
||
|
defer cancel()
|
||
|
response, err := wc.workflowService.GetWorkflowExecutionHistory(grpcCtx, request)
|
||
|
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
if response.RawHistory != nil {
|
||
|
history, err := serializer.DeserializeBlobDataToHistoryEvents(response.RawHistory, filterType)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
response.History = history
|
||
|
}
|
||
|
return response, err
|
||
|
}
|
||
|
|
||
|
// CompleteActivity reports activity completed. activity Execute method can return activity.ErrResultPending to
|
||
|
// indicate the activity is not completed when it's Execute method returns. In that case, this CompleteActivity() method
|
||
|
// should be called when that activity is completed with the actual result and error. If err is nil, activity task
|
||
|
// completed event will be reported; if err is CanceledError, activity task canceled event will be reported; otherwise,
|
||
|
// activity task failed event will be reported.
|
||
|
func (wc *WorkflowClient) CompleteActivity(ctx context.Context, taskToken []byte, result interface{}, err error) error {
|
||
|
if taskToken == nil {
|
||
|
return errors.New("invalid task token provided")
|
||
|
}
|
||
|
|
||
|
dataConverter := WithContext(ctx, wc.dataConverter)
|
||
|
var data *commonpb.Payloads
|
||
|
if result != nil {
|
||
|
var err0 error
|
||
|
data, err0 = encodeArg(dataConverter, result)
|
||
|
if err0 != nil {
|
||
|
return err0
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// We do allow canceled error to be passed here
|
||
|
cancelAllowed := true
|
||
|
request := convertActivityResultToRespondRequest(wc.identity, taskToken,
|
||
|
data, err, wc.dataConverter, wc.namespace, cancelAllowed)
|
||
|
return reportActivityComplete(ctx, wc.workflowService, request, wc.metricsHandler)
|
||
|
}
|
||
|
|
||
|
// CompleteActivityByID reports activity completed. Similar to CompleteActivity
|
||
|
// It takes namespace name, workflowID, runID, activityID as arguments.
|
||
|
func (wc *WorkflowClient) CompleteActivityByID(ctx context.Context, namespace, workflowID, runID, activityID string,
|
||
|
result interface{}, err error) error {
|
||
|
|
||
|
if activityID == "" || workflowID == "" || namespace == "" {
|
||
|
return errors.New("empty activity or workflow id or namespace")
|
||
|
}
|
||
|
|
||
|
dataConverter := WithContext(ctx, wc.dataConverter)
|
||
|
var data *commonpb.Payloads
|
||
|
if result != nil {
|
||
|
var err0 error
|
||
|
data, err0 = encodeArg(dataConverter, result)
|
||
|
if err0 != nil {
|
||
|
return err0
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// We do allow canceled error to be passed here
|
||
|
cancelAllowed := true
|
||
|
request := convertActivityResultToRespondRequestByID(wc.identity, namespace, workflowID, runID, activityID,
|
||
|
data, err, wc.dataConverter, cancelAllowed)
|
||
|
return reportActivityCompleteByID(ctx, wc.workflowService, request, wc.metricsHandler)
|
||
|
}
|
||
|
|
||
|
// RecordActivityHeartbeat records heartbeat for an activity.
|
||
|
func (wc *WorkflowClient) RecordActivityHeartbeat(ctx context.Context, taskToken []byte, details ...interface{}) error {
|
||
|
dataConverter := WithContext(ctx, wc.dataConverter)
|
||
|
data, err := encodeArgs(dataConverter, details)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return recordActivityHeartbeat(ctx, wc.workflowService, wc.metricsHandler, wc.identity, taskToken, data)
|
||
|
}
|
||
|
|
||
|
// RecordActivityHeartbeatByID records heartbeat for an activity.
|
||
|
func (wc *WorkflowClient) RecordActivityHeartbeatByID(ctx context.Context,
|
||
|
namespace, workflowID, runID, activityID string, details ...interface{}) error {
|
||
|
dataConverter := WithContext(ctx, wc.dataConverter)
|
||
|
data, err := encodeArgs(dataConverter, details)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return recordActivityHeartbeatByID(ctx, wc.workflowService, wc.metricsHandler, wc.identity, namespace, workflowID, runID, activityID, data)
|
||
|
}
|
||
|
|
||
|
// ListClosedWorkflow gets closed workflow executions based on request filters
|
||
|
// The errors it can throw:
|
||
|
// - serviceerror.InvalidArgument
|
||
|
// - serviceerror.Internal
|
||
|
// - serviceerror.Unavailable
|
||
|
// - serviceerror.NotFound
|
||
|
func (wc *WorkflowClient) ListClosedWorkflow(ctx context.Context, request *workflowservice.ListClosedWorkflowExecutionsRequest) (*workflowservice.ListClosedWorkflowExecutionsResponse, error) {
|
||
|
if request.GetNamespace() == "" {
|
||
|
request.Namespace = wc.namespace
|
||
|
}
|
||
|
grpcCtx, cancel := newGRPCContext(ctx, defaultGrpcRetryParameters(ctx))
|
||
|
defer cancel()
|
||
|
response, err := wc.workflowService.ListClosedWorkflowExecutions(grpcCtx, request)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return response, nil
|
||
|
}
|
||
|
|
||
|
// ListOpenWorkflow gets open workflow executions based on request filters
|
||
|
// The errors it can throw:
|
||
|
// - serviceerror.InvalidArgument
|
||
|
// - serviceerror.Internal
|
||
|
// - serviceerror.Unavailable
|
||
|
// - serviceerror.NotFound
|
||
|
func (wc *WorkflowClient) ListOpenWorkflow(ctx context.Context, request *workflowservice.ListOpenWorkflowExecutionsRequest) (*workflowservice.ListOpenWorkflowExecutionsResponse, error) {
|
||
|
if request.GetNamespace() == "" {
|
||
|
request.Namespace = wc.namespace
|
||
|
}
|
||
|
grpcCtx, cancel := newGRPCContext(ctx, defaultGrpcRetryParameters(ctx))
|
||
|
defer cancel()
|
||
|
response, err := wc.workflowService.ListOpenWorkflowExecutions(grpcCtx, request)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return response, nil
|
||
|
}
|
||
|
|
||
|
// ListWorkflow implementation
|
||
|
func (wc *WorkflowClient) ListWorkflow(ctx context.Context, request *workflowservice.ListWorkflowExecutionsRequest) (*workflowservice.ListWorkflowExecutionsResponse, error) {
|
||
|
if request.GetNamespace() == "" {
|
||
|
request.Namespace = wc.namespace
|
||
|
}
|
||
|
grpcCtx, cancel := newGRPCContext(ctx, defaultGrpcRetryParameters(ctx))
|
||
|
defer cancel()
|
||
|
response, err := wc.workflowService.ListWorkflowExecutions(grpcCtx, request)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return response, nil
|
||
|
}
|
||
|
|
||
|
// ListArchivedWorkflow implementation
|
||
|
func (wc *WorkflowClient) ListArchivedWorkflow(ctx context.Context, request *workflowservice.ListArchivedWorkflowExecutionsRequest) (*workflowservice.ListArchivedWorkflowExecutionsResponse, error) {
|
||
|
if request.GetNamespace() == "" {
|
||
|
request.Namespace = wc.namespace
|
||
|
}
|
||
|
timeout := maxListArchivedWorkflowTimeout
|
||
|
now := time.Now()
|
||
|
if ctx != nil {
|
||
|
if expiration, ok := ctx.Deadline(); ok && expiration.After(now) {
|
||
|
timeout = expiration.Sub(now)
|
||
|
if timeout > maxListArchivedWorkflowTimeout {
|
||
|
timeout = maxListArchivedWorkflowTimeout
|
||
|
} else if timeout < minRPCTimeout {
|
||
|
timeout = minRPCTimeout
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
grpcCtx, cancel := newGRPCContext(ctx, grpcTimeout(timeout), defaultGrpcRetryParameters(ctx))
|
||
|
defer cancel()
|
||
|
response, err := wc.workflowService.ListArchivedWorkflowExecutions(grpcCtx, request)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return response, nil
|
||
|
}
|
||
|
|
||
|
// ScanWorkflow implementation
|
||
|
func (wc *WorkflowClient) ScanWorkflow(ctx context.Context, request *workflowservice.ScanWorkflowExecutionsRequest) (*workflowservice.ScanWorkflowExecutionsResponse, error) {
|
||
|
if request.GetNamespace() == "" {
|
||
|
request.Namespace = wc.namespace
|
||
|
}
|
||
|
grpcCtx, cancel := newGRPCContext(ctx, defaultGrpcRetryParameters(ctx))
|
||
|
defer cancel()
|
||
|
response, err := wc.workflowService.ScanWorkflowExecutions(grpcCtx, request)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return response, nil
|
||
|
}
|
||
|
|
||
|
// CountWorkflow implementation
|
||
|
func (wc *WorkflowClient) CountWorkflow(ctx context.Context, request *workflowservice.CountWorkflowExecutionsRequest) (*workflowservice.CountWorkflowExecutionsResponse, error) {
|
||
|
if request.GetNamespace() == "" {
|
||
|
request.Namespace = wc.namespace
|
||
|
}
|
||
|
grpcCtx, cancel := newGRPCContext(ctx, defaultGrpcRetryParameters(ctx))
|
||
|
defer cancel()
|
||
|
response, err := wc.workflowService.CountWorkflowExecutions(grpcCtx, request)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return response, nil
|
||
|
}
|
||
|
|
||
|
// GetSearchAttributes implementation
|
||
|
func (wc *WorkflowClient) GetSearchAttributes(ctx context.Context) (*workflowservice.GetSearchAttributesResponse, error) {
|
||
|
grpcCtx, cancel := newGRPCContext(ctx, defaultGrpcRetryParameters(ctx))
|
||
|
defer cancel()
|
||
|
response, err := wc.workflowService.GetSearchAttributes(grpcCtx, &workflowservice.GetSearchAttributesRequest{})
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return response, nil
|
||
|
}
|
||
|
|
||
|
// DescribeWorkflowExecution returns information about the specified workflow execution.
|
||
|
// The errors it can return:
|
||
|
// - serviceerror.InvalidArgument
|
||
|
// - serviceerror.Internal
|
||
|
// - serviceerror.Unavailable
|
||
|
// - serviceerror.NotFound
|
||
|
func (wc *WorkflowClient) DescribeWorkflowExecution(ctx context.Context, workflowID, runID string) (*workflowservice.DescribeWorkflowExecutionResponse, error) {
|
||
|
request := &workflowservice.DescribeWorkflowExecutionRequest{
|
||
|
Namespace: wc.namespace,
|
||
|
Execution: &commonpb.WorkflowExecution{
|
||
|
WorkflowId: workflowID,
|
||
|
RunId: runID,
|
||
|
},
|
||
|
}
|
||
|
grpcCtx, cancel := newGRPCContext(ctx, defaultGrpcRetryParameters(ctx))
|
||
|
defer cancel()
|
||
|
response, err := wc.workflowService.DescribeWorkflowExecution(grpcCtx, request)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return response, nil
|
||
|
}
|
||
|
|
||
|
// QueryWorkflow queries a given workflow execution
|
||
|
// workflowID and queryType are required, other parameters are optional.
|
||
|
// - workflow ID of the workflow.
|
||
|
// - runID can be default(empty string). if empty string then it will pick the running execution of that workflow ID.
|
||
|
// - taskQueue can be default(empty string). If empty string then it will pick the taskQueue of the running execution of that workflow ID.
|
||
|
// - queryType is the type of the query.
|
||
|
// - args... are the optional query parameters.
|
||
|
// The errors it can return:
|
||
|
// - serviceerror.InvalidArgument
|
||
|
// - serviceerror.Internal
|
||
|
// - serviceerror.Unavailable
|
||
|
// - serviceerror.NotFound
|
||
|
// - serviceerror.QueryFailed
|
||
|
func (wc *WorkflowClient) QueryWorkflow(ctx context.Context, workflowID string, runID string, queryType string, args ...interface{}) (converter.EncodedValue, error) {
|
||
|
// Set header before interceptor run
|
||
|
ctx = contextWithNewHeader(ctx)
|
||
|
|
||
|
return wc.interceptor.QueryWorkflow(ctx, &ClientQueryWorkflowInput{
|
||
|
WorkflowID: workflowID,
|
||
|
RunID: runID,
|
||
|
QueryType: queryType,
|
||
|
Args: args,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// QueryWorkflowWithOptionsRequest is the request to QueryWorkflowWithOptions
|
||
|
type QueryWorkflowWithOptionsRequest struct {
|
||
|
// WorkflowID is a required field indicating the workflow which should be queried.
|
||
|
WorkflowID string
|
||
|
|
||
|
// RunID is an optional field used to identify a specific run of the queried workflow.
|
||
|
// If RunID is not provided the latest run will be used.
|
||
|
RunID string
|
||
|
|
||
|
// QueryType is a required field which specifies the query you want to run.
|
||
|
// By default, temporal supports "__stack_trace" as a standard query type, which will return string value
|
||
|
// representing the call stack of the target workflow. The target workflow could also setup different query handler to handle custom query types.
|
||
|
// See comments at workflow.SetQueryHandler(ctx Context, queryType string, handler interface{}) for more details on how to setup query handler within the target workflow.
|
||
|
QueryType string
|
||
|
|
||
|
// Args is an optional field used to identify the arguments passed to the query.
|
||
|
Args []interface{}
|
||
|
|
||
|
// QueryRejectCondition is an optional field used to reject queries based on workflow state.
|
||
|
// QUERY_REJECT_CONDITION_NONE indicates that query should not be rejected.
|
||
|
// QUERY_REJECT_CONDITION_NOT_OPEN indicates that query should be rejected if workflow is not open.
|
||
|
// QUERY_REJECT_CONDITION_NOT_COMPLETED_CLEANLY indicates that query should be rejected if workflow did not complete cleanly (e.g. terminated, canceled timeout etc...).
|
||
|
QueryRejectCondition enumspb.QueryRejectCondition
|
||
|
|
||
|
// Header is an optional header to include with the query.
|
||
|
Header *commonpb.Header
|
||
|
}
|
||
|
|
||
|
// QueryWorkflowWithOptionsResponse is the response to QueryWorkflowWithOptions
|
||
|
type QueryWorkflowWithOptionsResponse struct {
|
||
|
// QueryResult contains the result of executing the query.
|
||
|
// This will only be set if the query was completed successfully and not rejected.
|
||
|
QueryResult converter.EncodedValue
|
||
|
|
||
|
// QueryRejected contains information about the query rejection.
|
||
|
QueryRejected *querypb.QueryRejected
|
||
|
}
|
||
|
|
||
|
// QueryWorkflowWithOptions queries a given workflow execution and returns the query result synchronously.
|
||
|
// See QueryWorkflowWithOptionsRequest and QueryWorkflowWithOptionsResult for more information.
|
||
|
// The errors it can return:
|
||
|
// - serviceerror.InvalidArgument
|
||
|
// - serviceerror.Internal
|
||
|
// - serviceerror.Unavailable
|
||
|
// - serviceerror.NotFound
|
||
|
// - serviceerror.QueryFailed
|
||
|
func (wc *WorkflowClient) QueryWorkflowWithOptions(ctx context.Context, request *QueryWorkflowWithOptionsRequest) (*QueryWorkflowWithOptionsResponse, error) {
|
||
|
var input *commonpb.Payloads
|
||
|
if len(request.Args) > 0 {
|
||
|
var err error
|
||
|
if input, err = encodeArgs(wc.dataConverter, request.Args); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
req := &workflowservice.QueryWorkflowRequest{
|
||
|
Namespace: wc.namespace,
|
||
|
Execution: &commonpb.WorkflowExecution{
|
||
|
WorkflowId: request.WorkflowID,
|
||
|
RunId: request.RunID,
|
||
|
},
|
||
|
Query: &querypb.WorkflowQuery{
|
||
|
QueryType: request.QueryType,
|
||
|
QueryArgs: input,
|
||
|
Header: request.Header,
|
||
|
},
|
||
|
QueryRejectCondition: request.QueryRejectCondition,
|
||
|
}
|
||
|
|
||
|
grpcCtx, cancel := newGRPCContext(ctx, defaultGrpcRetryParameters(ctx))
|
||
|
defer cancel()
|
||
|
resp, err := wc.workflowService.QueryWorkflow(grpcCtx, req)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
if resp.QueryRejected != nil {
|
||
|
return &QueryWorkflowWithOptionsResponse{
|
||
|
QueryRejected: resp.QueryRejected,
|
||
|
QueryResult: nil,
|
||
|
}, nil
|
||
|
}
|
||
|
return &QueryWorkflowWithOptionsResponse{
|
||
|
QueryRejected: nil,
|
||
|
QueryResult: newEncodedValue(resp.QueryResult, wc.dataConverter),
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
// DescribeTaskQueue returns information about the target taskqueue, right now this API returns the
|
||
|
// pollers which polled this taskqueue in last few minutes.
|
||
|
// - taskqueue name of taskqueue
|
||
|
// - taskqueueType type of taskqueue, can be workflow or activity
|
||
|
// The errors it can return:
|
||
|
// - serviceerror.InvalidArgument
|
||
|
// - serviceerror.Internal
|
||
|
// - serviceerror.Unavailable
|
||
|
// - serviceerror.NotFound
|
||
|
func (wc *WorkflowClient) DescribeTaskQueue(ctx context.Context, taskQueue string, taskQueueType enumspb.TaskQueueType) (*workflowservice.DescribeTaskQueueResponse, error) {
|
||
|
request := &workflowservice.DescribeTaskQueueRequest{
|
||
|
Namespace: wc.namespace,
|
||
|
TaskQueue: &taskqueuepb.TaskQueue{Name: taskQueue, Kind: enumspb.TASK_QUEUE_KIND_NORMAL},
|
||
|
TaskQueueType: taskQueueType,
|
||
|
}
|
||
|
|
||
|
grpcCtx, cancel := newGRPCContext(ctx, defaultGrpcRetryParameters(ctx))
|
||
|
defer cancel()
|
||
|
resp, err := wc.workflowService.DescribeTaskQueue(grpcCtx, request)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return resp, nil
|
||
|
}
|
||
|
|
||
|
// ResetWorkflowExecution reset an existing workflow execution to WorkflowTaskFinishEventId(exclusive).
|
||
|
// And it will immediately terminating the current execution instance.
|
||
|
// RequestId is used to deduplicate requests. It will be autogenerated if not set.
|
||
|
func (wc *WorkflowClient) ResetWorkflowExecution(ctx context.Context, request *workflowservice.ResetWorkflowExecutionRequest) (*workflowservice.ResetWorkflowExecutionResponse, error) {
|
||
|
if request != nil && request.GetRequestId() == "" {
|
||
|
request.RequestId = uuid.New()
|
||
|
}
|
||
|
|
||
|
grpcCtx, cancel := newGRPCContext(ctx, defaultGrpcRetryParameters(ctx))
|
||
|
defer cancel()
|
||
|
resp, err := wc.workflowService.ResetWorkflowExecution(grpcCtx, request)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return resp, nil
|
||
|
}
|
||
|
|
||
|
// WorkflowService implements Client.WorkflowService.
|
||
|
func (wc *WorkflowClient) WorkflowService() workflowservice.WorkflowServiceClient {
|
||
|
return wc.workflowService
|
||
|
}
|
||
|
|
||
|
// Close client and clean up underlying resources.
|
||
|
func (wc *WorkflowClient) Close() {
|
||
|
if wc.connectionCloser == nil {
|
||
|
return
|
||
|
}
|
||
|
if err := wc.connectionCloser.Close(); err != nil {
|
||
|
wc.logger.Warn("unable to close connection", tagError, err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Register a namespace with temporal server
|
||
|
// The errors it can throw:
|
||
|
// - NamespaceAlreadyExistsError
|
||
|
// - serviceerror.InvalidArgument
|
||
|
// - serviceerror.Internal
|
||
|
// - serviceerror.Unavailable
|
||
|
func (nc *namespaceClient) Register(ctx context.Context, request *workflowservice.RegisterNamespaceRequest) error {
|
||
|
grpcCtx, cancel := newGRPCContext(ctx, defaultGrpcRetryParameters(ctx))
|
||
|
defer cancel()
|
||
|
var err error
|
||
|
_, err = nc.workflowService.RegisterNamespace(grpcCtx, request)
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// Describe a namespace. The namespace has 3 part of information
|
||
|
// NamespaceInfo - Which has Name, Status, Description, Owner Email
|
||
|
// NamespaceConfiguration - Configuration like Workflow Execution Retention Period In Days, Whether to emit metrics.
|
||
|
// ReplicationConfiguration - replication config like clusters and active cluster name
|
||
|
// The errors it can throw:
|
||
|
// - serviceerror.NotFound
|
||
|
// - serviceerror.InvalidArgument
|
||
|
// - serviceerror.Internal
|
||
|
// - serviceerror.Unavailable
|
||
|
func (nc *namespaceClient) Describe(ctx context.Context, namespace string) (*workflowservice.DescribeNamespaceResponse, error) {
|
||
|
request := &workflowservice.DescribeNamespaceRequest{
|
||
|
Namespace: namespace,
|
||
|
}
|
||
|
|
||
|
grpcCtx, cancel := newGRPCContext(ctx, defaultGrpcRetryParameters(ctx))
|
||
|
defer cancel()
|
||
|
response, err := nc.workflowService.DescribeNamespace(grpcCtx, request)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return response, nil
|
||
|
}
|
||
|
|
||
|
// Update a namespace.
|
||
|
// The errors it can throw:
|
||
|
// - serviceerror.NotFound
|
||
|
// - serviceerror.InvalidArgument
|
||
|
// - serviceerror.Internal
|
||
|
// - serviceerror.Unavailable
|
||
|
func (nc *namespaceClient) Update(ctx context.Context, request *workflowservice.UpdateNamespaceRequest) error {
|
||
|
grpcCtx, cancel := newGRPCContext(ctx, defaultGrpcRetryParameters(ctx))
|
||
|
defer cancel()
|
||
|
_, err := nc.workflowService.UpdateNamespace(grpcCtx, request)
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// Close client and clean up underlying resources.
|
||
|
func (nc *namespaceClient) Close() {
|
||
|
if nc.connectionCloser == nil {
|
||
|
return
|
||
|
}
|
||
|
if err := nc.connectionCloser.Close(); err != nil {
|
||
|
nc.logger.Warn("unable to close connection", tagError, err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (iter *historyEventIteratorImpl) HasNext() bool {
|
||
|
if iter.nextEventIndex < len(iter.events) || iter.err != nil {
|
||
|
return true
|
||
|
} else if !iter.initialized || len(iter.nexttoken) != 0 {
|
||
|
iter.initialized = true
|
||
|
response, err := iter.paginate(iter.nexttoken)
|
||
|
iter.nextEventIndex = 0
|
||
|
if err == nil {
|
||
|
iter.events = response.History.Events
|
||
|
iter.nexttoken = response.NextPageToken
|
||
|
iter.err = nil
|
||
|
} else {
|
||
|
iter.events = nil
|
||
|
iter.nexttoken = nil
|
||
|
iter.err = err
|
||
|
}
|
||
|
|
||
|
if iter.nextEventIndex < len(iter.events) || iter.err != nil {
|
||
|
return true
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
func (iter *historyEventIteratorImpl) Next() (*historypb.HistoryEvent, error) {
|
||
|
// if caller call the Next() when iteration is over, just return nil, nil
|
||
|
if !iter.HasNext() {
|
||
|
panic("HistoryEventIterator Next() called without checking HasNext()")
|
||
|
}
|
||
|
|
||
|
// we have cached events
|
||
|
if iter.nextEventIndex < len(iter.events) {
|
||
|
index := iter.nextEventIndex
|
||
|
iter.nextEventIndex++
|
||
|
return iter.events[index], nil
|
||
|
} else if iter.err != nil {
|
||
|
// we have err, clear that iter.err and return err
|
||
|
err := iter.err
|
||
|
iter.err = nil
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
panic("HistoryEventIterator Next() should return either a history event or a err")
|
||
|
}
|
||
|
|
||
|
func (workflowRun *workflowRunImpl) GetRunID() string {
|
||
|
return workflowRun.currentRunID.Get()
|
||
|
}
|
||
|
|
||
|
func (workflowRun *workflowRunImpl) GetID() string {
|
||
|
return workflowRun.workflowID
|
||
|
}
|
||
|
|
||
|
func (workflowRun *workflowRunImpl) Get(ctx context.Context, valuePtr interface{}) error {
|
||
|
|
||
|
iter := workflowRun.iterFn(ctx, workflowRun.currentRunID.Get())
|
||
|
if !iter.HasNext() {
|
||
|
panic("could not get last history event for workflow")
|
||
|
}
|
||
|
closeEvent, err := iter.Next()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
switch closeEvent.GetEventType() {
|
||
|
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED:
|
||
|
attributes := closeEvent.GetWorkflowExecutionCompletedEventAttributes()
|
||
|
if attributes.NewExecutionRunId != "" {
|
||
|
return workflowRun.follow(ctx, valuePtr, attributes.NewExecutionRunId)
|
||
|
}
|
||
|
if valuePtr == nil || attributes.Result == nil {
|
||
|
return nil
|
||
|
}
|
||
|
rf := reflect.ValueOf(valuePtr)
|
||
|
if rf.Type().Kind() != reflect.Ptr {
|
||
|
return errors.New("value parameter is not a pointer")
|
||
|
}
|
||
|
return workflowRun.dataConverter.FromPayloads(attributes.Result, valuePtr)
|
||
|
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_FAILED:
|
||
|
attributes := closeEvent.GetWorkflowExecutionFailedEventAttributes()
|
||
|
if attributes.NewExecutionRunId != "" {
|
||
|
return workflowRun.follow(ctx, valuePtr, attributes.NewExecutionRunId)
|
||
|
}
|
||
|
err = ConvertFailureToError(attributes.GetFailure(), workflowRun.dataConverter)
|
||
|
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_CANCELED:
|
||
|
attributes := closeEvent.GetWorkflowExecutionCanceledEventAttributes()
|
||
|
details := newEncodedValues(attributes.Details, workflowRun.dataConverter)
|
||
|
err = NewCanceledError(details)
|
||
|
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_TERMINATED:
|
||
|
err = newTerminatedError()
|
||
|
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_TIMED_OUT:
|
||
|
attributes := closeEvent.GetWorkflowExecutionTimedOutEventAttributes()
|
||
|
if attributes.NewExecutionRunId != "" {
|
||
|
return workflowRun.follow(ctx, valuePtr, attributes.NewExecutionRunId)
|
||
|
}
|
||
|
err = NewTimeoutError("Workflow timeout", enumspb.TIMEOUT_TYPE_START_TO_CLOSE, nil)
|
||
|
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_CONTINUED_AS_NEW:
|
||
|
attributes := closeEvent.GetWorkflowExecutionContinuedAsNewEventAttributes()
|
||
|
return workflowRun.follow(ctx, valuePtr, attributes.NewExecutionRunId)
|
||
|
default:
|
||
|
return fmt.Errorf("unexpected event type %s when handling workflow execution result", closeEvent.GetEventType())
|
||
|
}
|
||
|
|
||
|
err = NewWorkflowExecutionError(
|
||
|
workflowRun.workflowID,
|
||
|
workflowRun.currentRunID.Get(),
|
||
|
workflowRun.workflowType,
|
||
|
err)
|
||
|
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// follow is used by Get to follow a chain of executions linked by NewExecutionRunId, so that Get
|
||
|
// doesn't return until the chain finishes. These can be ContinuedAsNew events, Completed events
|
||
|
// (for workflows with a cron schedule), or Failed or TimedOut events (for workflows with a retry
|
||
|
// policy or cron schedule).
|
||
|
func (workflowRun *workflowRunImpl) follow(ctx context.Context, valuePtr interface{}, newRunID string) error {
|
||
|
curRunID := util.PopulatedOnceCell(newRunID)
|
||
|
workflowRun.currentRunID = &curRunID
|
||
|
return workflowRun.Get(ctx, valuePtr)
|
||
|
}
|
||
|
|
||
|
func getWorkflowMemo(input map[string]interface{}, dc converter.DataConverter) (*commonpb.Memo, error) {
|
||
|
if input == nil {
|
||
|
return nil, nil
|
||
|
}
|
||
|
|
||
|
memo := make(map[string]*commonpb.Payload)
|
||
|
for k, v := range input {
|
||
|
// TODO (shtin): use dc here???
|
||
|
memoBytes, err := converter.GetDefaultDataConverter().ToPayload(v)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("encode workflow memo error: %v", err.Error())
|
||
|
}
|
||
|
memo[k] = memoBytes
|
||
|
}
|
||
|
return &commonpb.Memo{Fields: memo}, nil
|
||
|
}
|
||
|
|
||
|
func serializeSearchAttributes(input map[string]interface{}) (*commonpb.SearchAttributes, error) {
|
||
|
if input == nil {
|
||
|
return nil, nil
|
||
|
}
|
||
|
|
||
|
attr := make(map[string]*commonpb.Payload)
|
||
|
for k, v := range input {
|
||
|
attrBytes, err := converter.GetDefaultDataConverter().ToPayload(v)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("encode search attribute [%s] error: %v", k, err)
|
||
|
}
|
||
|
attr[k] = attrBytes
|
||
|
}
|
||
|
return &commonpb.SearchAttributes{IndexedFields: attr}, nil
|
||
|
}
|
||
|
|
||
|
type workflowClientInterceptor struct{ client *WorkflowClient }
|
||
|
|
||
|
func (w *workflowClientInterceptor) ExecuteWorkflow(
|
||
|
ctx context.Context,
|
||
|
in *ClientExecuteWorkflowInput,
|
||
|
) (WorkflowRun, error) {
|
||
|
// This is always set before interceptor is invoked
|
||
|
workflowID := in.Options.ID
|
||
|
if workflowID == "" {
|
||
|
return nil, fmt.Errorf("no workflow ID in options")
|
||
|
}
|
||
|
|
||
|
executionTimeout := in.Options.WorkflowExecutionTimeout
|
||
|
runTimeout := in.Options.WorkflowRunTimeout
|
||
|
workflowTaskTimeout := in.Options.WorkflowTaskTimeout
|
||
|
|
||
|
dataConverter := WithContext(ctx, w.client.dataConverter)
|
||
|
if dataConverter == nil {
|
||
|
dataConverter = converter.GetDefaultDataConverter()
|
||
|
}
|
||
|
|
||
|
// Encode input
|
||
|
input, err := encodeArgs(dataConverter, in.Args)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
memo, err := getWorkflowMemo(in.Options.Memo, dataConverter)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
searchAttr, err := serializeSearchAttributes(in.Options.SearchAttributes)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
// get workflow headers from the context
|
||
|
header, err := headerPropagated(ctx, w.client.contextPropagators)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
// run propagators to extract information about tracing and other stuff, store in headers field
|
||
|
startRequest := &workflowservice.StartWorkflowExecutionRequest{
|
||
|
Namespace: w.client.namespace,
|
||
|
RequestId: uuid.New(),
|
||
|
WorkflowId: workflowID,
|
||
|
WorkflowType: &commonpb.WorkflowType{Name: in.WorkflowType},
|
||
|
TaskQueue: &taskqueuepb.TaskQueue{Name: in.Options.TaskQueue, Kind: enumspb.TASK_QUEUE_KIND_NORMAL},
|
||
|
Input: input,
|
||
|
WorkflowExecutionTimeout: &executionTimeout,
|
||
|
WorkflowRunTimeout: &runTimeout,
|
||
|
WorkflowTaskTimeout: &workflowTaskTimeout,
|
||
|
Identity: w.client.identity,
|
||
|
WorkflowIdReusePolicy: in.Options.WorkflowIDReusePolicy,
|
||
|
RetryPolicy: convertToPBRetryPolicy(in.Options.RetryPolicy),
|
||
|
CronSchedule: in.Options.CronSchedule,
|
||
|
Memo: memo,
|
||
|
SearchAttributes: searchAttr,
|
||
|
Header: header,
|
||
|
}
|
||
|
|
||
|
var response *workflowservice.StartWorkflowExecutionResponse
|
||
|
|
||
|
grpcCtx, cancel := newGRPCContext(ctx, grpcMetricsHandler(
|
||
|
w.client.metricsHandler.WithTags(metrics.RPCTags(in.WorkflowType, metrics.NoneTagValue, in.Options.TaskQueue))),
|
||
|
defaultGrpcRetryParameters(ctx))
|
||
|
defer cancel()
|
||
|
|
||
|
response, err = w.client.workflowService.StartWorkflowExecution(grpcCtx, startRequest)
|
||
|
|
||
|
// Allow already-started error
|
||
|
var runID string
|
||
|
if e, ok := err.(*serviceerror.WorkflowExecutionAlreadyStarted); ok && !in.Options.WorkflowExecutionErrorWhenAlreadyStarted {
|
||
|
runID = e.RunId
|
||
|
} else if err != nil {
|
||
|
return nil, err
|
||
|
} else {
|
||
|
runID = response.RunId
|
||
|
}
|
||
|
|
||
|
iterFn := func(fnCtx context.Context, fnRunID string) HistoryEventIterator {
|
||
|
metricsHandler := w.client.metricsHandler.WithTags(metrics.RPCTags(in.WorkflowType,
|
||
|
metrics.NoneTagValue, in.Options.TaskQueue))
|
||
|
return w.client.getWorkflowHistory(fnCtx, workflowID, fnRunID, true,
|
||
|
enumspb.HISTORY_EVENT_FILTER_TYPE_CLOSE_EVENT, metricsHandler)
|
||
|
}
|
||
|
|
||
|
curRunIDCell := util.PopulatedOnceCell(runID)
|
||
|
return &workflowRunImpl{
|
||
|
workflowType: in.WorkflowType,
|
||
|
workflowID: workflowID,
|
||
|
firstRunID: runID,
|
||
|
currentRunID: &curRunIDCell,
|
||
|
iterFn: iterFn,
|
||
|
dataConverter: w.client.dataConverter,
|
||
|
registry: w.client.registry,
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
func (w *workflowClientInterceptor) SignalWorkflow(ctx context.Context, in *ClientSignalWorkflowInput) error {
|
||
|
dataConverter := WithContext(ctx, w.client.dataConverter)
|
||
|
input, err := encodeArg(dataConverter, in.Arg)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// get workflow headers from the context
|
||
|
header, err := headerPropagated(ctx, w.client.contextPropagators)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
request := &workflowservice.SignalWorkflowExecutionRequest{
|
||
|
Namespace: w.client.namespace,
|
||
|
RequestId: uuid.New(),
|
||
|
WorkflowExecution: &commonpb.WorkflowExecution{
|
||
|
WorkflowId: in.WorkflowID,
|
||
|
RunId: in.RunID,
|
||
|
},
|
||
|
SignalName: in.SignalName,
|
||
|
Input: input,
|
||
|
Identity: w.client.identity,
|
||
|
Header: header,
|
||
|
}
|
||
|
|
||
|
grpcCtx, cancel := newGRPCContext(ctx, defaultGrpcRetryParameters(ctx))
|
||
|
defer cancel()
|
||
|
_, err = w.client.workflowService.SignalWorkflowExecution(grpcCtx, request)
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
func (w *workflowClientInterceptor) SignalWithStartWorkflow(
|
||
|
ctx context.Context,
|
||
|
in *ClientSignalWithStartWorkflowInput,
|
||
|
) (WorkflowRun, error) {
|
||
|
|
||
|
dataConverter := WithContext(ctx, w.client.dataConverter)
|
||
|
signalInput, err := encodeArg(dataConverter, in.SignalArg)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
executionTimeout := in.Options.WorkflowExecutionTimeout
|
||
|
runTimeout := in.Options.WorkflowRunTimeout
|
||
|
taskTimeout := in.Options.WorkflowTaskTimeout
|
||
|
|
||
|
// Encode input
|
||
|
input, err := encodeArgs(dataConverter, in.Args)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
memo, err := getWorkflowMemo(in.Options.Memo, dataConverter)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
searchAttr, err := serializeSearchAttributes(in.Options.SearchAttributes)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
// get workflow headers from the context
|
||
|
header, err := headerPropagated(ctx, w.client.contextPropagators)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
signalWithStartRequest := &workflowservice.SignalWithStartWorkflowExecutionRequest{
|
||
|
Namespace: w.client.namespace,
|
||
|
RequestId: uuid.New(),
|
||
|
WorkflowId: in.Options.ID,
|
||
|
WorkflowType: &commonpb.WorkflowType{Name: in.WorkflowType},
|
||
|
TaskQueue: &taskqueuepb.TaskQueue{Name: in.Options.TaskQueue, Kind: enumspb.TASK_QUEUE_KIND_NORMAL},
|
||
|
Input: input,
|
||
|
WorkflowExecutionTimeout: &executionTimeout,
|
||
|
WorkflowRunTimeout: &runTimeout,
|
||
|
WorkflowTaskTimeout: &taskTimeout,
|
||
|
SignalName: in.SignalName,
|
||
|
SignalInput: signalInput,
|
||
|
Identity: w.client.identity,
|
||
|
RetryPolicy: convertToPBRetryPolicy(in.Options.RetryPolicy),
|
||
|
CronSchedule: in.Options.CronSchedule,
|
||
|
Memo: memo,
|
||
|
SearchAttributes: searchAttr,
|
||
|
WorkflowIdReusePolicy: in.Options.WorkflowIDReusePolicy,
|
||
|
Header: header,
|
||
|
}
|
||
|
|
||
|
var response *workflowservice.SignalWithStartWorkflowExecutionResponse
|
||
|
|
||
|
// Start creating workflow request.
|
||
|
grpcCtx, cancel := newGRPCContext(ctx, defaultGrpcRetryParameters(ctx))
|
||
|
defer cancel()
|
||
|
|
||
|
response, err = w.client.workflowService.SignalWithStartWorkflowExecution(grpcCtx, signalWithStartRequest)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
iterFn := func(fnCtx context.Context, fnRunID string) HistoryEventIterator {
|
||
|
metricsHandler := w.client.metricsHandler.WithTags(metrics.RPCTags(in.WorkflowType,
|
||
|
metrics.NoneTagValue, in.Options.TaskQueue))
|
||
|
return w.client.getWorkflowHistory(fnCtx, in.Options.ID, fnRunID, true,
|
||
|
enumspb.HISTORY_EVENT_FILTER_TYPE_CLOSE_EVENT, metricsHandler)
|
||
|
}
|
||
|
|
||
|
curRunIDCell := util.PopulatedOnceCell(response.GetRunId())
|
||
|
return &workflowRunImpl{
|
||
|
workflowType: in.WorkflowType,
|
||
|
workflowID: in.Options.ID,
|
||
|
firstRunID: response.GetRunId(),
|
||
|
currentRunID: &curRunIDCell,
|
||
|
iterFn: iterFn,
|
||
|
dataConverter: w.client.dataConverter,
|
||
|
registry: w.client.registry,
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
func (w *workflowClientInterceptor) CancelWorkflow(ctx context.Context, in *ClientCancelWorkflowInput) error {
|
||
|
request := &workflowservice.RequestCancelWorkflowExecutionRequest{
|
||
|
Namespace: w.client.namespace,
|
||
|
RequestId: uuid.New(),
|
||
|
WorkflowExecution: &commonpb.WorkflowExecution{
|
||
|
WorkflowId: in.WorkflowID,
|
||
|
RunId: in.RunID,
|
||
|
},
|
||
|
Identity: w.client.identity,
|
||
|
}
|
||
|
grpcCtx, cancel := newGRPCContext(ctx, defaultGrpcRetryParameters(ctx))
|
||
|
defer cancel()
|
||
|
_, err := w.client.workflowService.RequestCancelWorkflowExecution(grpcCtx, request)
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
func (w *workflowClientInterceptor) TerminateWorkflow(ctx context.Context, in *ClientTerminateWorkflowInput) error {
|
||
|
datailsPayload, err := w.client.dataConverter.ToPayloads(in.Details...)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
request := &workflowservice.TerminateWorkflowExecutionRequest{
|
||
|
Namespace: w.client.namespace,
|
||
|
WorkflowExecution: &commonpb.WorkflowExecution{
|
||
|
WorkflowId: in.WorkflowID,
|
||
|
RunId: in.RunID,
|
||
|
},
|
||
|
Reason: in.Reason,
|
||
|
Identity: w.client.identity,
|
||
|
Details: datailsPayload,
|
||
|
}
|
||
|
|
||
|
grpcCtx, cancel := newGRPCContext(ctx, defaultGrpcRetryParameters(ctx))
|
||
|
defer cancel()
|
||
|
_, err = w.client.workflowService.TerminateWorkflowExecution(grpcCtx, request)
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
func (w *workflowClientInterceptor) QueryWorkflow(
|
||
|
ctx context.Context,
|
||
|
in *ClientQueryWorkflowInput,
|
||
|
) (converter.EncodedValue, error) {
|
||
|
// get workflow headers from the context
|
||
|
header, err := headerPropagated(ctx, w.client.contextPropagators)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
result, err := w.client.QueryWorkflowWithOptions(ctx, &QueryWorkflowWithOptionsRequest{
|
||
|
WorkflowID: in.WorkflowID,
|
||
|
RunID: in.RunID,
|
||
|
QueryType: in.QueryType,
|
||
|
Args: in.Args,
|
||
|
Header: header,
|
||
|
})
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return result.QueryResult, nil
|
||
|
}
|
||
|
|
||
|
// Required to implement ClientOutboundInterceptor
|
||
|
func (*workflowClientInterceptor) mustEmbedClientOutboundInterceptorBase() {}
|