Files
zeekay 24eca416c1 fork: retarget psrpc at github.com/hanzokv/go
PSRPC for Hanzo LiveKit, forked from livekit/psrpc (Apache-2.0). Two rewrites:

  github.com/livekit/psrpc      -> github.com/hanzoai/psrpc
  github.com/redis/go-redis/v9  -> github.com/hanzokv/go/v9

psrpc's message bus (bus.go, internal/bus/bus_redis.go, internal/bus/bustest)
speaks to a KV over go-redis. After hanzoai/livekit moved to our client, psrpc
was the one remaining edge pulling upstream go-redis into that binary — not our
code, but our dependency, the same shape redsync had for hanzoai/git.

The other livekit/* modules (protocol, mediatransportutil, mageutil) are NOT
forked and should not be: none of them touch a KV client, so they are ordinary
external dependencies and their names are addresses.
2026-07-26 08:12:46 -07:00

152 lines
3.9 KiB
Go

// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package middleware
import (
"context"
"time"
"google.golang.org/protobuf/proto"
"github.com/hanzoai/psrpc"
)
type RetryOptions struct {
MaxAttempts int
Timeout time.Duration
Backoff time.Duration
IsRecoverable func(err error) bool
GetRetryParameters func(err error, attempt int) (retry bool, timeout time.Duration, waitTime time.Duration) // will override the MaxAttempts, Timeout and Backoff parameters
}
func WithRPCRetries(opt RetryOptions) psrpc.ClientOption {
return psrpc.WithClientRPCInterceptors(NewRPCRetryInterceptor(opt))
}
func NewRPCRetryInterceptor(opt RetryOptions) psrpc.ClientRPCInterceptor {
return func(rpcInfo psrpc.RPCInfo, next psrpc.ClientRPCHandler) psrpc.ClientRPCHandler {
return func(ctx context.Context, req proto.Message, opts ...psrpc.RequestOption) (res proto.Message, err error) {
err = retry(opt, ctx.Done(), func(timeout time.Duration) error {
nextOpts := opts
if timeout > 0 {
nextOpts = make([]psrpc.RequestOption, len(opts)+1)
copy(nextOpts, opts)
nextOpts[len(opts)] = psrpc.WithRequestTimeout(timeout)
}
res, err = next(ctx, req, nextOpts...)
return err
})
return
}
}
}
func isTimeout(err error) bool {
code, ok := psrpc.GetErrorCode(err)
if !ok {
// retry all internal errors that were not explicitly wrapped
return true
}
return code == psrpc.DeadlineExceeded || code == psrpc.Unavailable
}
func getRetryWithBackoffParameters(o RetryOptions) func(err error, attempt int) (retry bool, timeout time.Duration, waitTime time.Duration) {
timeout := o.Timeout
return func(err error, attempt int) (bool, time.Duration, time.Duration) {
if !o.IsRecoverable(err) || attempt == o.MaxAttempts {
return false, 0, 0
}
timeout += o.Backoff
return true, timeout, 0
}
}
func retry(opt RetryOptions, done <-chan struct{}, fn func(timeout time.Duration) error) error {
timeout := opt.Timeout
attempt := 1
if opt.IsRecoverable == nil {
opt.IsRecoverable = isTimeout
}
if opt.GetRetryParameters == nil {
opt.GetRetryParameters = getRetryWithBackoffParameters(opt)
}
for {
err := fn(timeout)
if err == nil {
return nil
}
var retry bool
var waitTime time.Duration
retry, timeout, waitTime = opt.GetRetryParameters(err, attempt)
if !retry {
return err
}
attempt++
if waitTime > 0 { // Avoid randomenss
select {
case <-done:
return psrpc.ErrRequestCanceled
case <-time.After(waitTime):
}
} else {
select {
case <-done:
return psrpc.ErrRequestCanceled
default:
}
}
}
}
func WithStreamRetries(opt RetryOptions) psrpc.ClientOption {
return psrpc.WithClientStreamInterceptors(NewStreamRetryInterceptor(opt))
}
func NewStreamRetryInterceptor(opt RetryOptions) psrpc.StreamInterceptor {
return func(rpcInfo psrpc.RPCInfo, next psrpc.StreamHandler) psrpc.StreamHandler {
return &streamRetryInterceptor{
StreamHandler: next,
opt: opt,
}
}
}
type streamRetryInterceptor struct {
psrpc.StreamHandler
opt RetryOptions
}
func (s *streamRetryInterceptor) Send(msg proto.Message, opts ...psrpc.StreamOption) (err error) {
return retry(s.opt, nil, func(timeout time.Duration) error {
nextOpts := opts
if timeout > 0 {
nextOpts = make([]psrpc.StreamOption, len(opts)+1)
copy(nextOpts, opts)
nextOpts[len(opts)] = psrpc.WithTimeout(timeout)
}
return s.StreamHandler.Send(msg, nextOpts...)
})
}