| 1386 | } |
| 1387 | |
| 1388 | std::fprintf(stderr, "[server] listening on http://%s:%d\n", |
| 1389 | config_.host.c_str(), config_.port); |
| 1390 | |
| 1391 | // A backend-provided sequence engine replaces the one-request worker |
| 1392 | // with the concurrent scheduler. Upstream forwarding stays on the |
| 1393 | // classic path even when the local backend exposes an engine. |
| 1394 | if (SeqEngine * engine = backend_.seq_engine(); |
| 1395 | engine && config_.pflash_upstream_base.empty()) { |
| 1396 | worker_thread_ = |
| 1397 | std::thread([this, engine]() { scheduler_loop(*engine); }); |
| 1398 | } else { |
| 1399 | worker_thread_ = std::thread([this]() { worker_loop(); }); |
| 1400 | } |
| 1401 | |
| 1402 | // Accept loop. |
| 1403 | while (!stopping_.load()) { |
| 1404 | struct pollfd pfd{listen_fd_, POLLIN, 0}; |
| 1405 | int pr = poll(&pfd, 1, 200 /* ms */); |
| 1406 | if (pr <= 0) { |
| 1407 | // 0 = timeout (re-check stopping_); <0 with EINTR = signal. Both loop. |
| 1408 | if (pr < 0 && !sock_is_eintr(sock_errno())) { |
| 1409 | std::fprintf(stderr, "[server] poll() error: %s\n", sock_strerror()); |
| 1410 | } |
| 1411 | continue; |
| 1412 | } |
| 1413 | |
| 1414 | struct sockaddr_in client_sa{}; |
| 1415 | socklen_t client_len = sizeof(client_sa); |
| 1416 | SocketHandle client_fd = accept( |
| 1417 | listen_fd_, (struct sockaddr *)&client_sa, &client_len); |
| 1418 | if (!socket_is_valid(client_fd)) { |
| 1419 | if (stopping_.load()) break; |
| 1420 | if (sock_is_eintr(sock_errno()) || sock_is_eagain(sock_errno())) continue; |
| 1421 | std::fprintf(stderr, "[server] accept() error: %s\n", sock_strerror()); |
| 1422 | continue; |
| 1423 | } |
| 1424 | |
| 1425 | // Disable Nagle for low-latency SSE streaming. |
| 1426 | int flag = 1; |
| 1427 | setsockopt(client_fd, IPPROTO_TCP, TCP_NODELAY, SETSOCKOPT_CAST &flag, sizeof(flag)); |
| 1428 | |
| 1429 | // Spawn client thread (detached — client_main owns the fd). |
| 1430 | active_clients_.fetch_add(1); |
| 1431 | std::thread([this, client_fd]() { |
| 1432 | handle_client(client_fd); |
| 1433 | if (active_clients_.fetch_sub(1) == 1) { |
| 1434 | std::lock_guard<std::mutex> lk(clients_mu_); |
| 1435 | clients_cv_.notify_all(); |
| 1436 | } |
| 1437 | }).detach(); |
| 1438 | } |
| 1439 | |
| 1440 | // Wake the worker thread so it can observe stopping_ and exit. |
| 1441 | queue_cv_.notify_all(); |
| 1442 | |
| 1443 | // Wait for client threads to drain, but bound it: a client mid-stream (long |
| 1444 | // SSE generation) must not hold the process resident on shutdown. After the |
| 1445 | // grace period we proceed — detached client threads are torn down on exit. |
nothing calls this directly
no test coverage detected