|
| 1 | +// Copyright 2022 CloudWeGo Authors |
| 2 | +// |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +// you may not use this file except in compliance with the License. |
| 5 | +// You may obtain a copy of the License at |
| 6 | +// |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +// |
| 9 | +// Unless required by applicable law or agreed to in writing, software |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +// See the License for the specific language governing permissions and |
| 13 | +// limitations under the License. |
| 14 | + |
| 15 | +//go:build linux |
| 16 | + |
| 17 | +package netpoll |
| 18 | + |
| 19 | +import ( |
| 20 | + "runtime" |
| 21 | + "sync/atomic" |
| 22 | + "syscall" |
| 23 | + "time" |
| 24 | +) |
| 25 | + |
| 26 | +// IsHealthyForReuse reports whether a dialed TCP connection has no pending |
| 27 | +// inbound data and has not been closed by its peer. ownerWaitTimeout bounds |
| 28 | +// only the wait to acquire the receive owner's FDOperator; the socket probe |
| 29 | +// itself is non-blocking. |
| 30 | +// |
| 31 | +// The check is synchronized with the netpoll receive owner. In particular, it |
| 32 | +// keeps the FDOperator ownership across both the LinkBuffer check and the |
| 33 | +// non-consuming socket probe, so a poller cannot move bytes from the kernel |
| 34 | +// receive queue into the LinkBuffer between those observations. |
| 35 | +// |
| 36 | +// It is intended for an idle client connection that is exclusively owned by a |
| 37 | +// connection pool. It does not reserve the connection for a later write and |
| 38 | +// therefore cannot prevent a peer from closing it after this method returns. |
| 39 | +func (c *TCPConnection) IsHealthyForReuse(ownerWaitTimeout time.Duration) bool { |
| 40 | + // Reject invalid, already-closing, or concurrently flushing connections |
| 41 | + // before touching their receive operator. A non-positive budget cannot |
| 42 | + // safely establish ownership and therefore fails closed. |
| 43 | + if c == nil || ownerWaitTimeout <= 0 || !c.IsActive() || !c.lock(flushing) { |
| 44 | + return false |
| 45 | + } |
| 46 | + // Keep close and flush transitions serialized for the entire observation. |
| 47 | + defer c.unlock(flushing) |
| 48 | + |
| 49 | + // Close stops the flushing lock before it can free and recycle the operator. |
| 50 | + // Recheck after acquiring it so a concurrent close cannot hand us a reused |
| 51 | + // operator. |
| 52 | + if !c.IsActive() || c.operator == nil { |
| 53 | + return false |
| 54 | + } |
| 55 | + |
| 56 | + // Try the uncontended owner transition first so the healthy idle fast path |
| 57 | + // avoids both a clock read and a scheduler yield. |
| 58 | + op := c.operator |
| 59 | + if !op.do() { |
| 60 | + // A poller currently owns the operator. Wait only within the caller's |
| 61 | + // budget, while continuously rejecting lifecycle transitions that make |
| 62 | + // this operator ineligible for reuse. |
| 63 | + deadline := time.Now().Add(ownerWaitTimeout) |
| 64 | + for { |
| 65 | + // Stop waiting as soon as the connection closes or the operator starts |
| 66 | + // its detach/recycle lifecycle. |
| 67 | + if !c.IsActive() || op.isUnused() || atomic.LoadInt32(&op.detached) != 0 { |
| 68 | + return false |
| 69 | + } |
| 70 | + // Check the absolute deadline before each ownership attempt so repeated |
| 71 | + // contention cannot extend the caller-provided budget. |
| 72 | + if !time.Now().Before(deadline) { |
| 73 | + return false |
| 74 | + } |
| 75 | + if op.do() { |
| 76 | + // Ownership may be acquired exactly as the deadline expires. Release |
| 77 | + // it explicitly before failing so the poller is never left blocked. |
| 78 | + if !time.Now().Before(deadline) { |
| 79 | + op.done() |
| 80 | + return false |
| 81 | + } |
| 82 | + break |
| 83 | + } |
| 84 | + // Yield instead of busy-spinning while the poller finishes its current |
| 85 | + // read/InputAck critical section. |
| 86 | + runtime.Gosched() |
| 87 | + } |
| 88 | + } |
| 89 | + // Hold receive ownership through both buffer inspection and socket probing; |
| 90 | + // every return below must hand it back to the poller. |
| 91 | + defer op.done() |
| 92 | + |
| 93 | + // appendHup detaches the operator and calls OnHup asynchronously. A detached |
| 94 | + // operator is no longer eligible even before OnHup publishes closing state. |
| 95 | + if atomic.LoadInt32(&op.detached) != 0 { |
| 96 | + return false |
| 97 | + } |
| 98 | + |
| 99 | + // Buffered application data means the connection is not an idle reusable |
| 100 | + // transport, even when the kernel receive queue is currently empty. |
| 101 | + if !c.IsActive() || c.inputBuffer.Len() != 0 { |
| 102 | + return false |
| 103 | + } |
| 104 | + |
| 105 | + // Peek one byte directly from the socket. MSG_PEEK preserves application |
| 106 | + // data and MSG_DONTWAIT guarantees this probe never waits for network I/O. |
| 107 | + var buffer [1]byte |
| 108 | + _, _, err := syscall.Recvfrom(c.fd, buffer[:], syscall.MSG_PEEK|syscall.MSG_DONTWAIT) |
| 109 | + return c.isHealthyForReuseAfterPeek(op, err) |
| 110 | +} |
| 111 | + |
| 112 | +// isHealthyForReuseAfterPeek classifies the single nonblocking socket probe |
| 113 | +// and revalidates connection state at the final reuse decision point. |
| 114 | +func (c *TCPConnection) isHealthyForReuseAfterPeek(op *FDOperator, err error) bool { |
| 115 | + if err != syscall.EAGAIN { // EWOULDBLOCK is the same errno on Linux. |
| 116 | + // Data, FIN, EINTR, and socket errors all fail closed. In particular, |
| 117 | + // EINTR is not retried so this nonblocking probe remains bounded. |
| 118 | + return false |
| 119 | + } |
| 120 | + |
| 121 | + // The operator is still owned, so the LinkBuffer cannot be between readv |
| 122 | + // and InputAck while this decision is made. A concurrent user close can |
| 123 | + // detach the operator, so reject it at the decision point. |
| 124 | + return c.IsActive() && c.inputBuffer.Len() == 0 && atomic.LoadInt32(&op.detached) == 0 |
| 125 | +} |
0 commit comments