vitess.io/vitess@v0.16.2/go/vt/vtctl/internal/grpcshim/bidi_stream.go (about)

     1  /*
     2  Copyright 2021 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  	http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package grpcshim
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"io"
    23  	"sync"
    24  
    25  	"google.golang.org/grpc"
    26  	"google.golang.org/grpc/metadata"
    27  )
    28  
    29  // ErrStreamClosed is the error types embedding BidiStream should return when
    30  // their Send method is called and IsClosed returns true.
    31  var ErrStreamClosed = errors.New("stream closed for sending")
    32  
    33  // BidiStream is a shim struct implementing both the grpc.ClientStream and
    34  // grpc.ServerStream interfaces. It can be embedded into other types that need
    35  // all of those methods to satisfy the compiler, but are only interested in the
    36  // parameterized Send/Recv methods typically called by gRPC streaming servers
    37  // and clients. For example, in the localvtctldclient:
    38  //
    39  //	type backupStreamAdapter struct {
    40  //		*grpcshim.BidiStream
    41  //		ch chan *vtctldatapb.BackupResponse
    42  //	}
    43  //
    44  //	func (stream *backupStreamAdapter) Recv() (*vtctldatapb.BackupResponse, error) {
    45  //		select {
    46  //		case <-stream.Context().Done():
    47  //			return nil, stream.Context().Err()
    48  //		case <-stream.Closed():
    49  //			// Stream has been closed for future sends. If there are messages that
    50  //			// have already been sent, receive them until there are no more. After
    51  //			// all sent messages have been received, Recv will return the CloseErr.
    52  //			select {
    53  //			case msg := <-stream.ch:
    54  //				return msg, nil
    55  //			default:
    56  //				return nil, stream.CloseErr()
    57  //			}
    58  //		case err := <-stream.ErrCh:
    59  //			return nil, err
    60  //		case msg := <-stream.ch:
    61  //			return msg, nil
    62  //		}
    63  //	}
    64  //
    65  //	func (stream *backupStreamAdapter) Send(msg *vtctldatapb.BackupResponse) error {
    66  //		select {
    67  //		case <-stream.Context().Done():
    68  //			return stream.Context().Err()
    69  //		case <-stream.Closed():
    70  //			return grpcshim.ErrStreamClosed
    71  //		case stream.ch <- msg:
    72  //			return nil
    73  //		}
    74  //	}
    75  //
    76  //	// Backup is part of the vtctlservicepb.VtctldClient interface.
    77  //	func (client *localVtctldClient) Backup(ctx context.Context, in *vtctldatapb.BackupRequest, opts ...grpc.CallOption) (vtctlservicepb.Vtctld_BackupClient, error) {
    78  //		stream := &backupStreamAdapter{
    79  //			BidiStream: grpcshim.NewBidiStream(ctx),
    80  //			ch:         make(chan *vtctldatapb.BackupResponse, 1),
    81  //		}
    82  //		go func() {
    83  //			err := client.s.Backup(in, stream)
    84  //			stream.CloseWithError(err)
    85  //		}()
    86  //		return stream, nil
    87  //	}
    88  type BidiStream struct {
    89  	// ErrCh receives errors mid-stream, and should be selected on with the
    90  	// same priority as stream.ch and stream/send contexts' cancellations in
    91  	// Recv methods.
    92  	ErrCh chan error
    93  
    94  	ctx              context.Context
    95  	sendClosedCtx    context.Context
    96  	sendClosedCancel context.CancelFunc
    97  
    98  	m        sync.Mutex
    99  	closeErr error
   100  }
   101  
   102  // NewBidiStream returns a BidiStream ready for embedded use. The provided ctx
   103  // will be used for the stream context, and types embedding BidiStream should
   104  // check context cancellation/expiriation in their respective Recv and Send
   105  // methods.
   106  //
   107  // See the documentation on BidiStream for example usage.
   108  func NewBidiStream(ctx context.Context) *BidiStream {
   109  	sendClosedCtx, sendClosedCancel := context.WithCancel(context.Background())
   110  	return &BidiStream{
   111  		ErrCh:            make(chan error, 1),
   112  		ctx:              ctx,
   113  		sendClosedCtx:    sendClosedCtx,
   114  		sendClosedCancel: sendClosedCancel,
   115  	}
   116  }
   117  
   118  // Closed returns a channel which will be itself be closed when the stream has
   119  // been closed for sending.
   120  func (bs *BidiStream) Closed() <-chan struct{} {
   121  	return bs.sendClosedCtx.Done()
   122  }
   123  
   124  // IsClosed returns true if the stream has been closed for sending.
   125  //
   126  // It is a conveince function for attempting to select on the channel returned
   127  // by bs.Closed().
   128  func (bs *BidiStream) IsClosed() bool {
   129  	select {
   130  	case <-bs.Closed():
   131  		return true
   132  	default:
   133  		return false
   134  	}
   135  }
   136  
   137  // CloseWithError closes the stream for future sends, and sets the error
   138  // returned by bs.CloseErr(). If the passed err is nil, io.EOF is set instead.
   139  // If the stream is already closed, this is a no-op.
   140  func (bs *BidiStream) CloseWithError(err error) {
   141  	bs.m.Lock()
   142  	defer bs.m.Unlock()
   143  
   144  	if bs.IsClosed() {
   145  		return
   146  	}
   147  
   148  	if err == nil {
   149  		err = io.EOF
   150  	}
   151  
   152  	bs.closeErr = err
   153  	bs.sendClosedCancel()
   154  }
   155  
   156  // CloseErr returns the error set by CloseWithError, or nil if the stream is
   157  // not closed.
   158  func (bs *BidiStream) CloseErr() error {
   159  	bs.m.Lock()
   160  	defer bs.m.Unlock()
   161  	return bs.closeErr
   162  }
   163  
   164  var (
   165  	_ grpc.ClientStream = (*BidiStream)(nil)
   166  	_ grpc.ServerStream = (*BidiStream)(nil)
   167  )
   168  
   169  // client and server methods
   170  
   171  func (bs *BidiStream) Context() context.Context { return bs.ctx }
   172  func (bs *BidiStream) RecvMsg(m any) error      { return nil }
   173  func (bs *BidiStream) SendMsg(m any) error      { return nil }
   174  
   175  // client methods
   176  
   177  func (bs *BidiStream) Header() (metadata.MD, error) { return nil, nil }
   178  func (bs *BidiStream) Trailer() metadata.MD         { return nil }
   179  func (bs *BidiStream) CloseSend() error             { return nil }
   180  
   181  // server methods
   182  
   183  func (bs *BidiStream) SendHeader(md metadata.MD) error { return nil }
   184  func (bs *BidiStream) SetHeader(md metadata.MD) error  { return nil }
   185  func (bs *BidiStream) SetTrailer(md metadata.MD)       {}