mirror of
https://github.com/hanzoai/tasks.git
synced 2026-10-04 02:04:03 +00:00
## What changed?
<!-- Describe what has changed in this PR -->
Remove license header from every file. Because it is really hard to
follow in this PR here is the summary:
1. License header is removed from all `.go` and `.proto` files
:fireworks::fireworks:🎆.
2. `LICENSE` file in the root directory has only Temporal and Uber
copyrights.
3. 5 other `LICENSE` files added to the packages which have copyrights
different from Temporal and Uber: Datadog, Xargin, "Mat Ryer, Tyler
Bunnell and contributors".
4. `license_file` flag is removed from all code generation tools.
5. `copyright_file` flag is removed from `go:generate mockgen`
directive.
6. All copyright related targets are removed from `Makefile`.
7. Updated Temporal copyright year to 2025 everywhere.
## Why?
<!-- Tell your future self why have you made these changes -->
I double checked with legal department that it is not needed to have
license header in every file. One file per repo is enough. I put all
copyrights to the root `LICENSE` file and removed header from all other
files. Also updated tools and `Makefile`.
273 lines
8.4 KiB
Go
273 lines
8.4 KiB
Go
package executions
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
commonpb "go.temporal.io/api/common/v1"
|
|
"go.temporal.io/api/serviceerror"
|
|
"go.temporal.io/server/api/adminservice/v1"
|
|
"go.temporal.io/server/api/historyservice/v1"
|
|
persistencespb "go.temporal.io/server/api/persistence/v1"
|
|
"go.temporal.io/server/common/backoff"
|
|
"go.temporal.io/server/common/collection"
|
|
"go.temporal.io/server/common/dynamicconfig"
|
|
"go.temporal.io/server/common/log"
|
|
"go.temporal.io/server/common/log/tag"
|
|
"go.temporal.io/server/common/metrics"
|
|
"go.temporal.io/server/common/namespace"
|
|
"go.temporal.io/server/common/persistence"
|
|
"go.temporal.io/server/common/quotas"
|
|
"go.temporal.io/server/service/worker/scanner/executor"
|
|
)
|
|
|
|
const (
|
|
executionsPageSize = 100
|
|
|
|
taskStartupDelayRatio = 100 * time.Millisecond
|
|
taskStartupDelayRandomizationRatio = 1.0
|
|
)
|
|
|
|
type (
|
|
// task is a runnable task that adheres to the executor.Task interface
|
|
// for the scavenger, each of this task processes a single workflow mutableState
|
|
task struct {
|
|
shardID int32
|
|
executionManager persistence.ExecutionManager
|
|
registry namespace.Registry
|
|
historyClient historyservice.HistoryServiceClient
|
|
adminClient adminservice.AdminServiceClient
|
|
metricsHandler metrics.Handler
|
|
logger log.Logger
|
|
scavenger *Scavenger
|
|
|
|
ctx context.Context
|
|
rateLimiter quotas.RateLimiter
|
|
executionDataDurationBuffer dynamicconfig.DurationPropertyFn
|
|
enableHistoryEventIDValidator dynamicconfig.BoolPropertyFn
|
|
paginationToken []byte
|
|
}
|
|
)
|
|
|
|
// newTask returns a new instance of an executable task which will process a single mutableState
|
|
func newTask(
|
|
ctx context.Context,
|
|
shardID int32,
|
|
executionManager persistence.ExecutionManager,
|
|
registry namespace.Registry,
|
|
historyClient historyservice.HistoryServiceClient,
|
|
adminClient adminservice.AdminServiceClient,
|
|
metricsHandler metrics.Handler,
|
|
logger log.Logger,
|
|
scavenger *Scavenger,
|
|
rateLimiter quotas.RateLimiter,
|
|
executionDataDurationBuffer dynamicconfig.DurationPropertyFn,
|
|
enableHistoryEventIDValidator dynamicconfig.BoolPropertyFn,
|
|
) executor.Task {
|
|
return &task{
|
|
shardID: shardID,
|
|
executionManager: executionManager,
|
|
registry: registry,
|
|
historyClient: historyClient,
|
|
adminClient: adminClient,
|
|
|
|
metricsHandler: metricsHandler.WithTags(metrics.OperationTag(metrics.ExecutionsScavengerScope)),
|
|
logger: logger,
|
|
scavenger: scavenger,
|
|
|
|
ctx: ctx,
|
|
rateLimiter: rateLimiter,
|
|
executionDataDurationBuffer: executionDataDurationBuffer,
|
|
enableHistoryEventIDValidator: enableHistoryEventIDValidator,
|
|
}
|
|
}
|
|
|
|
// Run runs the task
|
|
func (t *task) Run() executor.TaskStatus {
|
|
time.Sleep(backoff.Jitter(
|
|
taskStartupDelayRatio*time.Duration(t.scavenger.numHistoryShards),
|
|
taskStartupDelayRandomizationRatio,
|
|
))
|
|
|
|
iter := collection.NewPagingIteratorWithToken(t.getPaginationFn(), t.paginationToken)
|
|
var retryTask bool
|
|
for iter.HasNext() {
|
|
_ = t.rateLimiter.Wait(t.ctx)
|
|
record, err := iter.Next()
|
|
if err != nil {
|
|
metrics.ScavengerValidationSkipsCount.With(t.metricsHandler).Record(1)
|
|
// continue validation process and retry after all workflow records has been iterated.
|
|
t.logger.Error("unable to paginate concrete execution", tag.ShardID(t.shardID), tag.Error(err))
|
|
retryTask = true
|
|
}
|
|
|
|
mutableState := &MutableState{WorkflowMutableState: record}
|
|
results := t.validate(mutableState)
|
|
printValidationResult(
|
|
mutableState,
|
|
results,
|
|
t.metricsHandler,
|
|
t.logger,
|
|
)
|
|
err = t.handleFailures(mutableState, results)
|
|
if err != nil {
|
|
// continue validation process and retry after all workflow records has been iterated.
|
|
executionInfo := mutableState.GetExecutionInfo()
|
|
metrics.ScavengerValidationSkipsCount.With(t.metricsHandler).Record(1)
|
|
t.logger.Error("unable to process failure result",
|
|
tag.ShardID(t.shardID),
|
|
tag.Error(err),
|
|
tag.WorkflowNamespaceID(executionInfo.GetNamespaceId()),
|
|
tag.WorkflowID(executionInfo.GetWorkflowId()),
|
|
tag.WorkflowRunID(mutableState.GetExecutionState().GetRunId()))
|
|
retryTask = true
|
|
}
|
|
}
|
|
if retryTask {
|
|
return executor.TaskStatusDefer
|
|
}
|
|
return executor.TaskStatusDone
|
|
}
|
|
|
|
func (t *task) validate(
|
|
mutableState *MutableState,
|
|
) []MutableStateValidationResult {
|
|
|
|
var results []MutableStateValidationResult
|
|
t.logger.Debug("validating mutable state",
|
|
tag.ShardID(t.shardID),
|
|
tag.WorkflowNamespaceID(mutableState.GetExecutionInfo().GetNamespaceId()),
|
|
tag.WorkflowID(mutableState.GetExecutionInfo().GetWorkflowId()),
|
|
tag.WorkflowRunID(mutableState.GetExecutionState().GetRunId()),
|
|
)
|
|
|
|
if validationResults, err := NewMutableStateValidator(
|
|
t.registry,
|
|
t.executionDataDurationBuffer,
|
|
).Validate(
|
|
t.ctx,
|
|
mutableState,
|
|
); err != nil {
|
|
t.logger.Error("unable to validate mutable state ID",
|
|
tag.ShardID(t.shardID),
|
|
tag.WorkflowNamespaceID(mutableState.GetExecutionInfo().GetNamespaceId()),
|
|
tag.WorkflowID(mutableState.GetExecutionInfo().GetWorkflowId()),
|
|
tag.WorkflowRunID(mutableState.GetExecutionState().GetRunId()),
|
|
tag.Error(err),
|
|
)
|
|
} else {
|
|
results = append(results, validationResults...)
|
|
}
|
|
|
|
// Fail fast if the mutable is corrupted, no need to validate history.
|
|
if len(results) > 0 {
|
|
return results
|
|
}
|
|
|
|
if t.enableHistoryEventIDValidator() {
|
|
if validationResults, err := NewHistoryEventIDValidator(
|
|
t.shardID,
|
|
t.executionManager,
|
|
).Validate(t.ctx, mutableState); err != nil {
|
|
t.logger.Error("unable to validate history event ID being contiguous",
|
|
tag.ShardID(t.shardID),
|
|
tag.WorkflowNamespaceID(mutableState.GetExecutionInfo().GetNamespaceId()),
|
|
tag.WorkflowID(mutableState.GetExecutionInfo().GetWorkflowId()),
|
|
tag.WorkflowRunID(mutableState.GetExecutionState().GetRunId()),
|
|
tag.Error(err),
|
|
)
|
|
} else {
|
|
results = append(results, validationResults...)
|
|
}
|
|
}
|
|
|
|
return results
|
|
}
|
|
|
|
func (t *task) getPaginationFn() collection.PaginationFn[*persistencespb.WorkflowMutableState] {
|
|
return func(paginationToken []byte) ([]*persistencespb.WorkflowMutableState, []byte, error) {
|
|
req := &persistence.ListConcreteExecutionsRequest{
|
|
ShardID: t.shardID,
|
|
PageSize: executionsPageSize,
|
|
PageToken: paginationToken,
|
|
}
|
|
resp, err := t.executionManager.ListConcreteExecutions(t.ctx, req)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
paginateItems := resp.States
|
|
t.paginationToken = resp.PageToken
|
|
return paginateItems, resp.PageToken, nil
|
|
}
|
|
}
|
|
|
|
func (t *task) handleFailures(
|
|
mutableState *MutableState,
|
|
results []MutableStateValidationResult,
|
|
) error {
|
|
for _, failure := range results {
|
|
switch failure.failureType {
|
|
case mutableStateRetentionFailureType:
|
|
executionInfo := mutableState.GetExecutionInfo()
|
|
runID := mutableState.GetExecutionState().GetRunId()
|
|
ns, err := t.registry.GetNamespaceByID(namespace.ID(executionInfo.GetNamespaceId()))
|
|
switch err.(type) {
|
|
case *serviceerror.NotFound,
|
|
*serviceerror.NamespaceNotFound:
|
|
t.logger.Error("Garbage data in DB after namespace is deleted", tag.WorkflowNamespaceID(executionInfo.GetNamespaceId()))
|
|
// We cannot do much in this case. It just ignores this error.
|
|
return nil
|
|
case nil:
|
|
// continue to delete
|
|
default:
|
|
return err
|
|
}
|
|
|
|
_, err = t.adminClient.DeleteWorkflowExecution(t.ctx, &adminservice.DeleteWorkflowExecutionRequest{
|
|
Namespace: ns.Name().String(),
|
|
Execution: &commonpb.WorkflowExecution{
|
|
WorkflowId: executionInfo.GetWorkflowId(),
|
|
RunId: runID,
|
|
},
|
|
})
|
|
switch err.(type) {
|
|
case *serviceerror.NotFound,
|
|
*serviceerror.NamespaceNotFound:
|
|
return nil
|
|
case nil:
|
|
continue
|
|
default:
|
|
return err
|
|
}
|
|
default:
|
|
// no-op
|
|
continue
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func printValidationResult(
|
|
mutableState *MutableState,
|
|
results []MutableStateValidationResult,
|
|
metricsHandler metrics.Handler,
|
|
logger log.Logger,
|
|
) {
|
|
metrics.ScavengerValidationRequestsCount.With(metricsHandler).Record(1)
|
|
if len(results) == 0 {
|
|
return
|
|
}
|
|
|
|
metrics.ScavengerValidationFailuresCount.With(metricsHandler).Record(1)
|
|
for _, result := range results {
|
|
metrics.ScavengerValidationFailuresCount.With(metricsHandler).Record(1, metrics.FailureTag(result.failureType))
|
|
logger.Info(
|
|
"validation failed for execution.",
|
|
tag.WorkflowNamespaceID(mutableState.GetExecutionInfo().GetNamespaceId()),
|
|
tag.WorkflowID(mutableState.GetExecutionInfo().GetWorkflowId()),
|
|
tag.WorkflowRunID(mutableState.GetExecutionState().GetRunId()),
|
|
tag.Value(result.failureDetails),
|
|
)
|
|
}
|
|
}
|