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  }