mirror of
https://github.com/hanzoai/tasks.git
synced 2026-10-04 02:04:03 +00:00
## What changed? Errors that occur prior to the telemetry interceptor are not captured, meaning there is no log, the request isn't counted, and the error isn't counted. I've extracted the telemetry interceptors error handling into a shared obj that I've wired into the NamespaceHandover error handling for now. An alternative approach was to move the TelemetryInterceptor earlier in the chain, but this would have caused double counting on redirects, and adding another dimension to our metrics (pre/post redirect) here to address the double counting would cause other classes of issues As of now, other interceptors that are added prior to the TelemetryInterceptor are also at risk of this. We can wrap PreTelemetry errors and have a new interceptor handle these if we want to more generically address this issue - view my commit history for "Experimental" (which I reverted) to see an example of this. ## Why? To specifically track metrics/logs on NamespaceHandover failures. ## How did you test it? - [x] built - [ ] run locally and tested manually - [ ] covered by existing tests - [ ] added new unit test(s) - [ ] added new functional test(s) ## Potential risks Potential metrics loss if for some reason telemetry metric error handling is not wired correctly after the refactor.
164 lines
4.6 KiB
Go
164 lines
4.6 KiB
Go
package interceptor
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
enumspb "go.temporal.io/api/enums/v1"
|
|
"go.temporal.io/server/common"
|
|
"go.temporal.io/server/common/api"
|
|
"go.temporal.io/server/common/clock"
|
|
"go.temporal.io/server/common/dynamicconfig"
|
|
"go.temporal.io/server/common/log"
|
|
"go.temporal.io/server/common/metrics"
|
|
"go.temporal.io/server/common/namespace"
|
|
"google.golang.org/grpc"
|
|
)
|
|
|
|
const (
|
|
ctxTailRoom = time.Millisecond * 100
|
|
)
|
|
|
|
var _ grpc.UnaryServerInterceptor = (*NamespaceHandoverInterceptor)(nil).Intercept
|
|
|
|
type (
|
|
// NamespaceHandoverInterceptor handles the namespace in handover replication state
|
|
NamespaceHandoverInterceptor struct {
|
|
namespaceRegistry namespace.Registry
|
|
timeSource clock.TimeSource
|
|
enabledForNS dynamicconfig.BoolPropertyFnWithNamespaceFilter
|
|
nsCacheRefreshInterval dynamicconfig.DurationPropertyFn
|
|
metricsHandler metrics.Handler
|
|
logger log.Logger
|
|
requestErrorHandler ErrorHandler
|
|
}
|
|
)
|
|
|
|
func NewNamespaceHandoverInterceptor(
|
|
dc *dynamicconfig.Collection,
|
|
namespaceRegistry namespace.Registry,
|
|
metricsHandler metrics.Handler,
|
|
logger log.Logger,
|
|
timeSource clock.TimeSource,
|
|
requestErrorHandler ErrorHandler,
|
|
) *NamespaceHandoverInterceptor {
|
|
|
|
return &NamespaceHandoverInterceptor{
|
|
enabledForNS: dynamicconfig.EnableNamespaceHandoverWait.Get(dc),
|
|
nsCacheRefreshInterval: dynamicconfig.NamespaceCacheRefreshInterval.Get(dc),
|
|
namespaceRegistry: namespaceRegistry,
|
|
metricsHandler: metricsHandler,
|
|
logger: logger,
|
|
timeSource: timeSource,
|
|
requestErrorHandler: requestErrorHandler,
|
|
}
|
|
}
|
|
|
|
func (i *NamespaceHandoverInterceptor) Intercept(
|
|
ctx context.Context,
|
|
req any,
|
|
info *grpc.UnaryServerInfo,
|
|
handler grpc.UnaryHandler,
|
|
) (_ any, retError error) {
|
|
defer log.CapturePanic(i.logger, &retError)
|
|
|
|
if !strings.HasPrefix(info.FullMethod, api.WorkflowServicePrefix) {
|
|
return handler(ctx, req)
|
|
}
|
|
|
|
// review which method is allowed
|
|
methodName := api.MethodName(info.FullMethod)
|
|
namespaceName := MustGetNamespaceName(i.namespaceRegistry, req)
|
|
|
|
if namespaceName != namespace.EmptyName && i.enabledForNS(namespaceName.String()) {
|
|
var waitTime *time.Duration
|
|
defer func() {
|
|
if waitTime != nil {
|
|
metrics.HandoverWaitLatency.With(i.metricsHandler).Record(*waitTime)
|
|
}
|
|
}()
|
|
waitTime, err := i.waitNamespaceHandoverUpdate(ctx, namespaceName, methodName)
|
|
if err != nil {
|
|
metricsHandler, logTags := CreateUnaryMetricsHandlerLogTags(
|
|
i.metricsHandler,
|
|
req,
|
|
info.FullMethod,
|
|
methodName,
|
|
namespaceName,
|
|
)
|
|
// count the request as this will not be counted
|
|
metrics.ServiceRequests.With(metricsHandler).Record(1)
|
|
|
|
i.requestErrorHandler.HandleError(
|
|
req,
|
|
info.FullMethod,
|
|
metricsHandler,
|
|
logTags,
|
|
err,
|
|
namespaceName,
|
|
)
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return handler(ctx, req)
|
|
}
|
|
|
|
func (i *NamespaceHandoverInterceptor) waitNamespaceHandoverUpdate(
|
|
ctx context.Context,
|
|
namespaceName namespace.Name,
|
|
methodName string,
|
|
) (waitTime *time.Duration, retErr error) {
|
|
if _, ok := allowedMethodsDuringHandover[methodName]; ok {
|
|
return nil, nil
|
|
}
|
|
|
|
startTime := i.timeSource.Now()
|
|
namespaceData, err := i.namespaceRegistry.GetNamespace(namespaceName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if namespaceData.ReplicationState() == enumspb.REPLICATION_STATE_HANDOVER {
|
|
cbID := uuid.New()
|
|
waitReplicationStateUpdate := make(chan struct{})
|
|
i.namespaceRegistry.RegisterStateChangeCallback(cbID, func(ns *namespace.Namespace, deletedFromDb bool) {
|
|
if ns.ID().String() != namespaceData.ID().String() {
|
|
return
|
|
}
|
|
if ns.State() != enumspb.NAMESPACE_STATE_REGISTERED ||
|
|
deletedFromDb ||
|
|
ns.ReplicationState() != enumspb.REPLICATION_STATE_HANDOVER ||
|
|
!ns.IsGlobalNamespace() {
|
|
// Stop wait on state change if:
|
|
// 1. namespace is deleting/deleted
|
|
// 2. namespace is not in handover
|
|
// 3. namespace is not global
|
|
select {
|
|
case <-waitReplicationStateUpdate:
|
|
default:
|
|
close(waitReplicationStateUpdate)
|
|
}
|
|
}
|
|
})
|
|
|
|
maxWaitDuration := i.nsCacheRefreshInterval() // cache refresh time
|
|
if deadline, ok := ctx.Deadline(); ok {
|
|
maxWaitDuration = max(0, time.Until(deadline)-ctxTailRoom)
|
|
}
|
|
returnTimer := time.NewTimer(maxWaitDuration)
|
|
var handoverErr error
|
|
select {
|
|
case <-returnTimer.C:
|
|
handoverErr = common.ErrNamespaceHandover
|
|
case <-waitReplicationStateUpdate:
|
|
returnTimer.Stop()
|
|
}
|
|
i.namespaceRegistry.UnregisterStateChangeCallback(cbID)
|
|
waitTime := time.Since(startTime)
|
|
return &waitTime, handoverErr
|
|
}
|
|
return nil, nil
|
|
}
|