trpc.group/trpc-go/trpc-go@v1.0.3/internal/writev/buffer.go (about) 1 // 2 // 3 // Tencent is pleased to support the open source community by making tRPC available. 4 // 5 // Copyright (C) 2023 THL A29 Limited, a Tencent company. 6 // All rights reserved. 7 // 8 // If you have downloaded a copy of the tRPC source code from Tencent, 9 // please note that tRPC source code is licensed under the Apache 2.0 License, 10 // A copy of the Apache 2.0 License is included in this file. 11 // 12 // 13 14 // Package writev provides Buffer and uses the writev() system call to send packages. 15 package writev 16 17 import ( 18 "errors" 19 "io" 20 "net" 21 "runtime" 22 23 "trpc.group/trpc-go/trpc-go/internal/ring" 24 ) 25 26 const ( 27 // default buffer queue length. 28 defaultBufferSize = 128 29 // The maximum number of data packets that can be sent by writev (from Go source code definition). 30 maxWritevBuffers = 1024 31 ) 32 33 var ( 34 // ErrAskQuit sends a close request externally. 35 ErrAskQuit = errors.New("writev goroutine is asked to quit") 36 // ErrStopped Buffer stops receiving data. 37 ErrStopped = errors.New("writev buffer stop to receive data") 38 ) 39 40 // QuitHandler Buffer goroutine exit handler. 41 type QuitHandler func(*Buffer) 42 43 // Buffer records the messages to be sent and sends them in batches using goroutines. 44 type Buffer struct { 45 opts *Options // configuration items. 46 w io.Writer // The underlying io.Writer that sends data. 47 queue *ring.Ring[[]byte] // queue for buffered messages. 48 wakeupCh chan struct{} // used to wake up the sending goroutine. 49 done chan struct{} // notify the sending goroutine to exit. 50 err error // record error message. 51 errCh chan error // internal error notification. 52 isQueueStopped bool // whether the cache queue stops receiving packets. 53 } 54 55 var defaultQuitHandler = func(b *Buffer) { 56 b.SetQueueStopped(true) 57 } 58 59 // NewBuffer creates a Buffer and starts the sending goroutine. 60 func NewBuffer(opt ...Option) *Buffer { 61 opts := &Options{ 62 bufferSize: defaultBufferSize, 63 handler: defaultQuitHandler, 64 } 65 for _, o := range opt { 66 o(opts) 67 } 68 69 b := &Buffer{ 70 queue: ring.New[[]byte](uint32(opts.bufferSize)), 71 opts: opts, 72 wakeupCh: make(chan struct{}, 1), 73 errCh: make(chan error, 1), 74 } 75 return b 76 } 77 78 // Start starts the sending goroutine, you need to set writer and done at startup. 79 func (b *Buffer) Start(writer io.Writer, done chan struct{}) { 80 b.w = writer 81 b.done = done 82 go b.start() 83 } 84 85 // Restart recreates a Buffer when restarting, reusing the buffer queue and configuration of the original Buffer. 86 func (b *Buffer) Restart(writer io.Writer, done chan struct{}) *Buffer { 87 buffer := &Buffer{ 88 queue: b.queue, 89 opts: b.opts, 90 wakeupCh: make(chan struct{}, 1), 91 errCh: make(chan error, 1), 92 } 93 buffer.Start(writer, done) 94 return buffer 95 } 96 97 // SetQueueStopped sets whether the buffer queue stops receiving packets. 98 func (b *Buffer) SetQueueStopped(stopped bool) { 99 b.isQueueStopped = stopped 100 if b.err == nil { 101 b.err = ErrStopped 102 } 103 } 104 105 // Write writes p to the buffer queue and returns the length of the data written. 106 // How to write a packet smaller than len(p), err returns the specific reason. 107 func (b *Buffer) Write(p []byte) (int, error) { 108 if b.opts.dropFull { 109 return b.writeNoWait(p) 110 } 111 return b.writeOrWait(p) 112 } 113 114 // Error returns the reason why the sending goroutine exited. 115 func (b *Buffer) Error() error { 116 return b.err 117 } 118 119 // Done return to exit the Channel. 120 func (b *Buffer) Done() chan struct{} { 121 return b.done 122 } 123 124 func (b *Buffer) wakeUp() { 125 // Based on performance optimization considerations: due to the poor 126 // efficiency of concurrent select write channel, check here first 127 // whether wakeupCh already has a wakeup message, reducing the chance 128 // of concurrently writing to the channel. 129 if len(b.wakeupCh) > 0 { 130 return 131 } 132 // try to send wakeup signal, don't wait. 133 select { 134 case b.wakeupCh <- struct{}{}: 135 default: 136 } 137 } 138 139 func (b *Buffer) writeNoWait(p []byte) (int, error) { 140 // The buffer queue stops receiving packets and returns directly. 141 if b.isQueueStopped { 142 return 0, b.err 143 } 144 // return directly when the queue is full. 145 if err := b.queue.Put(p); err != nil { 146 return 0, err 147 } 148 // Write the buffer queue successfully, wake up the sending goroutine. 149 b.wakeUp() 150 return len(p), nil 151 } 152 153 func (b *Buffer) writeOrWait(p []byte) (int, error) { 154 for { 155 // The buffer queue stops receiving packets and returns directly. 156 if b.isQueueStopped { 157 return 0, b.err 158 } 159 // Write the buffer queue successfully, wake up the sending goroutine. 160 if err := b.queue.Put(p); err == nil { 161 b.wakeUp() 162 return len(p), nil 163 } 164 // The queue is full, send the package directly. 165 if err := b.writeDirectly(); err != nil { 166 return 0, err 167 } 168 } 169 } 170 171 func (b *Buffer) writeDirectly() error { 172 if b.queue.IsEmpty() { 173 return nil 174 } 175 vals := make([][]byte, 0, maxWritevBuffers) 176 size, _ := b.queue.Gets(&vals) 177 if size == 0 { 178 return nil 179 } 180 bufs := make(net.Buffers, 0, maxWritevBuffers) 181 for _, v := range vals { 182 bufs = append(bufs, v) 183 } 184 if _, err := bufs.WriteTo(b.w); err != nil { 185 // Notify the sending goroutine setting error and exit. 186 select { 187 case b.errCh <- err: 188 default: 189 } 190 return err 191 } 192 return nil 193 } 194 195 func (b *Buffer) getOrWait(values *[][]byte) error { 196 for { 197 // Check whether to be notified to close the outgoing goroutine. 198 select { 199 case <-b.done: 200 return ErrAskQuit 201 case err := <-b.errCh: 202 return err 203 default: 204 } 205 // Bulk receive packets from the cache queue. 206 size, _ := b.queue.Gets(values) 207 if size > 0 { 208 return nil 209 } 210 211 // Fast Path: Due to the poor performance of using select 212 // to wake up the goroutine, it is preferred here to use Gosched() 213 // to delay checking the queue, improving the hit rate and 214 // the efficiency of obtaining packets in batches, thereby reducing 215 // the probability of using select to wake up the goroutine. 216 runtime.Gosched() 217 if !b.queue.IsEmpty() { 218 continue 219 } 220 // Slow Path: There are still no packets after the delayed check queue, 221 // indicating that the system is relatively idle. goroutine uses 222 // the select mechanism to wait for wakeup. The advantage of hibernation 223 // is to reduce CPU idling loss when the system is idle. 224 select { 225 case <-b.done: 226 return ErrAskQuit 227 case err := <-b.errCh: 228 return err 229 case <-b.wakeupCh: 230 } 231 } 232 } 233 234 func (b *Buffer) start() { 235 initBufs := make(net.Buffers, 0, maxWritevBuffers) 236 vals := make([][]byte, 0, maxWritevBuffers) 237 bufs := initBufs 238 239 defer b.opts.handler(b) 240 for { 241 if err := b.getOrWait(&vals); err != nil { 242 b.err = err 243 break 244 } 245 246 for _, v := range vals { 247 bufs = append(bufs, v) 248 } 249 vals = vals[:0] 250 251 if _, err := bufs.WriteTo(b.w); err != nil { 252 b.err = err 253 break 254 } 255 // Reset bufs to the initial position to prevent `append` from generating new memory allocations. 256 bufs = initBufs 257 } 258 }