Handling activity schedule-to-close timeouts

Hello,
I have a use case for catching schedule-to-close timeouts and noticed some peculiar behaviour on the errors being returned.

Say for example the schedule-to-close timeout is set to 10s. If the activity exceeds 10s in execution time, it returns a TimeoutError with a schedule-to-close timeout type. This is what I’m expecting and checking for in my activity error handling.

However, let’s say the activity executes very quickly but always returns an error, using the default retry policy. Now if I only check for the TimeoutError, the workflow fails with an ActivityError instead (because the next retry interval would already exceed the 10s schedule-to-close timeout, Temporal immediately terminates the workflow).

So in effect, to correctly catch the schedule-to-close timeout, this is what I’m using:

if err != nil {
	var actError *temporal.ActivityError
	var timeoutError *temporal.TimeoutError
	if (errors.As(err, &actError) && actError.RetryState() == enums.RETRY_STATE_TIMEOUT) ||
		(errors.As(err, &timeoutError) && timeoutError.TimeoutType() == enums.TIMEOUT_TYPE_SCHEDULE_TO_CLOSE) {
		// handle schedule-to-close timeout
	}
}

Which is a little clunky and unintuitive.

Is this the expected way to check for schedule-to-close timeouts? I would’ve thought that it makes more sense returning the schedule-to-close TimeoutError in all cases where the activity doesn’t continue as a result of schedule-to-close timeout, instead of having to check for a separate ActivityError with a timeout retry state as well.

1 Like

for me its, always returning activityError, but its suppose to be timeoutError
@maxim . Would u be able to help?

Activity error wraps up the actual error. So you have to unwrap it.

Sorry to inform, unwrap part is just holding the error string, not timeout Error. i try to log on every step, but i reach only activity error. Rest is skipped
@maxim

		if errors.As(err, &actError) {
			newErr := actError.Unwrap()
			workflow.GetLogger(ctx).Error(fmt.Sprintf("activity error: %v", newErr))
			if temporal.IsTimeoutError(newErr) {
				workflow.GetLogger(ctx).Error("timeout error")
				var timeout2Err *temporal.TimeoutError
				timeout2Err = newErr.(*temporal.TimeoutError)
				if timeout2Err.TimeoutType() == enums.TIMEOUT_TYPE_SCHEDULE_TO_CLOSE {
					workflow.GetLogger(ctx).Error("timeout error: TIMEOUT_TYPE_SCHEDULE_TO_CLOSE")
				}
			}
		}

The following works:

	if err != nil && temporal.IsTimeoutError(err) {
		var timeoutError *temporal.TimeoutError
		errors.As(err, &timeoutError)
		logger.Info("HelloWorld schedule to close timeout.", "result", timeoutError.TimeoutType())
	}