| 478 | } |
| 479 | |
| 480 | static void AcceptLoop() { |
| 481 | LOG("[HTTP] Accept loop started on port %u", g_port.load()); |
| 482 | while (g_running.load()) { |
| 483 | // use select with a timeout so we can check g_running periodically |
| 484 | fd_set readfds; |
| 485 | FD_ZERO(&readfds); |
| 486 | FD_SET(g_listenSock, &readfds); |
| 487 | |
| 488 | timeval tv; |
| 489 | tv.tv_sec = 1; |
| 490 | tv.tv_usec = 0; |
| 491 | |
| 492 | int sel = select(0, &readfds, nullptr, nullptr, &tv); |
| 493 | if (sel <= 0) continue; |
| 494 | |
| 495 | SOCKET client = accept(g_listenSock, nullptr, nullptr); |
| 496 | if (client == INVALID_SOCKET) continue; |
| 497 | |
| 498 | // Reject connections from other processes |
| 499 | if (!IsConnectionFromSteam(client)) { |
| 500 | closesocket(client); |
| 501 | continue; |
| 502 | } |
| 503 | |
| 504 | // SO_SNDTIMEO matters too: a stalled final send() can wedge a slot and saturate the thread cap. |
| 505 | DWORD rcvTimeout = 30000; // 30s |
| 506 | DWORD sndTimeout = 30000; // 30s |
| 507 | setsockopt(client, SOL_SOCKET, SO_RCVTIMEO, (const char*)&rcvTimeout, sizeof(rcvTimeout)); |
| 508 | setsockopt(client, SOL_SOCKET, SO_SNDTIMEO, (const char*)&sndTimeout, sizeof(sndTimeout)); |
| 509 | |
| 510 | // handle each connection on its own thread so parallel uploads don't queue up |
| 511 | { |
| 512 | std::lock_guard<std::mutex> lk(g_clientMtx); |
| 513 | PruneClientThreads(); |
| 514 | |
| 515 | // At the cap: backpressure instead of rejecting. A closed socket surfaces |
| 516 | // as a Steam cloud error; waiting for a slot lets large bursts drain. |
| 517 | int waitMs = 0; |
| 518 | while (g_clientSlots.size() >= kMaxClientThreads && g_running.load() && |
| 519 | waitMs < kAcceptBackpressureMaxMs) { |
| 520 | std::this_thread::sleep_for(std::chrono::milliseconds(20)); |
| 521 | waitMs += 20; |
| 522 | PruneClientThreads(); |
| 523 | } |
| 524 | if (g_clientSlots.size() >= kMaxClientThreads) { |
| 525 | LOG("[HTTP] Thread cap (%zu) still full after %dms, rejecting connection", |
| 526 | kMaxClientThreads, waitMs); |
| 527 | closesocket(client); |
| 528 | continue; |
| 529 | } |
| 530 | |
| 531 | auto doneFlag = std::make_shared<std::atomic<bool>>(false); |
| 532 | auto wrapper = [doneFlag](SOCKET s) { |
| 533 | HandleClient(s); |
| 534 | doneFlag->store(true); |
| 535 | }; |
| 536 | try { |
| 537 | g_clientSlots.push_back({std::thread(wrapper, client), doneFlag}); |
nothing calls this directly
no test coverage detected