Files
Alex ShtinandGitHub 8e8f1b0f7c Remove license header from every file (#7689)
## 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`.
2025-05-01 18:50:21 -07:00

309 lines
11 KiB
Go

package build_ids
import (
"context"
"time"
enumspb "go.temporal.io/api/enums/v1"
"go.temporal.io/sdk/activity"
"go.temporal.io/sdk/client"
"go.temporal.io/sdk/temporal"
"go.temporal.io/sdk/workflow"
"go.temporal.io/server/api/matchingservice/v1"
persistencespb "go.temporal.io/server/api/persistence/v1"
"go.temporal.io/server/common"
hlc "go.temporal.io/server/common/clock/hybrid_logical_clock"
"go.temporal.io/server/common/dynamicconfig"
"go.temporal.io/server/common/log"
"go.temporal.io/server/common/log/tag"
"go.temporal.io/server/common/namespace"
"go.temporal.io/server/common/persistence"
"go.temporal.io/server/common/persistence/visibility/manager"
"go.temporal.io/server/common/quotas"
"go.temporal.io/server/common/worker_versioning"
)
const (
BuildIdScavangerWorkflowName = "build-id-scavenger"
BuildIdScavangerActivityName = "scavenge-build-ids"
BuildIdScavengerWFID = "temporal-sys-build-id-scavenger"
BuildIdScavengerTaskQueueName = "temporal-sys-build-id-scavenger-taskqueue-0"
)
var (
BuildIdScavengerWFStartOptions = client.StartWorkflowOptions{
ID: BuildIdScavengerWFID,
TaskQueue: BuildIdScavengerTaskQueueName,
WorkflowIDReusePolicy: enumspb.WORKFLOW_ID_REUSE_POLICY_ALLOW_DUPLICATE,
CronSchedule: "0 */12 * * *",
}
)
type (
BuildIdScavangerInput struct {
NamespaceListPageSize int
TaskQueueListPageSize int
IgnoreRetentionTime bool // If true, consider build ids added since retention time also
}
Activities struct {
logger log.Logger
taskManager persistence.TaskManager
metadataManager persistence.MetadataManager
visibilityManager manager.VisibilityManager
namespaceRegistry namespace.Registry
matchingClient matchingservice.MatchingServiceClient
currentClusterName string
// Minimum duration since a build ID was last default in its containing set for it to be considered for removal.
// If a build ID was still default recently, there may be:
// 1. workers with that identifier processing tasks
// 2. workflows with that identifier that have yet to be indexed in visibility
// The scavenger should allow enough time to pass before cleaning these build ids.
removableBuildIdDurationSinceDefault dynamicconfig.DurationPropertyFn
buildIdScavengerVisibilityRPS dynamicconfig.FloatPropertyFn
}
heartbeatDetails struct {
NamespaceIdx int
TaskQueueIdx int
NamespaceNextPageToken []byte
TaskQueueNextPageToken []byte
}
)
func NewActivities(
logger log.Logger,
taskManager persistence.TaskManager,
metadataManager persistence.MetadataManager,
visibilityManager manager.VisibilityManager,
namespaceRegistry namespace.Registry,
matchingClient matchingservice.MatchingServiceClient,
currentClusterName string,
removableBuildIdDurationSinceDefault dynamicconfig.DurationPropertyFn,
buildIdScavengerVisibilityRPS dynamicconfig.FloatPropertyFn,
) *Activities {
return &Activities{
logger: logger,
taskManager: taskManager,
metadataManager: metadataManager,
visibilityManager: visibilityManager,
namespaceRegistry: namespaceRegistry,
matchingClient: matchingClient,
currentClusterName: currentClusterName,
removableBuildIdDurationSinceDefault: removableBuildIdDurationSinceDefault,
buildIdScavengerVisibilityRPS: buildIdScavengerVisibilityRPS,
}
}
// BuildIdScavangerWorkflow scans all task queue user data entries in all namespaces and cleans up unused build ids.
// This workflow is a wrapper around the long running ScavengeBuildIds activity.
func BuildIdScavangerWorkflow(ctx workflow.Context, input BuildIdScavangerInput) error {
activityCtx := workflow.WithActivityOptions(ctx, workflow.ActivityOptions{
// Give the activity enough time to scan the entire namespace
StartToCloseTimeout: 6 * time.Hour,
HeartbeatTimeout: 30 * time.Second,
})
return workflow.ExecuteActivity(activityCtx, BuildIdScavangerActivityName, input).Get(ctx, nil)
}
func (a *Activities) setDefaults(input *BuildIdScavangerInput) {
if input.NamespaceListPageSize == 0 {
input.NamespaceListPageSize = 100
}
if input.TaskQueueListPageSize == 0 {
input.TaskQueueListPageSize = 100
}
}
func (a *Activities) recordHeartbeat(ctx context.Context, heartbeat heartbeatDetails) {
activity.RecordHeartbeat(ctx, heartbeat)
}
// ScavengeBuildIds scans all task queue user data entries in all namespaces and cleans up unused build ids.
func (a *Activities) ScavengeBuildIds(ctx context.Context, input BuildIdScavangerInput) error {
a.setDefaults(&input)
var heartbeat heartbeatDetails
if activity.HasHeartbeatDetails(ctx) {
if err := activity.GetHeartbeatDetails(ctx, &heartbeat); err != nil {
return temporal.NewNonRetryableApplicationError("failed to load previous heartbeat details", "TypeError", err)
}
}
rateLimiter := quotas.NewDefaultOutgoingRateLimiter(quotas.RateFn(a.buildIdScavengerVisibilityRPS))
for {
nsResponse, err := a.metadataManager.ListNamespaces(ctx, &persistence.ListNamespacesRequest{
PageSize: input.NamespaceListPageSize,
NextPageToken: heartbeat.NamespaceNextPageToken,
IncludeDeleted: false, // Don't care about deleted namespaces.
})
if err != nil {
return err
}
for heartbeat.NamespaceIdx < len(nsResponse.Namespaces) {
nsId := nsResponse.Namespaces[heartbeat.NamespaceIdx].Namespace.Info.Id
if err := a.processNamespaceEntry(ctx, rateLimiter, input, &heartbeat, nsId); err != nil {
return err
}
heartbeat.NamespaceIdx++
a.recordHeartbeat(ctx, heartbeat)
}
heartbeat.NamespaceIdx = 0
heartbeat.NamespaceNextPageToken = nsResponse.NextPageToken
if len(heartbeat.NamespaceNextPageToken) == 0 {
break
}
a.recordHeartbeat(ctx, heartbeat)
}
return nil
}
func (a *Activities) processNamespaceEntry(
ctx context.Context,
rateLimiter quotas.RateLimiter,
input BuildIdScavangerInput,
heartbeat *heartbeatDetails,
nsId string,
) error {
ns, err := a.namespaceRegistry.GetNamespaceByID(namespace.ID(nsId))
if err != nil {
return err
}
// Only the active cluster for this namespace should perform the cleanup.
if !ns.ActiveInCluster(a.currentClusterName) {
return nil
}
for {
tqResponse, err := a.taskManager.ListTaskQueueUserDataEntries(ctx, &persistence.ListTaskQueueUserDataEntriesRequest{
NamespaceID: nsId,
PageSize: input.TaskQueueListPageSize,
NextPageToken: heartbeat.TaskQueueNextPageToken,
})
if err != nil {
return err
}
for heartbeat.TaskQueueIdx < len(tqResponse.Entries) {
entry := tqResponse.Entries[heartbeat.TaskQueueIdx]
if err := a.processUserDataEntry(ctx, rateLimiter, input, *heartbeat, ns, entry); err != nil {
if common.IsContextDeadlineExceededErr(err) {
// This is either a real DeadlineExceeded from the context, or the rate limiter
// thinks there's not enough time left until the deadline. Either way, we're done.
return err
} else if ctx.Err() != nil {
// Also return on context.Canceled.
return ctx.Err()
}
// Intentionally don't fail the activity on other single entry errors.
a.logger.Error("Failed to update task queue user data",
tag.WorkflowNamespace(ns.Name().String()),
tag.WorkflowTaskQueueName(entry.TaskQueue),
tag.Error(err))
}
heartbeat.TaskQueueIdx++
a.recordHeartbeat(ctx, *heartbeat)
}
heartbeat.TaskQueueIdx = 0
heartbeat.TaskQueueNextPageToken = tqResponse.NextPageToken
if len(heartbeat.TaskQueueNextPageToken) == 0 {
break
}
a.recordHeartbeat(ctx, *heartbeat)
}
return nil
}
func (a *Activities) processUserDataEntry(
ctx context.Context,
rateLimiter quotas.RateLimiter,
input BuildIdScavangerInput,
heartbeat heartbeatDetails,
ns *namespace.Namespace,
entry *persistence.TaskQueueUserDataEntry,
) error {
buildIdsToRemove, err := a.findBuildIdsToRemove(ctx, rateLimiter, input, heartbeat, ns, entry)
if err != nil {
return err
}
if len(buildIdsToRemove) == 0 {
return nil
}
_, err = a.matchingClient.UpdateWorkerBuildIdCompatibility(ctx, &matchingservice.UpdateWorkerBuildIdCompatibilityRequest{
NamespaceId: ns.ID().String(),
TaskQueue: entry.TaskQueue,
Operation: &matchingservice.UpdateWorkerBuildIdCompatibilityRequest_RemoveBuildIds_{
RemoveBuildIds: &matchingservice.UpdateWorkerBuildIdCompatibilityRequest_RemoveBuildIds{
KnownUserDataVersion: entry.UserData.Version,
BuildIds: buildIdsToRemove,
},
},
})
return err
}
// Queries visibility for each build ID in versioning data and returns a list of those that are safe for removal.
func (a *Activities) findBuildIdsToRemove(
ctx context.Context,
rateLimiter quotas.RateLimiter,
input BuildIdScavangerInput,
heartbeat heartbeatDetails,
ns *namespace.Namespace,
entry *persistence.TaskQueueUserDataEntry,
) ([]string, error) {
// Only consider build ids that have been active at least as long as the retention time.
// This assumes that when a build ID is added, it's used soon afterwards.
// This lets us avoid making visibility queries that would probably find some workflows.
retention := ns.Retention()
// Don't consider build ids that were recently the default, since there may be workers
// still processing tasks or data that hasn't made it to visibility yet.
removableBuildIdDurationSinceDefault := a.removableBuildIdDurationSinceDefault()
versioningData := entry.UserData.Data.GetVersioningData()
var buildIdsToRemove []string
for setIdx, set := range versioningData.GetVersionSets() {
// Note that setActive counts build ids that may have associated workflows, i.e. not
// just all with STATE_ACTIVE. Also note that we always examine the default build ID
// for a set last, so setActive will be 1 + the number of active non-default build ids.
setActive := len(set.BuildIds)
for buildIdIdx, buildId := range set.BuildIds {
if buildId.State == persistencespb.STATE_DELETED {
setActive--
continue
}
buildIdIsSetDefault := buildIdIdx == len(set.BuildIds)-1
setIsQueueDefault := setIdx == len(versioningData.VersionSets)-1
// Don't remove if build ID is the queue default or there's another active build ID in
// this set, since we might need to dispatch new tasks to this set. But if no build ids
// are active for the whole set, we can remove them all.
if buildIdIsSetDefault && (setIsQueueDefault || setActive > 1) {
continue
}
if hlc.Since(buildId.BecameDefaultTimestamp) < removableBuildIdDurationSinceDefault {
continue
}
if !input.IgnoreRetentionTime && hlc.Since(buildId.StateUpdateTimestamp) < retention {
continue
}
if err := rateLimiter.Wait(ctx); err != nil {
return nil, context.DeadlineExceeded
}
exists, err := worker_versioning.WorkflowsExistForBuildId(ctx, a.visibilityManager, ns, entry.TaskQueue, buildId.Id)
if err != nil {
return nil, err
}
a.recordHeartbeat(ctx, heartbeat)
if !exists {
a.logger.Info("Found build ID to remove",
tag.WorkflowNamespace(ns.Name().String()),
tag.WorkflowTaskQueueName(entry.TaskQueue),
tag.BuildId(buildId.Id),
)
buildIdsToRemove = append(buildIdsToRemove, buildId.Id)
setActive--
}
}
}
return buildIdsToRemove, nil
}