include/boost/corosio/native/detail/speculative_state.hpp

100.0% Lines (34 / 34) 100.0% Functions (8 / 8)
speculative_state.hpp
f(x) Functions (8)
Line TLA Hits Source Code
1 //
2 // Copyright (c) 2026 Steve Gerbino
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 // Official repository: https://github.com/cppalliance/corosio
8 //
9
10 #ifndef BOOST_COROSIO_NATIVE_DETAIL_SPECULATIVE_STATE_HPP
11 #define BOOST_COROSIO_NATIVE_DETAIL_SPECULATIVE_STATE_HPP
12
13 #include <atomic>
14
15 namespace boost::corosio::detail {
16
17 /** Per-socket per-op-type speculative-attempt hint.
18
19 Tracks whether a speculative non-blocking syscall is worth trying
20 for read and write paths. The flag is set false when speculation
21 discovers an exhausted buffer (EAGAIN) and restored when the async
22 completion path observes a kernel readiness signal.
23
24 Atomics are relaxed because the flag is a hint, not an invariant:
25 a stale read causes at most one wasted or skipped speculation, never
26 a correctness failure.
27
28 @par Thread Safety
29 Distinct objects: Safe.
30 Shared objects: Safe.
31 */
32 class speculative_state
33 {
34 std::atomic<bool> try_read_{true};
35 std::atomic<bool> try_write_{true};
36
37 // Failure-streak counter for the read path. Increments on every
38 // speculative-read EAGAIN; resets to 0 whenever a speculative read
39 // succeeds. When it reaches max_read_failures the socket gives up
40 // on speculative reads permanently — perma_off_read_ latches and
41 // may_speculate_read() returns false regardless of any subsequent
42 // async-read re-arm signal.
43 //
44 // Distinguishes "transient EAGAIN under heavy success" (e.g.
45 // socket_throughput streaming: 1 EAGAIN per ~100 successes ->
46 // streak resets, perma-off never triggers) from "structural EAGAIN
47 // pattern" (e.g. fan_out:nested/16: every speculation EAGAINs ->
48 // streak hits max_read_failures and we stop wasting syscalls).
49 static constexpr int max_read_failures = 4;
50 std::atomic<int> read_eagain_streak_{0};
51 std::atomic<bool> perma_off_read_{false};
52
53 public:
54 /// Return true when speculative read is currently worth trying.
55 159224x bool may_speculate_read() const noexcept
56 {
57 318225x return try_read_.load(std::memory_order_relaxed) &&
58 318225x !perma_off_read_.load(std::memory_order_relaxed);
59 }
60
61 /// Return true when speculative write is currently worth trying.
62 158589x bool may_speculate_write() const noexcept
63 {
64 158589x return try_write_.load(std::memory_order_relaxed);
65 }
66
67 /// Disable speculative reads (kernel buffer is empty).
68 /// Tracks the failure streak; permanently disables speculation
69 /// for this socket once the streak hits max_read_failures.
70 610x void on_read_exhausted() noexcept
71 {
72 610x try_read_.store(false, std::memory_order_relaxed);
73 610x int s = read_eagain_streak_.load(std::memory_order_relaxed);
74 610x if (s < max_read_failures)
75 {
76 610x ++s;
77 610x read_eagain_streak_.store(s, std::memory_order_relaxed);
78 610x if (s >= max_read_failures)
79 4x perma_off_read_.store(true, std::memory_order_relaxed);
80 }
81 610x }
82
83 /// Reset the failure streak on a successful speculative read. The
84 /// successful syscall is proof that the workload pattern *does*
85 /// hit speculation often enough to be worth the occasional EAGAIN.
86 157962x void on_read_success() noexcept
87 {
88 315924x if (read_eagain_streak_.load(std::memory_order_relaxed) != 0)
89 1x read_eagain_streak_.store(0, std::memory_order_relaxed);
90 157962x }
91
92 /// Disable speculative writes (kernel buffer is full).
93 45x void on_write_exhausted() noexcept
94 {
95 45x try_write_.store(false, std::memory_order_relaxed);
96 45x }
97
98 /// Restore speculative reads (kernel signalled readiness via CQE).
99 /// If the socket has hit perma_off_read_ the re-arm is suppressed
100 /// — the strike-counter / perma-off latch overrides this signal.
101 9701x void on_async_read_ready() noexcept
102 {
103 9701x if (!perma_off_read_.load(std::memory_order_relaxed))
104 9576x try_read_.store(true, std::memory_order_relaxed);
105 9701x }
106
107 /// Restore speculative writes (kernel signalled readiness via CQE).
108 9568x void on_async_write_ready() noexcept
109 {
110 9568x try_write_.store(true, std::memory_order_relaxed);
111 9568x }
112
113 /** Restore the default, fully-speculative state.
114
115 The learned hint is scoped to one logical connection; a recycled
116 socket starts a new one and should not inherit the prior
117 connection's streak or perma-off latch.
118 */
119 9002x void reset() noexcept
120 {
121 9002x try_read_.store(true, std::memory_order_relaxed);
122 9002x try_write_.store(true, std::memory_order_relaxed);
123 9002x read_eagain_streak_.store(0, std::memory_order_relaxed);
124 9002x perma_off_read_.store(false, std::memory_order_relaxed);
125 9002x }
126 };
127
128 } // namespace boost::corosio::detail
129
130 #endif
131