go.uber.org/cadence@v1.2.9/internal/activity.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  //
     5  // Permission is hereby granted, free of charge, to any person obtaining a copy
     6  // of this software and associated documentation files (the "Software"), to deal
     7  // in the Software without restriction, including without limitation the rights
     8  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     9  // copies of the Software, and to permit persons to whom the Software is
    10  // furnished to do so, subject to the following conditions:
    11  //
    12  // The above copyright notice and this permission notice shall be included in
    13  // all copies or substantial portions of the Software.
    14  //
    15  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    16  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    17  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    18  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    19  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    20  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    21  // THE SOFTWARE.
    22  
    23  package internal
    24  
    25  import (
    26  	"context"
    27  	"time"
    28  
    29  	"github.com/opentracing/opentracing-go"
    30  	"github.com/uber-go/tally"
    31  	"go.uber.org/zap"
    32  	"go.uber.org/zap/zapcore"
    33  
    34  	"go.uber.org/cadence/.gen/go/shared"
    35  )
    36  
    37  type (
    38  	// ActivityType identifies a activity type.
    39  	ActivityType struct {
    40  		Name string
    41  	}
    42  
    43  	// ActivityInfo contains information about currently executing activity.
    44  	ActivityInfo struct {
    45  		TaskToken          []byte
    46  		WorkflowType       *WorkflowType
    47  		WorkflowDomain     string
    48  		WorkflowExecution  WorkflowExecution
    49  		ActivityID         string
    50  		ActivityType       ActivityType
    51  		TaskList           string
    52  		HeartbeatTimeout   time.Duration // Maximum time between heartbeats. 0 means no heartbeat needed.
    53  		ScheduledTimestamp time.Time     // Time of activity scheduled by a workflow
    54  		StartedTimestamp   time.Time     // Time of activity start
    55  		Deadline           time.Time     // Time of activity timeout
    56  		Attempt            int32         // Attempt starts from 0, and increased by 1 for every retry if retry policy is specified.
    57  	}
    58  
    59  	// RegisterActivityOptions consists of options for registering an activity
    60  	RegisterActivityOptions struct {
    61  		// When an activity is a function the name is an actual activity type name.
    62  		// When an activity is part of a structure then each member of the structure becomes an activity with
    63  		// this Name as a prefix + activity function name.
    64  		Name string
    65  		// Activity type name is equal to function name instead of fully qualified
    66  		// name including function package (and struct type if used).
    67  		// This option has no effect when explicit Name is provided.
    68  		EnableShortName               bool
    69  		DisableAlreadyRegisteredCheck bool
    70  		// Automatically send heartbeats for this activity at an interval that is less than the HeartbeatTimeout.
    71  		// This option has no effect if the activity is executed with a HeartbeatTimeout of 0.
    72  		// Default: false
    73  		EnableAutoHeartbeat bool
    74  	}
    75  
    76  	// ActivityOptions stores all activity-specific parameters that will be stored inside of a context.
    77  	// The current timeout resolution implementation is in seconds and uses math.Ceil(d.Seconds()) as the duration. But is
    78  	// subjected to change in the future.
    79  	ActivityOptions struct {
    80  		// TaskList that the activity needs to be scheduled on.
    81  		// optional: The default task list with the same name as the workflow task list.
    82  		TaskList string
    83  
    84  		// ScheduleToCloseTimeout - The end to end timeout for the activity needed.
    85  		// The zero value of this uses default value.
    86  		// Optional: The default value is the sum of ScheduleToStartTimeout and StartToCloseTimeout
    87  		ScheduleToCloseTimeout time.Duration
    88  
    89  		// ScheduleToStartTimeout - The queue timeout before the activity starts executed.
    90  		// Mandatory: No default.
    91  		ScheduleToStartTimeout time.Duration
    92  
    93  		// StartToCloseTimeout - The timeout from the start of execution to end of it.
    94  		// Mandatory: No default.
    95  		StartToCloseTimeout time.Duration
    96  
    97  		// HeartbeatTimeout - The periodic timeout while the activity is in execution. This is
    98  		// the max interval the server needs to hear at-least one ping from the activity.
    99  		// Optional: Default zero, means no heart beating is needed.
   100  		HeartbeatTimeout time.Duration
   101  
   102  		// WaitForCancellation - Whether to wait for cancelled activity to be completed(
   103  		// activity can be failed, completed, cancel accepted)
   104  		// Optional: default false
   105  		WaitForCancellation bool
   106  
   107  		// ActivityID - Business level activity ID, this is not needed for most of the cases if you have
   108  		// to specify this then talk to cadence team. This is something will be done in future.
   109  		// Optional: default empty string
   110  		ActivityID string
   111  
   112  		// RetryPolicy specify how to retry activity if error happens. When RetryPolicy.ExpirationInterval is specified
   113  		// and it is larger than the activity's ScheduleToStartTimeout, then the ExpirationInterval will override activity's
   114  		// ScheduleToStartTimeout. This is to avoid retrying on ScheduleToStartTimeout error which only happen when worker
   115  		// is not picking up the task within the timeout. Retrying ScheduleToStartTimeout does not make sense as it just
   116  		// mark the task as failed and create a new task and put back in the queue waiting worker to pick again. Cadence
   117  		// server also make sure the ScheduleToStartTimeout will not be larger than the workflow's timeout.
   118  		// Same apply to ScheduleToCloseTimeout. See more details about RetryPolicy on the doc for RetryPolicy.
   119  		// Optional: default is no retry
   120  		RetryPolicy *RetryPolicy
   121  	}
   122  
   123  	// LocalActivityOptions stores local activity specific parameters that will be stored inside of a context.
   124  	LocalActivityOptions struct {
   125  		// ScheduleToCloseTimeout - The end to end timeout for the local activity.
   126  		// This field is required.
   127  		ScheduleToCloseTimeout time.Duration
   128  
   129  		// RetryPolicy specify how to retry activity if error happens.
   130  		// Optional: default is no retry
   131  		RetryPolicy *RetryPolicy
   132  	}
   133  )
   134  
   135  // RegisterActivity - register an activity function or a pointer to a structure with the framework.
   136  // The public form is: activity.Register(...)
   137  // An activity function takes a context and input and returns a (result, error) or just error.
   138  //
   139  // And activity struct is a structure with all its exported methods treated as activities. The default
   140  // name of each activity is the <structure name>_<method name>. Use RegisterActivityWithOptions to override the
   141  // "<structure name>_" prefix.
   142  //
   143  // Examples:
   144  //
   145  //		func sampleActivity(ctx context.Context, input []byte) (result []byte, err error)
   146  //		func sampleActivity(ctx context.Context, arg1 int, arg2 string) (result *customerStruct, err error)
   147  //		func sampleActivity(ctx context.Context) (err error)
   148  //		func sampleActivity() (result string, err error)
   149  //		func sampleActivity(arg1 bool) (result int, err error)
   150  //		func sampleActivity(arg1 bool) (err error)
   151  //
   152  //	 type Activities struct {
   153  //	    // fields
   154  //	 }
   155  //	 func (a *Activities) SampleActivity1(ctx context.Context, arg1 int, arg2 string) (result *customerStruct, err error) {
   156  //	   ...
   157  //	 }
   158  //
   159  //	 func (a *Activities) SampleActivity2(ctx context.Context, arg1 int, arg2 *customerStruct) (result string, err error) {
   160  //	   ...
   161  //	 }
   162  //
   163  // Serialization of all primitive types, structures is supported ... except channels, functions, variadic, unsafe pointer.
   164  // This method calls panic if activityFunc doesn't comply with the expected format.
   165  // Deprecated: Global activity registration methods are replaced by equivalent Worker instance methods.
   166  // This method is kept to maintain backward compatibility and should not be used.
   167  func RegisterActivity(activityFunc interface{}) {
   168  	RegisterActivityWithOptions(activityFunc, RegisterActivityOptions{})
   169  }
   170  
   171  // RegisterActivityWithOptions registers the activity function or struct pointer with options.
   172  // The public form is: activity.RegisterWithOptions(...)
   173  // The user can use options to provide an external name for the activity or leave it empty if no
   174  // external name is required. This can be used as
   175  //
   176  //	activity.RegisterWithOptions(barActivity, RegisterActivityOptions{})
   177  //	activity.RegisterWithOptions(barActivity, RegisterActivityOptions{Name: "barExternal"})
   178  //
   179  // When registering the structure that implements activities the name is used as a prefix that is
   180  // prepended to the activity method name.
   181  //
   182  //	activity.RegisterWithOptions(&Activities{ ... }, RegisterActivityOptions{Name: "MyActivities_"})
   183  //
   184  // To override each name of activities defined through a structure register the methods one by one:
   185  // activities := &Activities{ ... }
   186  // activity.RegisterWithOptions(activities.SampleActivity1, RegisterActivityOptions{Name: "Sample1"})
   187  // activity.RegisterWithOptions(activities.SampleActivity2, RegisterActivityOptions{Name: "Sample2"})
   188  // See RegisterActivity function for more info.
   189  // The other use of options is to disable duplicated activity registration check
   190  // which might be useful for integration tests.
   191  // activity.RegisterWithOptions(barActivity, RegisterActivityOptions{DisableAlreadyRegisteredCheck: true})
   192  // Deprecated: Global activity registration methods are replaced by equivalent Worker instance methods.
   193  // This method is kept to maintain backward compatibility and should not be used.
   194  func RegisterActivityWithOptions(activityFunc interface{}, opts RegisterActivityOptions) {
   195  	registry := getGlobalRegistry()
   196  	registry.RegisterActivityWithOptions(activityFunc, opts)
   197  }
   198  
   199  // GetActivityInfo returns information about currently executing activity.
   200  func GetActivityInfo(ctx context.Context) ActivityInfo {
   201  	env := getActivityEnv(ctx)
   202  	return ActivityInfo{
   203  		ActivityID:         env.activityID,
   204  		ActivityType:       env.activityType,
   205  		TaskToken:          env.taskToken,
   206  		WorkflowExecution:  env.workflowExecution,
   207  		HeartbeatTimeout:   env.heartbeatTimeout,
   208  		Deadline:           env.deadline,
   209  		ScheduledTimestamp: env.scheduledTimestamp,
   210  		StartedTimestamp:   env.startedTimestamp,
   211  		TaskList:           env.taskList,
   212  		Attempt:            env.attempt,
   213  		WorkflowType:       env.workflowType,
   214  		WorkflowDomain:     env.workflowDomain,
   215  	}
   216  }
   217  
   218  // HasHeartbeatDetails checks if there is heartbeat details from last attempt.
   219  func HasHeartbeatDetails(ctx context.Context) bool {
   220  	env := getActivityEnv(ctx)
   221  	return len(env.heartbeatDetails) > 0
   222  }
   223  
   224  // GetHeartbeatDetails extract heartbeat details from last failed attempt. This is used in combination with retry policy.
   225  // An activity could be scheduled with an optional retry policy on ActivityOptions. If the activity failed then server
   226  // would attempt to dispatch another activity task to retry according to the retry policy. If there was heartbeat
   227  // details reported by activity from the failed attempt, the details would be delivered along with the activity task for
   228  // retry attempt. Activity could extract the details by GetHeartbeatDetails() and resume from the progress.
   229  func GetHeartbeatDetails(ctx context.Context, d ...interface{}) error {
   230  	env := getActivityEnv(ctx)
   231  	if len(env.heartbeatDetails) == 0 {
   232  		return ErrNoData
   233  	}
   234  	encoded := newEncodedValues(env.heartbeatDetails, env.dataConverter)
   235  	return encoded.Get(d...)
   236  }
   237  
   238  // GetActivityLogger returns a logger that can be used in activity
   239  func GetActivityLogger(ctx context.Context) *zap.Logger {
   240  	env := getActivityEnv(ctx)
   241  	return env.logger
   242  }
   243  
   244  // GetActivityMetricsScope returns a metrics scope that can be used in activity
   245  func GetActivityMetricsScope(ctx context.Context) tally.Scope {
   246  	env := getActivityEnv(ctx)
   247  	return env.metricsScope
   248  }
   249  
   250  // GetWorkerStopChannel returns a read-only channel. The closure of this channel indicates the activity worker is stopping.
   251  // When the worker is stopping, it will close this channel and wait until the worker stop timeout finishes. After the timeout
   252  // hit, the worker will cancel the activity context and then exit. The timeout can be defined by worker option: WorkerStopTimeout.
   253  // Use this channel to handle activity graceful exit when the activity worker stops.
   254  func GetWorkerStopChannel(ctx context.Context) <-chan struct{} {
   255  	env := getActivityEnv(ctx)
   256  	return env.workerStopChannel
   257  }
   258  
   259  // RecordActivityHeartbeat sends heartbeat for the currently executing activity
   260  // If the activity is either cancelled (or) workflow/activity doesn't exist then we would cancel
   261  // the context with error context.Canceled.
   262  //
   263  //	TODO: we don't have a way to distinguish between the two cases when context is cancelled because
   264  //	context doesn't support overriding value of ctx.Error.
   265  //	TODO: Implement automatic heartbeating with cancellation through ctx.
   266  //
   267  // details - the details that you provided here can be seen in the workflow when it receives TimeoutError, you
   268  // can check error TimeoutType()/Details().
   269  func RecordActivityHeartbeat(ctx context.Context, details ...interface{}) {
   270  	env := getActivityEnv(ctx)
   271  	if env.isLocalActivity {
   272  		// no-op for local activity
   273  		return
   274  	}
   275  	var data []byte
   276  	var err error
   277  	// We would like to be a able to pass in "nil" as part of details(that is no progress to report to)
   278  	if len(details) != 1 || details[0] != nil {
   279  		data, err = encodeArgs(getDataConverterFromActivityCtx(ctx), details)
   280  		if err != nil {
   281  			panic(err)
   282  		}
   283  	}
   284  	err = env.serviceInvoker.BatchHeartbeat(data)
   285  	if err != nil {
   286  		log := GetActivityLogger(ctx)
   287  		log.Debug("RecordActivityHeartbeat With Error:", zap.Error(err))
   288  	}
   289  }
   290  
   291  // ServiceInvoker abstracts calls to the Cadence service from an activity implementation.
   292  // Implement to unit test activities.
   293  type ServiceInvoker interface {
   294  	// All the heartbeat methods will return ActivityTaskCanceledError if activity is cancelled.
   295  	// Heartbeat sends a record heartbeat request to Cadence server directly without buffering.
   296  	// It should only be used by the sessions framework.
   297  	Heartbeat(details []byte) error
   298  	// BatchHeartbeat sends heartbeat on the first attempt, and batches additional requests
   299  	// to send it later according to heartbeat timeout.
   300  	BatchHeartbeat(details []byte) error
   301  	// BackgroundHeartbeat should only be used by Cadence library internally to heartbeat automatically
   302  	// without detail.
   303  	BackgroundHeartbeat() error
   304  	Close(flushBufferedHeartbeat bool)
   305  
   306  	SignalWorkflow(ctx context.Context, domain, workflowID, runID, signalName string, signalInput []byte) error
   307  }
   308  
   309  // WithActivityTask adds activity specific information into context.
   310  // Use this method to unit test activity implementations that use context extractor methodshared.
   311  func WithActivityTask(
   312  	ctx context.Context,
   313  	task *shared.PollForActivityTaskResponse,
   314  	taskList string,
   315  	invoker ServiceInvoker,
   316  	logger *zap.Logger,
   317  	scope tally.Scope,
   318  	dataConverter DataConverter,
   319  	workerStopChannel <-chan struct{},
   320  	contextPropagators []ContextPropagator,
   321  	tracer opentracing.Tracer,
   322  ) context.Context {
   323  	var deadline time.Time
   324  	scheduled := time.Unix(0, task.GetScheduledTimestampOfThisAttempt())
   325  	started := time.Unix(0, task.GetStartedTimestamp())
   326  	scheduleToCloseTimeout := time.Duration(task.GetScheduleToCloseTimeoutSeconds()) * time.Second
   327  	startToCloseTimeout := time.Duration(task.GetStartToCloseTimeoutSeconds()) * time.Second
   328  	heartbeatTimeout := time.Duration(task.GetHeartbeatTimeoutSeconds()) * time.Second
   329  	scheduleToCloseDeadline := scheduled.Add(scheduleToCloseTimeout)
   330  	startToCloseDeadline := started.Add(startToCloseTimeout)
   331  	// Minimum of the two deadlines.
   332  	if scheduleToCloseDeadline.Before(startToCloseDeadline) {
   333  		deadline = scheduleToCloseDeadline
   334  	} else {
   335  		deadline = startToCloseDeadline
   336  	}
   337  
   338  	// keep in sync with local activity logger tags
   339  	logger = logger.With(
   340  		zapcore.Field{Key: tagActivityID, Type: zapcore.StringType, String: *task.ActivityId},
   341  		zapcore.Field{Key: tagActivityType, Type: zapcore.StringType, String: *task.ActivityType.Name},
   342  		zapcore.Field{Key: tagWorkflowType, Type: zapcore.StringType, String: *task.WorkflowType.Name},
   343  		zapcore.Field{Key: tagWorkflowID, Type: zapcore.StringType, String: *task.WorkflowExecution.WorkflowId},
   344  		zapcore.Field{Key: tagRunID, Type: zapcore.StringType, String: *task.WorkflowExecution.RunId},
   345  	)
   346  
   347  	return context.WithValue(ctx, activityEnvContextKey, &activityEnvironment{
   348  		taskToken:      task.TaskToken,
   349  		serviceInvoker: invoker,
   350  		activityType:   ActivityType{Name: *task.ActivityType.Name},
   351  		activityID:     *task.ActivityId,
   352  		workflowExecution: WorkflowExecution{
   353  			RunID: *task.WorkflowExecution.RunId,
   354  			ID:    *task.WorkflowExecution.WorkflowId},
   355  		logger:             logger,
   356  		metricsScope:       scope,
   357  		deadline:           deadline,
   358  		heartbeatTimeout:   heartbeatTimeout,
   359  		scheduledTimestamp: scheduled,
   360  		startedTimestamp:   started,
   361  		taskList:           taskList,
   362  		dataConverter:      dataConverter,
   363  		attempt:            task.GetAttempt(),
   364  		heartbeatDetails:   task.HeartbeatDetails,
   365  		workflowType: &WorkflowType{
   366  			Name: *task.WorkflowType.Name,
   367  		},
   368  		workflowDomain:     *task.WorkflowDomain,
   369  		workerStopChannel:  workerStopChannel,
   370  		contextPropagators: contextPropagators,
   371  		tracer:             tracer,
   372  	})
   373  }