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 }