Files
tasks/components/scheduler/scheduler.go
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

269 lines
8.1 KiB
Go

package scheduler
import (
"fmt"
"time"
commonpb "go.temporal.io/api/common/v1"
enumspb "go.temporal.io/api/enums/v1"
schedulepb "go.temporal.io/api/schedule/v1"
schedulespb "go.temporal.io/server/api/schedule/v1"
"go.temporal.io/server/common/util"
"go.temporal.io/server/service/history/hsm"
"go.temporal.io/server/service/worker/scheduler"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/timestamppb"
)
type (
// Scheduler is a top-level state machine compromised of 3 sub state machines:
// - Generator: buffers actions according to the schedule specification
// - Invoker: executes buffered actions
// - Backfiller: buffers actions according to requested backfills
//
// A running Scheduler will always have exactly one of each of the above sub state
// machines mounted as nodes within the HSM tree. The top-level machine itself
// remains in a singular running state for its lifetime (all work is done within the
// sub state machines). The Scheduler state machine is only responsible for creating
// the singleton sub state machines.
Scheduler struct {
*schedulespb.SchedulerInternal
// Locally-cached state, invalidated whenever cacheConflictToken != ConflictToken.
cacheConflictToken int64
compiledSpec *scheduler.CompiledSpec
}
// The machine definitions provide serialization/deserialization and type information.
schedulerMachineDefinition struct{}
SchedulerMachineState int
)
const (
// Unique identifier for top-level scheduler state machine.
SchedulerMachineType = "scheduler.Scheduler"
// The top-level scheduler only has a single, constant state.
SchedulerMachineStateRunning SchedulerMachineState = 0
// How many recent actions to keep on the Info.RecentActions list.
recentActionCount = 10
)
var (
_ hsm.StateMachine[SchedulerMachineState] = Scheduler{}
_ hsm.StateMachineDefinition = &schedulerMachineDefinition{}
)
// NewScheduler returns an initialized Scheduler state machine (without any sub
// state machines).
func NewScheduler(
namespace, namespaceID, scheduleID string,
sched *schedulepb.Schedule,
patch *schedulepb.SchedulePatch,
) *Scheduler {
var zero time.Time
return &Scheduler{
SchedulerInternal: &schedulespb.SchedulerInternal{
Schedule: sched,
Info: &schedulepb.ScheduleInfo{
ActionCount: 0,
MissedCatchupWindow: 0,
OverlapSkipped: 0,
BufferDropped: 0,
BufferSize: 0,
RunningWorkflows: []*commonpb.WorkflowExecution{},
RecentActions: []*schedulepb.ScheduleActionResult{},
FutureActionTimes: []*timestamppb.Timestamp{},
CreateTime: timestamppb.Now(),
UpdateTime: timestamppb.New(zero),
},
InitialPatch: patch,
Namespace: namespace,
NamespaceId: namespaceID,
ScheduleId: scheduleID,
ConflictToken: scheduler.InitialConflictToken,
},
cacheConflictToken: scheduler.InitialConflictToken,
compiledSpec: nil,
}
}
// RegisterStateMachine registers state machine definitions with the HSM
// registry. Should be called during dependency injection.
func RegisterStateMachines(r *hsm.Registry) error {
if err := r.RegisterMachine(schedulerMachineDefinition{}); err != nil {
return err
}
if err := r.RegisterMachine(generatorMachineDefinition{}); err != nil {
return err
}
if err := r.RegisterMachine(invokerMachineDefinition{}); err != nil {
return err
}
// TODO: add other state machines here
return nil
}
func (s Scheduler) State() SchedulerMachineState {
return SchedulerMachineStateRunning
}
func (s Scheduler) SetState(_ SchedulerMachineState) {}
func (s Scheduler) RegenerateTasks(node *hsm.Node) ([]hsm.Task, error) {
// The top level scheduler has no tasks of its own.
return nil, nil
}
func (schedulerMachineDefinition) Type() string {
return SchedulerMachineType
}
func (schedulerMachineDefinition) Serialize(state any) ([]byte, error) {
if state, ok := state.(Scheduler); ok {
return proto.Marshal(state.SchedulerInternal)
}
return nil, fmt.Errorf("invalid scheduler state provided: %v", state)
}
func (schedulerMachineDefinition) Deserialize(body []byte) (any, error) {
state := &schedulespb.SchedulerInternal{}
return Scheduler{
SchedulerInternal: state,
compiledSpec: nil,
}, proto.Unmarshal(body, state)
}
func (schedulerMachineDefinition) CompareState(a any, b any) (int, error) {
panic("TODO: CompareState not yet implemented for Scheduler")
}
// useScheduledAction returns true when the Scheduler should allow scheduled
// actions to be taken.
//
// When decrement is true, the schedule's state's `RemainingActions` counter is
// decremented when an action can be taken. When decrement is false, no state
// is mutated.
func (s *Scheduler) useScheduledAction(decrement bool) bool {
// If paused, don't do anything.
if s.Schedule.State.Paused {
return false
}
// If unlimited actions, allow.
if !s.Schedule.State.LimitedActions {
return true
}
// Otherwise check and decrement limit.
if s.Schedule.State.RemainingActions > 0 {
if decrement {
s.Schedule.State.RemainingActions--
// The conflict token is updated because a client might be in the process of
// preparing an update request that increments their schedule's RemainingActions
// field.
s.updateConflictToken()
}
return true
}
// No actions left
return false
}
func (s *Scheduler) getCompiledSpec(specBuilder *scheduler.SpecBuilder) (*scheduler.CompiledSpec, error) {
s.validateCachedState()
// Cache compiled spec.
if s.compiledSpec == nil {
cspec, err := specBuilder.NewCompiledSpec(s.Schedule.Spec)
if err != nil {
return nil, err
}
s.compiledSpec = cspec
}
return s.compiledSpec, nil
}
func (s Scheduler) jitterSeed() string {
return fmt.Sprintf("%s-%s", s.NamespaceId, s.ScheduleId)
}
func (s Scheduler) identity() string {
return fmt.Sprintf("temporal-scheduler-%s-%s", s.Namespace, s.ScheduleId)
}
func (s Scheduler) overlapPolicy() enumspb.ScheduleOverlapPolicy {
policy := s.Schedule.Policies.OverlapPolicy
if policy == enumspb.SCHEDULE_OVERLAP_POLICY_UNSPECIFIED {
policy = enumspb.SCHEDULE_OVERLAP_POLICY_SKIP
}
return policy
}
func (s Scheduler) resolveOverlapPolicy(overlapPolicy enumspb.ScheduleOverlapPolicy) enumspb.ScheduleOverlapPolicy {
if overlapPolicy == enumspb.SCHEDULE_OVERLAP_POLICY_UNSPECIFIED {
overlapPolicy = s.overlapPolicy()
}
return overlapPolicy
}
// validateCachedState clears cached fields whenever the Scheduler's
// ConflictToken doesn't match its cacheConflictToken field. Validation is only
// as effective as the Scheduler's backing persisted state is up-to-date.
func (s *Scheduler) validateCachedState() {
if s.cacheConflictToken != s.ConflictToken {
// Bust stale cached fields.
s.compiledSpec = nil
// We're now up-to-date.
s.cacheConflictToken = s.ConflictToken
}
}
// updateConflictToken bumps the Scheduler's conflict token. This has a side
// effect of invalidating the local cache. Use whenever applying a mutation that
// should invalidate other in-flight updates.
func (s *Scheduler) updateConflictToken() {
s.ConflictToken++
}
type EventRecordAction struct {
Node *hsm.Node
ActionCount int64
OverlapSkipped int64
BufferDropped int64
MissedCatchupWindow int64
Results []*schedulepb.ScheduleActionResult
}
// Fired when an action has been taken by the state machine scheduler and should
// be recorded.
var TransitionRecordAction = hsm.NewTransition(
[]SchedulerMachineState{SchedulerMachineStateRunning},
SchedulerMachineStateRunning,
func(s Scheduler, event EventRecordAction) (hsm.TransitionOutput, error) {
s.Info.ActionCount += event.ActionCount
s.Info.OverlapSkipped += event.OverlapSkipped
s.Info.BufferDropped += event.BufferDropped
s.Info.MissedCatchupWindow += event.MissedCatchupWindow
if len(event.Results) > 0 {
s.Info.RecentActions = util.SliceTail(append(s.Info.RecentActions, event.Results...), recentActionCount)
}
for _, result := range event.Results {
if result.StartWorkflowResult != nil {
s.Info.RunningWorkflows = append(s.Info.RunningWorkflows, result.StartWorkflowResult)
}
}
return hsm.TransitionOutput{}, nil
},
)