Commit d7b5013
authored
core(socket): outbound write queue — stop overlapping composed writes losing shares (#874)
core::Socket::write started a composed boost::asio::async_write immediately,
with no outbound queue. Asio forbids initiating a second composed write on a
descriptor before the first completes: the two async_write_some continuations
are serviced FIFO per descriptor, so any message needing more than one round
gets bytes from the other message spliced into its middle. The peer sees a bad
length/checksum and drops us.
This is reachable on every coin. send_shares issues three back-to-back writes
on one socket in one handler turn (remember_tx -> shares -> forget_tx):
dash/node.cpp:1346/1357/1362, ltc:756/777/784, btc:757/778/785,
dgb:737/758/765, bch:760/781/788. handle_version writes getaddrs/addrme before
the up-to-32 KB have_tx advert. Because send_shares reports its hashes as
"sent" on SUBMISSION and broadcast_share never retries, a spliced frame loses
that share to that peer permanently — silent PPLNS loss with no counter and no
log line.
Fix: a per-socket std::deque of framed buffers plus a writing_ in-flight flag.
write() frames on the caller's thread exactly as before, queues, and starts the
drain only when nothing is in flight; do_write() starts the next composed write
only from the previous one's completion handler, so at most ONE composed
async_write is ever outstanding per socket. Same shape as the working
StratumSession::send_response/do_write already in core/stratum_server.cpp.
Thread-safety: writes are submitted from node/compute threads as well as the
io_context thread, which is how the bug is reachable. m_write_mutex is a LEAF
lock held only around the deque/flag mutation — never across async_write
initiation, an m_node callback, or any other lock — so no new lock-order edge.
The empty-queue check and the writing_ clear happen under the same lock, so a
concurrent submit can never lose the wakeup.
Semantics preserved: on write error the whole queued chain fails together and
error() is reported once, exactly as a single failed write did; a write onto an
already-closed socket stays a silent no-op; the single-write path is
byte-identical.
Tests (folded into the existing allowlisted core_test target, never a new
add_executable — that is the "Not Run" trap): src/core/test/
socket_write_queue_test.cpp drives a real loopback TCP pair with a squeezed
send buffer so each message needs many write_some rounds, then asserts
overlapping writes arrive concatenated in submission order, that concurrent
writers from several threads still produce whole checksum-valid frames, that a
single write is byte-identical, and that writes on a closed socket are dropped
without an error callback. The first two FAIL on the pre-fix tree (byte streams
diverge mid-payload with the next message's magic prefix spliced in).
Fixes #8631 parent 12b71e4 commit d7b5013
4 files changed
Lines changed: 494 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
120 | 120 | | |
121 | 121 | | |
122 | 122 | | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
123 | 180 | | |
124 | 181 | | |
125 | 182 | | |
126 | 183 | | |
127 | 184 | | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
128 | 190 | | |
129 | 191 | | |
130 | 192 | | |
131 | 193 | | |
132 | 194 | | |
133 | 195 | | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
134 | 203 | | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
135 | 209 | | |
136 | 210 | | |
137 | 211 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
| 4 | + | |
4 | 5 | | |
| 6 | + | |
5 | 7 | | |
6 | 8 | | |
7 | 9 | | |
| |||
73 | 75 | | |
74 | 76 | | |
75 | 77 | | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
76 | 114 | | |
77 | 115 | | |
78 | 116 | | |
| |||
94 | 132 | | |
95 | 133 | | |
96 | 134 | | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
97 | 145 | | |
98 | 146 | | |
99 | 147 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
18 | | - | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
19 | 27 | | |
20 | 28 | | |
21 | 29 | | |
| |||
0 commit comments