github.com/x04/go/src@v0.0.0-20200202162449-3d481ceb3525/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 "github.com/x04/go/src/internal/syscall/unix" 9 "github.com/x04/go/src/sync/atomic" 10 "github.com/x04/go/src/syscall" 11 "github.com/x04/go/src/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.EAGAIN { 91 return n, err 92 } 93 if err := sock.pd.waitRead(sock.isFile); err != nil { 94 return n, err 95 } 96 } 97 } 98 99 // splicePump moves all the buffered data from a pipe to a socket. 100 // 101 // Invariant: when entering splicePump, there are exactly inPipe 102 // bytes of data in the pipe, from a previous call to spliceDrain. 103 // 104 // By analogy to the condition from spliceDrain, splicePump 105 // only needs to poll the socket for readiness, if splice returns 106 // EAGAIN. 107 // 108 // If splicePump cannot move all the data in a single call to 109 // splice(2), it loops over the buffered data until it has written 110 // all of it to the socket. This behavior is similar to the Write 111 // step of an io.Copy in userspace. 112 func splicePump(sock *FD, pipefd int, inPipe int) (int, error) { 113 if err := sock.writeLock(); err != nil { 114 return 0, err 115 } 116 defer sock.writeUnlock() 117 if err := sock.pd.prepareWrite(sock.isFile); err != nil { 118 return 0, err 119 } 120 written := 0 121 for inPipe > 0 { 122 n, err := splice(sock.Sysfd, pipefd, inPipe, spliceNonblock) 123 // Here, the condition n == 0 && err == nil should never be 124 // observed, since Splice controls the write side of the pipe. 125 if n > 0 { 126 inPipe -= n 127 written += n 128 continue 129 } 130 if err != syscall.EAGAIN { 131 return written, err 132 } 133 if err := sock.pd.waitWrite(sock.isFile); err != nil { 134 return written, err 135 } 136 } 137 return written, nil 138 } 139 140 // splice wraps the splice system call. Since the current implementation 141 // only uses splice on sockets and pipes, the offset arguments are unused. 142 // splice returns int instead of int64, because callers never ask it to 143 // move more data in a single call than can fit in an int32. 144 func splice(out int, in int, max int, flags int) (int, error) { 145 n, err := syscall.Splice(in, nil, out, nil, max, flags) 146 return int(n), err 147 } 148 149 var disableSplice unsafe.Pointer 150 151 // newTempPipe sets up a temporary pipe for a splice operation. 152 func newTempPipe() (prfd, pwfd int, sc string, err error) { 153 p := (*bool)(atomic.LoadPointer(&disableSplice)) 154 if p != nil && *p { 155 return -1, -1, "splice", syscall.EINVAL 156 } 157 158 var fds [2]int 159 // pipe2 was added in 2.6.27 and our minimum requirement is 2.6.23, so it 160 // might not be implemented. Falling back to pipe is possible, but prior to 161 // 2.6.29 splice returns -EAGAIN instead of 0 when the connection is 162 // closed. 163 const flags = syscall.O_CLOEXEC | syscall.O_NONBLOCK 164 if err := syscall.Pipe2(fds[:], flags); err != nil { 165 return -1, -1, "pipe2", err 166 } 167 168 if p == nil { 169 p = new(bool) 170 defer atomic.StorePointer(&disableSplice, unsafe.Pointer(p)) 171 172 // F_GETPIPE_SZ was added in 2.6.35, which does not have the -EAGAIN bug. 173 if _, _, errno := syscall.Syscall(unix.FcntlSyscall, uintptr(fds[0]), syscall.F_GETPIPE_SZ, 0); errno != 0 { 174 *p = true 175 destroyTempPipe(fds[0], fds[1]) 176 return -1, -1, "fcntl", errno 177 } 178 } 179 180 return fds[0], fds[1], "", nil 181 } 182 183 // destroyTempPipe destroys a temporary pipe. 184 func destroyTempPipe(prfd, pwfd int) error { 185 err := CloseFunc(prfd) 186 err1 := CloseFunc(pwfd) 187 if err == nil { 188 return err1 189 } 190 return err 191 }