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