Files
psrpc/pkg/client/multi.go
T

171 lines
4.1 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 client
import (
"context"
"time"
"google.golang.org/protobuf/proto"
"github.com/livekit/psrpc"
"github.com/livekit/psrpc/internal"
"github.com/livekit/psrpc/internal/bus"
"github.com/livekit/psrpc/internal/interceptors"
"github.com/livekit/psrpc/pkg/info"
"github.com/livekit/psrpc/pkg/metadata"
"github.com/livekit/psrpc/pkg/rand"
)
func RequestMulti[ResponseType proto.Message](
ctx context.Context,
c *RPCClient,
rpc string,
topic []string,
request proto.Message,
opts ...psrpc.RequestOption,
) (rChan <-chan *psrpc.Response[ResponseType], err error) {
if c.closed.IsBroken() {
return nil, psrpc.ErrClientClosed
}
i := c.GetInfo(rpc, topic)
// request hooks
for _, hook := range c.RequestHooks {
hook(ctx, request, i.RPCInfo)
}
resChan := make(chan *psrpc.Response[ResponseType], c.ChannelSize)
m := &multiRPC[ResponseType]{
c: c,
i: i,
requestID: rand.NewRequestID(),
resChan: resChan,
}
reqInterceptors := getRequestInterceptors(
c.MultiRPCInterceptors,
getRequestOpts(ctx, i, c.ClientOpts, opts...).Interceptors,
)
m.handler = interceptors.ChainClientInterceptors[psrpc.ClientMultiRPCHandler](
reqInterceptors, i, m,
)
if err = m.handler.Send(ctx, request, opts...); err != nil {
for _, hook := range c.ResponseHooks {
hook(ctx, request, i.RPCInfo, nil, err)
}
return
}
return resChan, nil
}
type multiRPC[ResponseType proto.Message] struct {
c *RPCClient
i *info.RequestInfo
requestID string
handler psrpc.ClientMultiRPCHandler
resChan chan<- *psrpc.Response[ResponseType]
}
func (m *multiRPC[ResponseType]) Send(ctx context.Context, req proto.Message, opts ...psrpc.RequestOption) error {
o := getRequestOpts(ctx, m.i, m.c.ClientOpts, opts...)
b, err := bus.SerializePayload(req)
if err != nil {
return psrpc.NewError(psrpc.MalformedRequest, err)
}
now := time.Now()
ir := &internal.Request{
RequestId: m.requestID,
ClientId: m.c.ID,
SentAt: now.UnixNano(),
Expiry: now.Add(o.Timeout).UnixNano(),
Multi: true,
RawRequest: b,
Metadata: metadata.OutgoingContextMetadata(ctx),
}
resChan := make(chan *internal.Response, m.c.ChannelSize)
m.c.mu.Lock()
m.c.responseChannels[m.requestID] = resChan
m.c.mu.Unlock()
go m.handleResponses(ctx, req, resChan, o)
if err = m.c.bus.Publish(ctx, m.i.GetRPCChannel(), ir); err != nil {
return psrpc.NewError(psrpc.Internal, err)
}
return nil
}
func (m *multiRPC[ResponseType]) handleResponses(
ctx context.Context,
req proto.Message,
resChan chan *internal.Response,
opts psrpc.RequestOpts,
) {
timer := time.NewTimer(opts.Timeout)
for {
select {
case res := <-resChan:
var v ResponseType
var err error
if res.Error != "" {
err = psrpc.NewErrorFromResponse(res.Code, res.Error, res.ErrorDetails...)
} else {
v, err = bus.DeserializePayload[ResponseType](res.RawResponse)
if err != nil {
err = psrpc.NewError(psrpc.MalformedResponse, err)
}
}
// response hooks
for _, hook := range m.c.ResponseHooks {
hook(ctx, req, m.i.RPCInfo, v, err)
}
m.handler.Recv(v, err)
case <-timer.C:
m.handler.Close()
return
case <-ctx.Done():
m.handler.Close()
return
}
}
}
func (m *multiRPC[ResponseType]) Recv(msg proto.Message, err error) {
m.resChan <- &psrpc.Response[ResponseType]{
Result: msg.(ResponseType),
Err: err,
}
}
func (m *multiRPC[ResponseType]) Close() {
m.c.mu.Lock()
delete(m.c.responseChannels, m.requestID)
m.c.mu.Unlock()
close(m.resChan)
}