| 213 | } |
| 214 | |
| 215 | std::string Sock::RecvUntilTerminator(uint8_t terminator, |
| 216 | std::chrono::milliseconds timeout, |
| 217 | CThreadInterrupt& interrupt, |
| 218 | size_t max_data) const |
| 219 | { |
| 220 | const auto deadline = GetTime<std::chrono::milliseconds>() + timeout; |
| 221 | std::string data; |
| 222 | bool terminator_found{false}; |
| 223 | |
| 224 | // We must not consume any bytes past the terminator from the socket. |
| 225 | // One option is to read one byte at a time and check if we have read a terminator. |
| 226 | // However that is very slow. Instead, we peek at what is in the socket and only read |
| 227 | // as many bytes as possible without crossing the terminator. |
| 228 | // Reading 64 MiB of random data with 262526 terminator chars takes 37 seconds to read |
| 229 | // one byte at a time VS 0.71 seconds with the "peek" solution below. Reading one byte |
| 230 | // at a time is about 50 times slower. |
| 231 | |
| 232 | for (;;) { |
| 233 | if (data.size() >= max_data) { |
| 234 | throw std::runtime_error( |
| 235 | strprintf("Received too many bytes without a terminator (%u)", data.size())); |
| 236 | } |
| 237 | |
| 238 | char buf[512]; |
| 239 | |
| 240 | const ssize_t peek_ret{Recv(buf, std::min(sizeof(buf), max_data - data.size()), MSG_PEEK)}; |
| 241 | |
| 242 | switch (peek_ret) { |
| 243 | case -1: { |
| 244 | const int err{WSAGetLastError()}; |
| 245 | if (IOErrorIsPermanent(err)) { |
| 246 | throw std::runtime_error(strprintf("recv(): %s", NetworkErrorString(err))); |
| 247 | } |
| 248 | break; |
| 249 | } |
| 250 | case 0: |
| 251 | throw std::runtime_error("Connection unexpectedly closed by peer"); |
| 252 | default: |
| 253 | auto end = buf + peek_ret; |
| 254 | auto terminator_pos = std::find(buf, end, terminator); |
| 255 | terminator_found = terminator_pos != end; |
| 256 | |
| 257 | const size_t try_len{terminator_found ? terminator_pos - buf + 1 : |
| 258 | static_cast<size_t>(peek_ret)}; |
| 259 | |
| 260 | const ssize_t read_ret{Recv(buf, try_len, 0)}; |
| 261 | |
| 262 | if (read_ret < 0 || static_cast<size_t>(read_ret) != try_len) { |
| 263 | throw std::runtime_error( |
| 264 | strprintf("recv() returned %u bytes on attempt to read %u bytes but previous " |
| 265 | "peek claimed %u bytes are available", |
| 266 | read_ret, try_len, peek_ret)); |
| 267 | } |
| 268 | |
| 269 | // Don't include the terminator in the output. |
| 270 | const size_t append_len{terminator_found ? try_len - 1 : try_len}; |
| 271 | |
| 272 | data.append(buf, buf + append_len); |