github.com/geraldss/go/src@v0.0.0-20210511222824-ac7d0ebfc235/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 "internal/syscall/unix" 9 "sync/atomic" 10 "syscall" 11 "unsafe" 12 ) 13 14 const ( 15 // spliceNonblock makes calls to splice(2) non-blocking. 16 spliceNonblock = 0x2 17 18 // maxSpliceSize is the maximum amount of data Splice asks 19 // the kernel to move in a single call to splice(2). 20 maxSpliceSize = 4 << 20 21 ) 22 23 // Splice transfers at most remain bytes of data from src to dst, using the 24 // splice system call to minimize copies of data from and to userspace. 25 // 26 // Splice creates a temporary pipe, to serve as a buffer for the data transfer. 27 // src and dst must both be stream-oriented sockets. 28 // 29 // If err != nil, sc is the system call which caused the error. 30 func Splice(dst, src *FD, remain int64) (written int64, handled bool, sc string, err error) { 31 prfd, pwfd, sc, err := newTempPipe() 32 if err != nil { 33 return 0, false, sc, err 34 } 35 defer destroyTempPipe(prfd, pwfd) 36 var inPipe, n int 37 for err == nil && remain > 0 { 38 max := maxSpliceSize 39 if int64(max) > remain { 40 max = int(remain) 41 } 42 inPipe, err = spliceDrain(pwfd, src, max) 43 // The operation is considered handled if splice returns no 44 // error, or an error other than EINVAL. An EINVAL means the 45 // kernel does not support splice for the socket type of src. 46 // The failed syscall does not consume any data so it is safe 47 // to fall back to a generic copy. 48 // 49 // spliceDrain should never return EAGAIN, so if err != nil, 50 // Splice cannot continue. 51 // 52 // If inPipe == 0 && err == nil, src is at EOF, and the 53 // transfer is complete. 54 handled = handled || (err != syscall.EINVAL) 55 if err != nil || (inPipe == 0 && err == nil) { 56 break 57 } 58 n, err = splicePump(dst, prfd, inPipe) 59 if n > 0 { 60 written += int64(n) 61 remain -= int64(n) 62 } 63 } 64 if err != nil { 65 return written, handled, "splice", err 66 } 67 return written, true, "", nil 68 } 69 70 // spliceDrain moves data from a socket to a pipe. 71 // 72 // Invariant: when entering spliceDrain, the pipe is empty. It is either in its 73 // initial state, or splicePump has emptied it previously. 74 // 75 // Given this, spliceDrain can reasonably assume that the pipe is ready for 76 // writing, so if splice returns EAGAIN, it must be because the socket is not 77 // ready for reading. 78 // 79 // If spliceDrain returns (0, nil), src is at EOF. 80 func spliceDrain(pipefd int, sock *FD, max int) (int, error) { 81 if err := sock.readLock(); err != nil { 82 return 0, err 83 } 84 defer sock.readUnlock() 85 if err := sock.pd.prepareRead(sock.isFile); err != nil { 86 return 0, err 87 } 88 for { 89 n, err := splice(pipefd, sock.Sysfd, max, spliceNonblock) 90 if err == syscall.EINTR { 91 continue 92 } 93 if err != syscall.EAGAIN { 94 return n, err 95 } 96 if err := sock.pd.waitRead(sock.isFile); err != nil { 97 return n, err 98 } 99 } 100 } 101 102 // splicePump moves all the buffered data from a pipe to a socket. 103 // 104 // Invariant: when entering splicePump, there are exactly inPipe 105 // bytes of data in the pipe, from a previous call to spliceDrain. 106 // 107 // By analogy to the condition from spliceDrain, splicePump 108 // only needs to poll the socket for readiness, if splice returns 109 // EAGAIN. 110 // 111 // If splicePump cannot move all the data in a single call to 112 // splice(2), it loops over the buffered data until it has written 113 // all of it to the socket. This behavior is similar to the Write 114 // step of an io.Copy in userspace. 115 func splicePump(sock *FD, pipefd int, inPipe int) (int, error) { 116 if err := sock.writeLock(); err != nil { 117 return 0, err 118 } 119 defer sock.writeUnlock() 120 if err := sock.pd.prepareWrite(sock.isFile); err != nil { 121 return 0, err 122 } 123 written := 0 124 for inPipe > 0 { 125 n, err := splice(sock.Sysfd, pipefd, inPipe, spliceNonblock) 126 // Here, the condition n == 0 && err == nil should never be 127 // observed, since Splice controls the write side of the pipe. 128 if n > 0 { 129 inPipe -= n 130 written += n 131 continue 132 } 133 if err != syscall.EAGAIN { 134 return written, err 135 } 136 if err := sock.pd.waitWrite(sock.isFile); err != nil { 137 return written, err 138 } 139 } 140 return written, nil 141 } 142 143 // splice wraps the splice system call. Since the current implementation 144 // only uses splice on sockets and pipes, the offset arguments are unused. 145 // splice returns int instead of int64, because callers never ask it to 146 // move more data in a single call than can fit in an int32. 147 func splice(out int, in int, max int, flags int) (int, error) { 148 n, err := syscall.Splice(in, nil, out, nil, max, flags) 149 return int(n), err 150 } 151 152 var disableSplice unsafe.Pointer 153 154 // newTempPipe sets up a temporary pipe for a splice operation. 155 func newTempPipe() (prfd, pwfd int, sc string, err error) { 156 p := (*bool)(atomic.LoadPointer(&disableSplice)) 157 if p != nil && *p { 158 return -1, -1, "splice", syscall.EINVAL 159 } 160 161 var fds [2]int 162 // pipe2 was added in 2.6.27 and our minimum requirement is 2.6.23, so it 163 // might not be implemented. Falling back to pipe is possible, but prior to 164 // 2.6.29 splice returns -EAGAIN instead of 0 when the connection is 165 // closed. 166 const flags = syscall.O_CLOEXEC | syscall.O_NONBLOCK 167 if err := syscall.Pipe2(fds[:], flags); err != nil { 168 return -1, -1, "pipe2", err 169 } 170 171 if p == nil { 172 p = new(bool) 173 defer atomic.StorePointer(&disableSplice, unsafe.Pointer(p)) 174 175 // F_GETPIPE_SZ was added in 2.6.35, which does not have the -EAGAIN bug. 176 if _, _, errno := syscall.Syscall(unix.FcntlSyscall, uintptr(fds[0]), syscall.F_GETPIPE_SZ, 0); errno != 0 { 177 *p = true 178 destroyTempPipe(fds[0], fds[1]) 179 return -1, -1, "fcntl", errno 180 } 181 } 182 183 return fds[0], fds[1], "", nil 184 } 185 186 // destroyTempPipe destroys a temporary pipe. 187 func destroyTempPipe(prfd, pwfd int) error { 188 err := CloseFunc(prfd) 189 err1 := CloseFunc(pwfd) 190 if err == nil { 191 return err1 192 } 193 return err 194 }