Files
tasks/service/matching/physical_task_queue_manager.go
ShivamandGitHub be5ce2c5fc Making poller history a dynamic config (#7344)
## What changed?
<!-- Describe what has changed in this PR -->
- PollerHistoryTTL is now a dynamic config

## Why?
<!-- Tell your future self why have you made these changes -->
- Versioning-3.1 tests required setting a low value for this config in
order for some tests to work - right now, those tests were excluded from
CI runs and that's not good.

## How did you test it?
<!-- How have you verified this change? Tested locally? Added a unit
test? Checked in staging env? -->
- Unskipped the tests that were being skipped previously.

## Potential risks
<!-- Assuming the worst case, what can be broken when deploying this
change to production? -->

## Documentation
<!-- Have you made sure this change doesn't falsify anything currently
stated in `docs/`? If significant
new behavior is added, have you described that in `docs/`? -->

## Is hotfix candidate?
<!-- Is this PR a hotfix candidate or does it require a notification to
be sent to the broader community? (Yes/No) -->
2025-02-14 16:35:14 -05:00

654 lines
24 KiB
Go

// The MIT License
//
// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved.
//
// Copyright (c) 2020 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package matching
import (
"bytes"
"context"
"errors"
"fmt"
"sync"
"sync/atomic"
"time"
"github.com/nexus-rpc/sdk-go/nexus"
"github.com/pborman/uuid"
enumspb "go.temporal.io/api/enums/v1"
"go.temporal.io/api/serviceerror"
taskqueuepb "go.temporal.io/api/taskqueue/v1"
"go.temporal.io/api/workflowservice/v1"
"go.temporal.io/server/api/matchingservice/v1"
persistencespb "go.temporal.io/server/api/persistence/v1"
taskqueuespb "go.temporal.io/server/api/taskqueue/v1"
"go.temporal.io/server/common"
"go.temporal.io/server/common/clock"
"go.temporal.io/server/common/cluster"
"go.temporal.io/server/common/debug"
"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/testing/testhooks"
"go.temporal.io/server/common/worker_versioning"
"go.temporal.io/server/service/worker/deployment"
"google.golang.org/protobuf/types/known/durationpb"
)
const (
// Time budget for empty task to propagate through the function stack and be returned to
// pollForActivityTask or pollForWorkflowTask handler.
returnEmptyTaskTimeBudget = time.Second
// Fake Task ID to wrap a task for syncmatch
syncMatchTaskId = -137
ioTimeout = 5 * time.Second * debug.TimeoutMultiplier
// Threshold for counting a AddTask call as a no recent poller call
noPollerThreshold = time.Minute * 2
)
type (
taskQueueManagerOpt func(*physicalTaskQueueManagerImpl)
addTaskParams struct {
taskInfo *persistencespb.TaskInfo
forwardInfo *taskqueuespb.TaskForwardInfo
}
// physicalTaskQueueManagerImpl manages a single DB-level (aka physical) task queue in memory
physicalTaskQueueManagerImpl struct {
status int32
partitionMgr *taskQueuePartitionManagerImpl
queue *PhysicalTaskQueueKey
config *taskQueueConfig
backlogMgr *backlogManagerImpl
liveness *liveness
matcher *TaskMatcher // for matching a task producer with a poller
namespaceRegistry namespace.Registry
logger log.Logger
throttledLogger log.ThrottledLogger
matchingClient matchingservice.MatchingServiceClient
clusterMeta cluster.Metadata
metricsHandler metrics.Handler // namespace/taskqueue tagged metric scope
// pollerHistory stores poller which poll from this taskqueue in last few minutes
pollerHistory *pollerHistory
currentPolls atomic.Int64
taskValidator taskValidator
tasksAddedInIntervals *taskTracker
tasksDispatchedInIntervals *taskTracker
// deploymentWorkflowStarted keeps track if we have already registered the task queue worker
// in the deployment.
deploymentLock sync.Mutex // TODO (Shivam): Rename after the pre-release versioning API's are removed.
deploymentRegistered bool // TODO (Shivam): Rename after the pre-release versioning API's are removed.
deploymentVersionRegistered bool // TODO (Shivam): Rename after the pre-release versioning API's are removed.
deploymentRegisterError error // last "too many ..." error we got when registering // TODO (Shivam): Rename after the pre-release versioning API's are removed.
firstPoll time.Time
}
)
var _ physicalTaskQueueManager = (*physicalTaskQueueManagerImpl)(nil)
var (
errRemoteSyncMatchFailed = serviceerror.NewCanceled("remote sync match failed")
errMissingNormalQueueName = errors.New("missing normal queue name")
)
func newPhysicalTaskQueueManager(
partitionMgr *taskQueuePartitionManagerImpl,
queue *PhysicalTaskQueueKey,
opts ...taskQueueManagerOpt,
) (*physicalTaskQueueManagerImpl, error) {
e := partitionMgr.engine
config := partitionMgr.config
buildIdTagValue := queue.Version().MetricsTagValue()
logger := log.With(partitionMgr.logger, tag.WorkerBuildId(buildIdTagValue))
throttledLogger := log.With(partitionMgr.throttledLogger, tag.WorkerBuildId(buildIdTagValue))
taggedMetricsHandler := partitionMgr.metricsHandler.WithTags(
metrics.OperationTag(metrics.MatchingTaskQueueMgrScope),
metrics.WorkerBuildIdTag(buildIdTagValue, config.BreakdownMetricsByBuildID()))
pqMgr := &physicalTaskQueueManagerImpl{
status: common.DaemonStatusInitialized,
partitionMgr: partitionMgr,
namespaceRegistry: e.namespaceRegistry,
matchingClient: e.matchingRawClient,
clusterMeta: e.clusterMeta,
queue: queue,
logger: logger,
throttledLogger: throttledLogger,
config: config,
metricsHandler: taggedMetricsHandler,
tasksAddedInIntervals: newTaskTracker(clock.NewRealTimeSource()),
tasksDispatchedInIntervals: newTaskTracker(clock.NewRealTimeSource()),
}
pqMgr.pollerHistory = newPollerHistory(partitionMgr.config.PollerHistoryTTL())
pqMgr.liveness = newLiveness(
clock.NewRealTimeSource(),
config.MaxTaskQueueIdleTime,
func() { pqMgr.UnloadFromPartitionManager(unloadCauseIdle) },
)
pqMgr.taskValidator = newTaskValidator(pqMgr.newIOContext, pqMgr.clusterMeta, pqMgr.namespaceRegistry, pqMgr.partitionMgr.engine.historyClient)
pqMgr.backlogMgr = newBacklogManager(
pqMgr,
config,
e.taskManager,
logger,
throttledLogger,
e.matchingRawClient,
taggedMetricsHandler,
partitionMgr.callerInfoContext,
)
var fwdr *Forwarder
var err error
if !queue.Partition().IsRoot() && queue.Partition().Kind() != enumspb.TASK_QUEUE_KIND_STICKY {
// Every DB Queue needs its own forwarder so that the throttles do not interfere
fwdr, err = newForwarder(&config.forwarderConfig, queue, e.matchingRawClient)
if err != nil {
return nil, err
}
}
pqMgr.matcher = newTaskMatcher(config, fwdr, pqMgr.metricsHandler)
for _, opt := range opts {
opt(pqMgr)
}
return pqMgr, nil
}
func (c *physicalTaskQueueManagerImpl) Start() {
if !atomic.CompareAndSwapInt32(
&c.status,
common.DaemonStatusInitialized,
common.DaemonStatusStarted,
) {
return
}
c.liveness.Start()
c.backlogMgr.Start()
c.logger.Info("Started physicalTaskQueueManager", tag.LifeCycleStarted, tag.Cause(c.config.loadCause.String()))
c.metricsHandler.Counter(metrics.TaskQueueStartedCounter.Name()).Record(1)
c.partitionMgr.engine.updatePhysicalTaskQueueGauge(c.partitionMgr.ns, c.partitionMgr.partition, c.queue.version, 1)
}
// Stop does not unload the queue from its partition. It is intended to be called by the partition manager when
// unloading a queues. For stopping and unloading a queue call UnloadFromPartitionManager instead.
func (c *physicalTaskQueueManagerImpl) Stop(unloadCause unloadCause) {
if !atomic.CompareAndSwapInt32(
&c.status,
common.DaemonStatusStarted,
common.DaemonStatusStopped,
) {
return
}
c.backlogMgr.Stop()
c.matcher.Stop()
c.liveness.Stop()
c.logger.Info("Stopped physicalTaskQueueManager", tag.LifeCycleStopped, tag.Cause(unloadCause.String()))
c.metricsHandler.Counter(metrics.TaskQueueStoppedCounter.Name()).Record(1)
c.partitionMgr.engine.updatePhysicalTaskQueueGauge(c.partitionMgr.ns, c.partitionMgr.partition, c.queue.version, -1)
}
func (c *physicalTaskQueueManagerImpl) WaitUntilInitialized(ctx context.Context) error {
return c.backlogMgr.WaitUntilInitialized(ctx)
}
func (c *physicalTaskQueueManagerImpl) SpoolTask(taskInfo *persistencespb.TaskInfo) error {
c.liveness.markAlive()
return c.backlogMgr.SpoolTask(taskInfo)
}
// PollTask blocks waiting for a task.
// Returns error when context deadline is exceeded
// maxDispatchPerSecond is the max rate at which tasks are allowed
// to be dispatched from this task queue to pollers
func (c *physicalTaskQueueManagerImpl) PollTask(
ctx context.Context,
pollMetadata *pollMetadata,
) (*internalTask, error) {
c.liveness.markAlive()
c.currentPolls.Add(1)
defer c.currentPolls.Add(-1)
namespaceId := namespace.ID(c.queue.NamespaceId())
namespaceEntry, err := c.namespaceRegistry.GetNamespaceByID(namespaceId)
if err != nil {
return nil, err
}
// [cleanup-wv-pre-release]
if c.partitionMgr.engine.config.EnableDeployments(namespaceEntry.Name().String()) {
if err = c.ensureRegisteredInDeployment(ctx, namespaceEntry, pollMetadata); err != nil {
return nil, err
}
}
if c.partitionMgr.engine.config.EnableDeploymentVersions(namespaceEntry.Name().String()) {
if err = c.ensureRegisteredInDeploymentVersion(ctx, namespaceEntry, pollMetadata); err != nil {
return nil, err
}
}
// the desired global rate limit for the task queue comes from the
// poller, which lives inside the client side worker. There is
// one rateLimiter for this entire task queue and as we get polls,
// we update the ratelimiter rps if it has changed from the last
// value. Last poller wins if different pollers provide different values
if rps := pollMetadata.taskQueueMetadata.GetMaxTasksPerSecond(); rps != nil {
c.matcher.UpdateRatelimit(rps.Value)
}
if !namespaceEntry.ActiveInCluster(c.clusterMeta.GetCurrentClusterName()) {
return c.matcher.PollForQuery(ctx, pollMetadata)
}
for {
task, err := c.matcher.Poll(ctx, pollMetadata)
if err != nil {
return nil, err
}
// It's possible to get an expired task here: taskReader checks for expiration when
// reading from persistence, and physicalTaskQueueManager checks for expiration in
// ProcessSpooledTask, but the one task blocked in the matcher could expire while it's
// there. In that case, go back for another task.
// If we didn't do this, the task would be rejected when we call RecordXTaskStarted on
// history, but this is more efficient.
if task.event != nil && IsTaskExpired(task.event.AllocatedTaskInfo) {
c.metricsHandler.Counter(metrics.ExpiredTasksPerTaskQueueCounter.Name()).Record(1)
task.finish(nil, false)
continue
}
task.namespace = c.partitionMgr.ns.Name()
task.backlogCountHint = c.backlogMgr.BacklogCountHint
if pollMetadata.forwardedFrom == "" && // only track the original polls, not forwarded ones.
(!task.isStarted() || !task.started.hasEmptyResponse()) { // Need to filter out the empty "started" ones
c.tasksDispatchedInIntervals.incrementTaskCount()
}
return task, nil
}
}
func (c *physicalTaskQueueManagerImpl) MarkAlive() {
c.liveness.markAlive()
}
// DispatchSpooledTask dispatches a task to a poller. When there are no pollers to pick
// up the task or if rate limit is exceeded, this method will return error. Task
// *will not* be persisted to db
func (c *physicalTaskQueueManagerImpl) DispatchSpooledTask(
ctx context.Context,
task *internalTask,
userDataChanged <-chan struct{},
) error {
return c.matcher.MustOffer(ctx, task, userDataChanged)
}
func (c *physicalTaskQueueManagerImpl) ProcessSpooledTask(
ctx context.Context,
task *internalTask,
) error {
if !c.taskValidator.maybeValidate(task.event.AllocatedTaskInfo, c.queue.TaskType()) {
task.finish(nil, false)
c.metricsHandler.Counter(metrics.ExpiredTasksPerTaskQueueCounter.Name()).Record(1)
// Don't try to set read level here because it may have been advanced already.
return nil
}
return c.partitionMgr.ProcessSpooledTask(ctx, task, c.queue)
}
// DispatchQueryTask will dispatch query to local or remote poller. If forwarded then result or error is returned,
// if dispatched to local poller then nil and nil is returned.
func (c *physicalTaskQueueManagerImpl) DispatchQueryTask(
ctx context.Context,
taskId string,
request *matchingservice.QueryWorkflowRequest,
) (*matchingservice.QueryWorkflowResponse, error) {
task := newInternalQueryTask(taskId, request)
if !task.isForwarded() {
c.tasksAddedInIntervals.incrementTaskCount()
}
return c.matcher.OfferQuery(ctx, task)
}
func (c *physicalTaskQueueManagerImpl) DispatchNexusTask(
ctx context.Context,
taskId string,
request *matchingservice.DispatchNexusTaskRequest,
) (*matchingservice.DispatchNexusTaskResponse, error) {
deadline, _ := ctx.Deadline() // If not set by user, our client will set a default.
var opDeadline time.Time
if header := nexus.Header(request.GetRequest().GetHeader()); header != nil {
if opTimeoutHeader := header.Get(nexus.HeaderOperationTimeout); opTimeoutHeader != "" {
opTimeout, err := time.ParseDuration(opTimeoutHeader)
if err != nil {
// Operation-Timeout header is not required so don't fail request on parsing errors.
c.logger.Warn(fmt.Sprintf("unable to parse %v header: %v", nexus.HeaderOperationTimeout, opTimeoutHeader), tag.Error(err), tag.WorkflowNamespaceID(request.NamespaceId))
} else {
opDeadline = time.Now().Add(opTimeout)
}
}
}
task := newInternalNexusTask(taskId, deadline, opDeadline, request)
if !task.isForwarded() {
c.tasksAddedInIntervals.incrementTaskCount()
}
return c.matcher.OfferNexusTask(ctx, task)
}
func (c *physicalTaskQueueManagerImpl) UpdatePollerInfo(id pollerIdentity, pollMetadata *pollMetadata) {
c.pollerHistory.updatePollerInfo(id, pollMetadata)
}
// GetAllPollerInfo returns all pollers that polled from this taskqueue in last few minutes
func (c *physicalTaskQueueManagerImpl) GetAllPollerInfo() []*taskqueuepb.PollerInfo {
if c.pollerHistory == nil {
return nil
}
return c.pollerHistory.getPollerInfo(time.Time{})
}
func (c *physicalTaskQueueManagerImpl) HasPollerAfter(accessTime time.Time) bool {
if c.currentPolls.Load() > 0 {
return true
}
if c.pollerHistory == nil {
return false
}
recentPollers := c.pollerHistory.getPollerInfo(accessTime)
return len(recentPollers) > 0
}
// LegacyDescribeTaskQueue returns information about the target taskqueue, right now this API returns the
// pollers which polled this taskqueue in last few minutes and status of taskqueue's ackManager
// (readLevel, ackLevel, backlogCountHint and taskIDBlock).
func (c *physicalTaskQueueManagerImpl) LegacyDescribeTaskQueue(includeTaskQueueStatus bool) *matchingservice.DescribeTaskQueueResponse {
response := &matchingservice.DescribeTaskQueueResponse{
DescResponse: &workflowservice.DescribeTaskQueueResponse{
Pollers: c.GetAllPollerInfo(),
},
}
if includeTaskQueueStatus {
response.DescResponse.TaskQueueStatus = c.backlogMgr.BacklogStatus()
response.DescResponse.TaskQueueStatus.RatePerSecond = c.matcher.Rate()
}
return response
}
func (c *physicalTaskQueueManagerImpl) GetStats() *taskqueuepb.TaskQueueStats {
return &taskqueuepb.TaskQueueStats{
ApproximateBacklogCount: c.backlogMgr.db.getApproximateBacklogCount(),
ApproximateBacklogAge: durationpb.New(c.backlogMgr.taskReader.getBacklogHeadAge()), // using this and not matcher's
// because it reports only the age of the current physical queue backlog (not including the redirected backlogs) which is consistent
// with the ApproximateBacklogCount metric.
TasksAddRate: c.tasksAddedInIntervals.rate(),
TasksDispatchRate: c.tasksDispatchedInIntervals.rate(),
}
}
func (c *physicalTaskQueueManagerImpl) GetInternalTaskQueueStatus() *taskqueuespb.InternalTaskQueueStatus {
return &taskqueuespb.InternalTaskQueueStatus{
ReadLevel: c.backlogMgr.taskAckManager.getReadLevel(),
AckLevel: c.backlogMgr.taskAckManager.getAckLevel(),
TaskIdBlock: &taskqueuepb.TaskIdBlock{StartId: c.backlogMgr.taskWriter.taskIDBlock.start, EndId: c.backlogMgr.taskWriter.taskIDBlock.end},
ReadBufferLength: int64(len(c.backlogMgr.taskReader.taskBuffer)),
}
}
func (c *physicalTaskQueueManagerImpl) String() string {
buf := new(bytes.Buffer)
if c.queue.TaskType() == enumspb.TASK_QUEUE_TYPE_ACTIVITY {
buf.WriteString("Activity")
} else {
buf.WriteString("Workflow")
}
_, _ = fmt.Fprintf(buf, "Backlog=%s\n", c.backlogMgr.String())
return buf.String()
}
func (c *physicalTaskQueueManagerImpl) TrySyncMatch(ctx context.Context, task *internalTask) (bool, error) {
if !task.isForwarded() {
// request sent by history service
c.liveness.markAlive()
c.tasksAddedInIntervals.incrementTaskCount()
if disable, _ := testhooks.Get[bool](c.partitionMgr.engine.testHooks, testhooks.MatchingDisableSyncMatch); disable {
return false, nil
}
}
childCtx, cancel := newChildContext(ctx, c.config.SyncMatchWaitDuration(), time.Second)
defer cancel()
return c.matcher.Offer(childCtx, task)
}
// [cleanup-wv-pre-release]
func (c *physicalTaskQueueManagerImpl) ensureRegisteredInDeployment(
ctx context.Context,
namespaceEntry *namespace.Namespace,
pollMetadata *pollMetadata,
) error {
workerDeployment := worker_versioning.DeploymentFromCapabilities(pollMetadata.workerVersionCapabilities, pollMetadata.deploymentOptions)
if workerDeployment == nil {
return nil
}
if !c.partitionMgr.engine.config.EnableDeployments(namespaceEntry.Name().String()) {
return errDeploymentsNotAllowed
}
// lock so that only one poll does the update and the rest wait for it
c.deploymentLock.Lock()
defer c.deploymentLock.Unlock()
if c.deploymentRegistered {
// deployment already registered
return nil
}
if c.deploymentRegisterError != nil {
// deployment not possible due to registration limits
return c.deploymentRegisterError
}
userData, _, err := c.partitionMgr.GetUserDataManager().GetUserData()
if err != nil {
return err
}
deploymentData := userData.GetData().GetPerType()[int32(c.queue.TaskType())].GetDeploymentData()
if hasDeploymentVersion(deploymentData, worker_versioning.DeploymentVersionFromDeployment(workerDeployment)) {
// already registered in user data, we can assume the workflow is running.
// TODO: consider replication scenarios where user data is replicated before
// the deployment workflow.
return nil
}
// we need to update the deployment workflow to tell it about this task queue
// TODO: add some backoff here if we got an error last time
if c.firstPoll.IsZero() {
c.firstPoll = c.partitionMgr.engine.timeSource.Now()
}
err = c.partitionMgr.engine.deploymentStoreClient.RegisterTaskQueueWorker(
ctx, namespaceEntry, workerDeployment, c.queue.TaskQueueFamily().Name(), c.queue.TaskType(), c.firstPoll,
"matching service", uuid.New())
if err != nil {
var errTooMany deployment.ErrMaxTaskQueuesInDeployment
if errors.As(err, &errTooMany) {
c.deploymentRegisterError = errTooMany
}
return err
}
// the deployment workflow will register itself in this task queue's user data.
// wait for it to propagate here.
for {
userData, userDataChanged, err := c.partitionMgr.GetUserDataManager().GetUserData()
if err != nil {
return err
}
deploymentData := userData.GetData().GetPerType()[int32(c.queue.TaskType())].GetDeploymentData()
if hasDeploymentVersion(deploymentData, worker_versioning.DeploymentVersionFromDeployment(workerDeployment)) {
break
}
select {
case <-userDataChanged:
case <-ctx.Done():
c.logger.Error("timed out waiting for deployment to appear in user data")
return ctx.Err()
}
}
c.deploymentRegistered = true
return nil
}
func (c *physicalTaskQueueManagerImpl) ensureRegisteredInDeploymentVersion(
ctx context.Context,
namespaceEntry *namespace.Namespace,
pollMetadata *pollMetadata,
) error {
workerDeployment := worker_versioning.DeploymentFromCapabilities(pollMetadata.workerVersionCapabilities, pollMetadata.deploymentOptions)
if workerDeployment == nil {
return nil
}
if !c.partitionMgr.engine.config.EnableDeploymentVersions(namespaceEntry.Name().String()) {
return errMissingDeploymentVersion
}
// lock so that only one poll does the update and the rest wait for it
c.deploymentLock.Lock()
defer c.deploymentLock.Unlock()
if c.deploymentVersionRegistered {
// deployment version already registered
return nil
}
if c.deploymentRegisterError != nil {
// deployment not possible due to registration limits
return c.deploymentRegisterError
}
userData, _, err := c.partitionMgr.GetUserDataManager().GetUserData()
if err != nil {
return err
}
deploymentData := userData.GetData().GetPerType()[int32(c.queue.TaskType())].GetDeploymentData()
if findDeploymentVersion(deploymentData, worker_versioning.DeploymentVersionFromDeployment(workerDeployment)) != -1 {
// already registered in user data, we can assume the workflow is running.
// TODO: consider replication scenarios where user data is replicated before
// the deployment workflow.
return nil
}
// we need to update the deployment workflow to tell it about this task queue
// TODO: add some backoff here if we got an error last time
err = c.partitionMgr.engine.workerDeploymentClient.RegisterTaskQueueWorker(
ctx, namespaceEntry, workerDeployment.SeriesName, workerDeployment.BuildId, c.queue.TaskQueueFamily().Name(), c.queue.TaskType(),
"matching service", uuid.New())
if err != nil {
var errTooMany deployment.ErrMaxTaskQueuesInDeployment
if errors.As(err, &errTooMany) {
c.deploymentRegisterError = errTooMany
}
return err
}
// the deployment workflow will register itself in this task queue's user data.
// wait for it to propagate here.
for {
userData, userDataChanged, err := c.partitionMgr.GetUserDataManager().GetUserData()
if err != nil {
return err
}
deploymentData := userData.GetData().GetPerType()[int32(c.queue.TaskType())].GetDeploymentData()
if findDeploymentVersion(deploymentData, worker_versioning.DeploymentVersionFromDeployment(workerDeployment)) >= 0 {
break
}
select {
case <-userDataChanged:
case <-ctx.Done():
c.logger.Error("timed out waiting for worker deployment version to appear in user data")
return ctx.Err()
}
}
c.deploymentVersionRegistered = true
return nil
}
// newChildContext creates a child context with desired timeout.
// if tailroom is non-zero, then child context timeout will be
// the minOf(parentCtx.Deadline()-tailroom, timeout). Use this
// method to create child context when childContext cannot use
// all of parent's deadline but instead there is a need to leave
// some time for parent to do some post-work
func newChildContext(
parent context.Context,
timeout time.Duration,
tailroom time.Duration,
) (context.Context, context.CancelFunc) {
if parent.Err() != nil {
return parent, func() {}
}
deadline, ok := parent.Deadline()
if !ok {
return context.WithTimeout(parent, timeout)
}
remaining := time.Until(deadline) - tailroom
if remaining < timeout {
timeout = max(0, remaining)
}
return context.WithTimeout(parent, timeout)
}
func (c *physicalTaskQueueManagerImpl) QueueKey() *PhysicalTaskQueueKey {
return c.queue
}
func (c *physicalTaskQueueManagerImpl) newIOContext() (context.Context, context.CancelFunc) {
ctx, cancel := context.WithTimeout(context.Background(), ioTimeout)
return c.partitionMgr.callerInfoContext(ctx), cancel
}
func (c *physicalTaskQueueManagerImpl) UnloadFromPartitionManager(unloadCause unloadCause) {
c.partitionMgr.unloadPhysicalQueue(c, unloadCause)
}
func (c *physicalTaskQueueManagerImpl) ShouldEmitGauges() bool {
return c.config.BreakdownMetricsByTaskQueue() &&
c.config.BreakdownMetricsByPartition() &&
(!c.queue.IsVersioned() || c.config.BreakdownMetricsByBuildID())
}