github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/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 "github.com/hxx258456/ccgo/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 }