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 }