github.com/livekit/protocol@v1.16.1-0.20240517185851-47e4c6bba773/rpc/egress_client.go (about)

     1  // Copyright 2023 LiveKit, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package rpc
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"math"
    21  	"math/rand"
    22  	"time"
    23  
    24  	"github.com/livekit/protocol/livekit"
    25  	"github.com/livekit/psrpc"
    26  	"github.com/livekit/psrpc/pkg/middleware"
    27  )
    28  
    29  const (
    30  	retries     = 3
    31  	backoffBase = 1 * time.Second
    32  )
    33  
    34  type EgressClient interface {
    35  	EgressInternalClient
    36  	EgressHandlerClient
    37  }
    38  
    39  type egressClient struct {
    40  	EgressInternalClient
    41  	EgressHandlerClient
    42  }
    43  
    44  func isErrRecoverable(err error) bool {
    45  	var e psrpc.Error
    46  	if !errors.As(err, &e) {
    47  		return true
    48  	}
    49  	return e.Code() == psrpc.DeadlineExceeded ||
    50  		e.Code() == psrpc.ResourceExhausted ||
    51  		e.Code() == psrpc.Unavailable
    52  }
    53  
    54  func NewEgressClient(params ClientParams) (EgressClient, error) {
    55  	if params.Bus == nil {
    56  		return nil, nil
    57  	}
    58  
    59  	opts := params.Options()
    60  	timeout := params.Timeout
    61  	if timeout < 10*time.Second {
    62  		timeout = 10 * time.Second
    63  	}
    64  
    65  	internalOpts := append(opts,
    66  		psrpc.WithClientChannelSize(1000),
    67  		middleware.WithRPCRetries(middleware.RetryOptions{
    68  			Timeout: timeout,
    69  			GetRetryParameters: func(err error, attempt int) (retry bool, timeout time.Duration, waitTime time.Duration) {
    70  				if !isErrRecoverable(err) {
    71  					return false, 0, 0
    72  				}
    73  
    74  				if attempt >= retries {
    75  					return false, 0, 0
    76  				}
    77  
    78  				// backoff = base * 2 ^ (attempt - 1) * rand[1,2)
    79  				backoff := time.Duration(float64(backoffBase) * math.Pow(2, float64(attempt-1)) * (rand.Float64() + 1))
    80  				timeout = time.Duration(float64(timeout) * math.Pow(2, float64(attempt)))
    81  
    82  				return true, timeout, backoff
    83  			},
    84  		}))
    85  
    86  	internalClient, err := NewEgressInternalClient(params.Bus, internalOpts...)
    87  	if err != nil {
    88  		return nil, err
    89  	}
    90  
    91  	handlerClient, err := NewEgressHandlerClient(params.Bus, opts...)
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  
    96  	return &egressClient{
    97  		EgressInternalClient: internalClient,
    98  		EgressHandlerClient:  handlerClient,
    99  	}, nil
   100  }
   101  
   102  func (c *egressClient) StartEgress(ctx context.Context, topic string, req *StartEgressRequest, opts ...psrpc.RequestOption) (*livekit.EgressInfo, error) {
   103  	o := append([]psrpc.RequestOption{
   104  		psrpc.WithSelectionOpts(psrpc.SelectionOpts{
   105  			MaximumAffinity:     1,
   106  			AffinityTimeout:     time.Second,
   107  			ShortCircuitTimeout: time.Millisecond * 500,
   108  		}),
   109  	}, opts...)
   110  	return c.EgressInternalClient.StartEgress(ctx, topic, req, o...)
   111  }