go.temporal.io/server@v1.23.0/common/rpc/interceptor/concurrent_request_limit.go (about)

     1  // The MIT License
     2  //
     3  // Copyright (c) 2020 Temporal Technologies Inc.  All rights reserved.
     4  //
     5  // Copyright (c) 2020 Uber Technologies, Inc.
     6  //
     7  // Permission is hereby granted, free of charge, to any person obtaining a copy
     8  // of this software and associated documentation files (the "Software"), to deal
     9  // in the Software without restriction, including without limitation the rights
    10  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    11  // copies of the Software, and to permit persons to whom the Software is
    12  // furnished to do so, subject to the following conditions:
    13  //
    14  // The above copyright notice and this permission notice shall be included in
    15  // all copies or substantial portions of the Software.
    16  //
    17  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    18  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    19  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    20  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    21  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    22  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    23  // THE SOFTWARE.
    24  
    25  package interceptor
    26  
    27  import (
    28  	"context"
    29  	"sync"
    30  	"sync/atomic"
    31  
    32  	enumspb "go.temporal.io/api/enums/v1"
    33  	"go.temporal.io/api/serviceerror"
    34  	"go.temporal.io/api/workflowservice/v1"
    35  	"go.temporal.io/server/common/quotas"
    36  	"google.golang.org/grpc"
    37  
    38  	"go.temporal.io/server/common/log"
    39  	"go.temporal.io/server/common/metrics"
    40  	"go.temporal.io/server/common/namespace"
    41  )
    42  
    43  type (
    44  	// ConcurrentRequestLimitInterceptor intercepts requests to the server and enforces a limit on the number of
    45  	// requests that can be in-flight at any given time, according to the configured quotas.
    46  	ConcurrentRequestLimitInterceptor struct {
    47  		namespaceRegistry namespace.Registry
    48  		logger            log.Logger
    49  		quotaCalculator   quotas.ClusterAwareNamespaceSpecificQuotaCalculator
    50  		// tokens is a map of method name to the number of tokens that should be consumed for that method. If there is
    51  		// no entry for a method, then no tokens will be consumed, so the method will not be limited.
    52  		tokens map[string]int
    53  
    54  		sync.Mutex
    55  		activeTokensCount map[string]*int32
    56  	}
    57  )
    58  
    59  var (
    60  	_ grpc.UnaryServerInterceptor = (*ConcurrentRequestLimitInterceptor)(nil).Intercept
    61  
    62  	ErrNamespaceCountLimitServerBusy = serviceerror.NewResourceExhausted(enumspb.RESOURCE_EXHAUSTED_CAUSE_CONCURRENT_LIMIT, "namespace concurrent poller limit exceeded")
    63  )
    64  
    65  func NewConcurrentRequestLimitInterceptor(
    66  	namespaceRegistry namespace.Registry,
    67  	memberCounter quotas.MemberCounter,
    68  	logger log.Logger,
    69  	perInstanceQuota func(ns string) int,
    70  	globalQuota func(ns string) int,
    71  	tokens map[string]int,
    72  ) *ConcurrentRequestLimitInterceptor {
    73  	return &ConcurrentRequestLimitInterceptor{
    74  		namespaceRegistry: namespaceRegistry,
    75  		logger:            logger,
    76  		quotaCalculator: quotas.ClusterAwareNamespaceSpecificQuotaCalculator{
    77  			MemberCounter:    memberCounter,
    78  			PerInstanceQuota: perInstanceQuota,
    79  			GlobalQuota:      globalQuota,
    80  		},
    81  		tokens:            tokens,
    82  		activeTokensCount: make(map[string]*int32),
    83  	}
    84  }
    85  
    86  func (ni *ConcurrentRequestLimitInterceptor) Intercept(
    87  	ctx context.Context,
    88  	req interface{},
    89  	info *grpc.UnaryServerInfo,
    90  	handler grpc.UnaryHandler,
    91  ) (interface{}, error) {
    92  	_, methodName := SplitMethodName(info.FullMethod)
    93  	// token will default to 0
    94  	token := ni.tokens[methodName]
    95  
    96  	if token != 0 {
    97  		// for GetWorkflowExecutionHistoryRequest, we only care about long poll requests
    98  		longPollReq, ok := req.(*workflowservice.GetWorkflowExecutionHistoryRequest)
    99  		if ok && !longPollReq.WaitNewEvent {
   100  			// ignore non-long-poll GetHistory calls.
   101  			token = 0
   102  		}
   103  	}
   104  
   105  	if token != 0 {
   106  		nsName := MustGetNamespaceName(ni.namespaceRegistry, req)
   107  		counter := ni.counter(nsName, methodName)
   108  		count := atomic.AddInt32(counter, int32(token))
   109  		defer atomic.AddInt32(counter, -int32(token))
   110  
   111  		handler := GetMetricsHandlerFromContext(ctx, ni.logger)
   112  		metrics.ServicePendingRequests.With(handler).Record(float64(count))
   113  
   114  		// frontend.namespaceCount is applied per poller type temporarily to prevent
   115  		// one poller type to take all token waiting in the long poll.
   116  		if float64(count) > ni.quotaCalculator.GetQuota(nsName.String()) {
   117  			return nil, ErrNamespaceCountLimitServerBusy
   118  		}
   119  	}
   120  
   121  	return handler(ctx, req)
   122  }
   123  
   124  func (ni *ConcurrentRequestLimitInterceptor) counter(
   125  	namespace namespace.Name,
   126  	methodName string,
   127  ) *int32 {
   128  	key := ni.getTokenKey(namespace, methodName)
   129  
   130  	ni.Lock()
   131  	defer ni.Unlock()
   132  
   133  	counter, ok := ni.activeTokensCount[key]
   134  	if !ok {
   135  		counter = new(int32)
   136  		ni.activeTokensCount[key] = counter
   137  	}
   138  	return counter
   139  }
   140  
   141  func (ni *ConcurrentRequestLimitInterceptor) getTokenKey(
   142  	namespace namespace.Name,
   143  	methodName string,
   144  ) string {
   145  	return namespace.String() + "/" + methodName
   146  }