gitee.com/ks-custle/core-gm@v0.0.0-20230922171213-b83bdd97b62c/internal/poll/splice_linux.go (about)

     1  // Copyright 2018 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package poll
     6  
     7  import (
     8  	"runtime"
     9  	"sync"
    10  	"sync/atomic"
    11  	"syscall"
    12  	"unsafe"
    13  
    14  	"gitee.com/ks-custle/core-gm/internal/syscall/unix"
    15  )
    16  
    17  const (
    18  	// spliceNonblock makes calls to splice(2) non-blocking.
    19  	spliceNonblock = 0x2
    20  
    21  	// maxSpliceSize is the maximum amount of data Splice asks
    22  	// the kernel to move in a single call to splice(2).
    23  	maxSpliceSize = 4 << 20
    24  )
    25  
    26  // Splice transfers at most remain bytes of data from src to dst, using the
    27  // splice system call to minimize copies of data from and to userspace.
    28  //
    29  // Splice gets a pipe buffer from the pool or creates a new one if needed, to serve as a buffer for the data transfer.
    30  // src and dst must both be stream-oriented sockets.
    31  //
    32  // If err != nil, sc is the system call which caused the error.
    33  func Splice(dst, src *FD, remain int64) (written int64, handled bool, sc string, err error) {
    34  	p, sc, err := getPipe()
    35  	if err != nil {
    36  		return 0, false, sc, err
    37  	}
    38  	defer putPipe(p)
    39  	var inPipe, n int
    40  	for err == nil && remain > 0 {
    41  		max := maxSpliceSize
    42  		if int64(max) > remain {
    43  			max = int(remain)
    44  		}
    45  		inPipe, err = spliceDrain(p.wfd, src, max)
    46  		// The operation is considered handled if splice returns no
    47  		// error, or an error other than EINVAL. An EINVAL means the
    48  		// kernel does not support splice for the socket type of src.
    49  		// The failed syscall does not consume any data so it is safe
    50  		// to fall back to a generic copy.
    51  		//
    52  		// spliceDrain should never return EAGAIN, so if err != nil,
    53  		// Splice cannot continue.
    54  		//
    55  		// If inPipe == 0 && err == nil, src is at EOF, and the
    56  		// transfer is complete.
    57  		handled = handled || (err != syscall.EINVAL)
    58  		if err != nil || inPipe == 0 {
    59  			break
    60  		}
    61  		p.data += inPipe
    62  
    63  		n, err = splicePump(dst, p.rfd, inPipe)
    64  		if n > 0 {
    65  			written += int64(n)
    66  			remain -= int64(n)
    67  			p.data -= n
    68  		}
    69  	}
    70  	if err != nil {
    71  		return written, handled, "splice", err
    72  	}
    73  	return written, true, "", nil
    74  }
    75  
    76  // spliceDrain moves data from a socket to a pipe.
    77  //
    78  // Invariant: when entering spliceDrain, the pipe is empty. It is either in its
    79  // initial state, or splicePump has emptied it previously.
    80  //
    81  // Given this, spliceDrain can reasonably assume that the pipe is ready for
    82  // writing, so if splice returns EAGAIN, it must be because the socket is not
    83  // ready for reading.
    84  //
    85  // If spliceDrain returns (0, nil), src is at EOF.
    86  func spliceDrain(pipefd int, sock *FD, max int) (int, error) {
    87  	if err := sock.readLock(); err != nil {
    88  		return 0, err
    89  	}
    90  	defer sock.readUnlock()
    91  	if err := sock.pd.prepareRead(sock.isFile); err != nil {
    92  		return 0, err
    93  	}
    94  	for {
    95  		n, err := splice(pipefd, sock.Sysfd, max, spliceNonblock)
    96  		if err == syscall.EINTR {
    97  			continue
    98  		}
    99  		if err != syscall.EAGAIN {
   100  			return n, err
   101  		}
   102  		if err := sock.pd.waitRead(sock.isFile); err != nil {
   103  			return n, err
   104  		}
   105  	}
   106  }
   107  
   108  // splicePump moves all the buffered data from a pipe to a socket.
   109  //
   110  // Invariant: when entering splicePump, there are exactly inPipe
   111  // bytes of data in the pipe, from a previous call to spliceDrain.
   112  //
   113  // By analogy to the condition from spliceDrain, splicePump
   114  // only needs to poll the socket for readiness, if splice returns
   115  // EAGAIN.
   116  //
   117  // If splicePump cannot move all the data in a single call to
   118  // splice(2), it loops over the buffered data until it has written
   119  // all of it to the socket. This behavior is similar to the Write
   120  // step of an io.Copy in userspace.
   121  func splicePump(sock *FD, pipefd int, inPipe int) (int, error) {
   122  	if err := sock.writeLock(); err != nil {
   123  		return 0, err
   124  	}
   125  	defer sock.writeUnlock()
   126  	if err := sock.pd.prepareWrite(sock.isFile); err != nil {
   127  		return 0, err
   128  	}
   129  	written := 0
   130  	for inPipe > 0 {
   131  		n, err := splice(sock.Sysfd, pipefd, inPipe, spliceNonblock)
   132  		// Here, the condition n == 0 && err == nil should never be
   133  		// observed, since Splice controls the write side of the pipe.
   134  		if n > 0 {
   135  			inPipe -= n
   136  			written += n
   137  			continue
   138  		}
   139  		if err != syscall.EAGAIN {
   140  			return written, err
   141  		}
   142  		if err := sock.pd.waitWrite(sock.isFile); err != nil {
   143  			return written, err
   144  		}
   145  	}
   146  	return written, nil
   147  }
   148  
   149  // splice wraps the splice system call. Since the current implementation
   150  // only uses splice on sockets and pipes, the offset arguments are unused.
   151  // splice returns int instead of int64, because callers never ask it to
   152  // move more data in a single call than can fit in an int32.
   153  func splice(out int, in int, max int, flags int) (int, error) {
   154  	n, err := syscall.Splice(in, nil, out, nil, max, flags)
   155  	return int(n), err
   156  }
   157  
   158  type splicePipe struct {
   159  	rfd  int
   160  	wfd  int
   161  	data int
   162  }
   163  
   164  // splicePipePool caches pipes to avoid high-frequency construction and destruction of pipe buffers.
   165  // The garbage collector will free all pipes in the sync.Pool periodically, thus we need to set up
   166  // a finalizer for each pipe to close its file descriptors before the actual GC.
   167  var splicePipePool = sync.Pool{New: newPoolPipe}
   168  
   169  func newPoolPipe() interface{} {
   170  	// Discard the error which occurred during the creation of pipe buffer,
   171  	// redirecting the data transmission to the conventional way utilizing read() + write() as a fallback.
   172  	p := newPipe()
   173  	if p == nil {
   174  		return nil
   175  	}
   176  	runtime.SetFinalizer(p, destroyPipe)
   177  	return p
   178  }
   179  
   180  // getPipe tries to acquire a pipe buffer from the pool or create a new one with newPipe() if it gets nil from the cache.
   181  //
   182  // Note that it may fail to create a new pipe buffer by newPipe(), in which case getPipe() will return a generic error
   183  // and system call name splice in a string as the indication.
   184  func getPipe() (*splicePipe, string, error) {
   185  	v := splicePipePool.Get()
   186  	if v == nil {
   187  		return nil, "splice", syscall.EINVAL
   188  	}
   189  	return v.(*splicePipe), "", nil
   190  }
   191  
   192  func putPipe(p *splicePipe) {
   193  	// If there is still data left in the pipe,
   194  	// then close and discard it instead of putting it back into the pool.
   195  	if p.data != 0 {
   196  		runtime.SetFinalizer(p, nil)
   197  		destroyPipe(p)
   198  		return
   199  	}
   200  	splicePipePool.Put(p)
   201  }
   202  
   203  var disableSplice unsafe.Pointer
   204  
   205  // newPipe sets up a pipe for a splice operation.
   206  func newPipe() (sp *splicePipe) {
   207  	p := (*bool)(atomic.LoadPointer(&disableSplice))
   208  	if p != nil && *p {
   209  		return nil
   210  	}
   211  
   212  	var fds [2]int
   213  	// pipe2 was added in 2.6.27 and our minimum requirement is 2.6.23, so it
   214  	// might not be implemented. Falling back to pipe is possible, but prior to
   215  	// 2.6.29 splice returns -EAGAIN instead of 0 when the connection is
   216  	// closed.
   217  	const flags = syscall.O_CLOEXEC | syscall.O_NONBLOCK
   218  	if err := syscall.Pipe2(fds[:], flags); err != nil {
   219  		return nil
   220  	}
   221  
   222  	sp = &splicePipe{rfd: fds[0], wfd: fds[1]}
   223  
   224  	if p == nil {
   225  		p = new(bool)
   226  		defer atomic.StorePointer(&disableSplice, unsafe.Pointer(p))
   227  
   228  		// F_GETPIPE_SZ was added in 2.6.35, which does not have the -EAGAIN bug.
   229  		if _, _, errno := syscall.Syscall(unix.FcntlSyscall, uintptr(fds[0]), syscall.F_GETPIPE_SZ, 0); errno != 0 {
   230  			*p = true
   231  			destroyPipe(sp)
   232  			return nil
   233  		}
   234  	}
   235  
   236  	return
   237  }
   238  
   239  // destroyPipe destroys a pipe.
   240  func destroyPipe(p *splicePipe) {
   241  	CloseFunc(p.rfd)
   242  	CloseFunc(p.wfd)
   243  }