Files
LocalAI/pkg/grpc/channel_stream.go
T
Richard Palethorpe 3de91d48d9 feat(motion): add GEM-X backend and duplex streaming API
Add the GEM-X native backend with live pose and root displacement output, bounded duplex WebSocket streaming, session authorization, metrics and usage accounting. Share the embedded bidirectional RPC channel adapter across streaming backends.

Include backend build and gallery integration, API documentation, schemas and tests.

Assisted-by: Codex:gpt-6
Signed-off-by: Richard Palethorpe <io@richiejp.com>
(cherry picked from commit 016dc3d71a)
2026-10-13 08:27:24 +00:00

130 lines
3.4 KiB
Go

// SPDX-License-Identifier: MIT
package grpc
import (
"context"
"io"
"sync"
"google.golang.org/grpc/metadata"
)
// channelStream carries in-process bidirectional RPCs. Publishing the terminal
// error before closing responses lets callers drain the response tail reliably.
type channelStream[Request, Response any] struct {
ctx context.Context
requests chan Request
responses chan Response
done chan struct{}
err error
}
type channelStreamServer[Request, Response any] struct {
*channelStream[Request, Response]
}
type channelStreamClient[Request, Response any] struct {
*channelStream[Request, Response]
sendMu sync.Mutex
closed bool
cleanup *streamCleanup
}
func newChannelStream[Request, Response any](ctx context.Context, capacity int, serve func(*channelStreamServer[Request, Response]) error) *channelStreamClient[Request, Response] {
stream := &channelStream[Request, Response]{ctx: ctx, requests: make(chan Request, capacity), responses: make(chan Response, capacity), done: make(chan struct{})}
client := &channelStreamClient[Request, Response]{channelStream: stream, cleanup: newStreamCleanup(ctx, nil)}
go func() {
stream.err = serve(&channelStreamServer[Request, Response]{stream})
close(stream.done)
close(stream.responses)
}()
return client
}
func (s *channelStream[Request, Response]) Context() context.Context { return s.ctx }
func (s *channelStreamServer[Request, Response]) Send(response Response) error {
select {
case s.responses <- response:
return nil
case <-s.ctx.Done():
return s.ctx.Err()
}
}
func (s *channelStreamServer[Request, Response]) Recv() (Request, error) {
select {
case request, ok := <-s.requests:
if ok {
return request, nil
}
case <-s.ctx.Done():
var zero Request
return zero, s.ctx.Err()
}
var zero Request
return zero, io.EOF
}
func (s *channelStreamServer[Request, Response]) SetHeader(metadata.MD) error { return nil }
func (s *channelStreamServer[Request, Response]) SendHeader(metadata.MD) error { return nil }
func (s *channelStreamServer[Request, Response]) SetTrailer(metadata.MD) {}
func (s *channelStreamServer[Request, Response]) SendMsg(message any) error {
if response, ok := message.(Response); ok {
return s.Send(response)
}
return nil
}
// Generated bidirectional handlers use typed Recv directly.
func (s *channelStreamServer[Request, Response]) RecvMsg(any) error { return nil }
func (c *channelStreamClient[Request, Response]) AddCleanup(fn func()) { c.cleanup.add(fn) }
func (c *channelStreamClient[Request, Response]) Send(request Request) error {
c.sendMu.Lock()
defer c.sendMu.Unlock()
if c.closed {
return io.EOF
}
select {
case <-c.done:
return io.EOF
case <-c.ctx.Done():
return c.ctx.Err()
default:
}
select {
case c.requests <- request:
return nil
case <-c.done:
return io.EOF
case <-c.ctx.Done():
return c.ctx.Err()
}
}
func (c *channelStreamClient[Request, Response]) Recv() (Response, error) {
var zero Response
select {
case response, ok := <-c.responses:
if ok {
return response, nil
}
c.cleanup.finish()
if c.err != nil {
return zero, c.err
}
return zero, io.EOF
case <-c.ctx.Done():
c.cleanup.finish()
return zero, c.ctx.Err()
}
}
// Half-close only the request side; the server may still produce responses.
func (c *channelStreamClient[Request, Response]) CloseSend() error {
c.sendMu.Lock()
defer c.sendMu.Unlock()
if !c.closed {
c.closed = true
close(c.requests)
}
return nil
}