github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/p2p/server_stream_handle.go (about) 1 // Copyright 2021 PingCAP, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package p2p 15 16 import ( 17 "context" 18 "sync" 19 20 "github.com/pingcap/tiflow/pkg/errors" 21 "github.com/pingcap/tiflow/proto/p2p" 22 "go.uber.org/atomic" 23 ) 24 25 // streamHandle is used to provide the server a non-blocking handle of a stream. 26 // It is needed because grpc-go does not provide a stream easily cancellable. 27 // Since Go is not friendly with the receiver closing a channel, we need this 28 // auxiliary data structure to make the stream cancellation more graceful. 29 type streamHandle struct { 30 // mu protects sendCh. We should take the shared lock 31 // when sending and the exclusive lock when closing it. 32 // We consider Read to be anything except closing the channel, 33 // and close is Write in this definition. 34 // This definition provides an elegant solution for lock protecting Go channels. 35 mu sync.RWMutex 36 sendCh chan<- p2p.SendMessageResponse 37 38 isClosed atomic.Bool 39 closeCh chan struct{} 40 41 // read-only 42 streamMeta *p2p.StreamMeta 43 } 44 45 // newStreamHandle returns a new streamHandle. 46 func newStreamHandle(meta *p2p.StreamMeta, sendCh chan<- p2p.SendMessageResponse) *streamHandle { 47 return &streamHandle{ 48 sendCh: sendCh, 49 closeCh: make(chan struct{}), 50 streamMeta: meta, 51 } 52 } 53 54 // Send sends a message to the stream. 55 // If called after Close, an error will be returned. 56 func (s *streamHandle) Send(ctx context.Context, response p2p.SendMessageResponse) error { 57 s.mu.RLock() 58 defer s.mu.RUnlock() 59 60 // We need to read from isClosed while holding the lock. 61 // Otherwise, we risk the sendCh being closed after we have checked 62 // isClosed to be false. If this happens, we will try to send 63 // to a closed channel, which will panic. 64 if s.isClosed.Load() { 65 return errors.ErrPeerMessageInternalSenderClosed.GenWithStackByArgs() 66 } 67 68 select { 69 case <-ctx.Done(): 70 return errors.Trace(ctx.Err()) 71 case s.sendCh <- response: 72 case <-s.closeCh: 73 return errors.ErrPeerMessageInternalSenderClosed.GenWithStackByArgs() 74 } 75 76 return nil 77 } 78 79 // Close closes the stream handle. 80 // We should not call Send after Close. 81 func (s *streamHandle) Close() { 82 if s.isClosed.Swap(true) { 83 // already closed 84 return 85 } 86 // Must close `s.closeCh` while not holding `s.mu`. 87 close(s.closeCh) 88 89 s.mu.Lock() 90 defer s.mu.Unlock() 91 92 close(s.sendCh) 93 } 94 95 // GetStreamMeta returns the metadata associated with the stream. 96 func (s *streamHandle) GetStreamMeta() *p2p.StreamMeta { 97 return s.streamMeta 98 }