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) {}