ClickHouse / ClickHouse/clickhouse-cpp

`SocketInput::DoRead` reports stale errno on clean connection close

Đã đóng Phù hợp với người mới
#487 1 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

Ngôn ngữ chính
C
Star
382
Fork
208
Merge trung bình
2 ngày 19 giờ
Pull request đã merge (30 ngày)
14

Mô tả

SocketInput::DoRead reports stale errno on clean connection close (recv() == 0)

Summary

When the remote peer closes the TCP connection cleanly, recv() returns 0. The error handling code at this point calls getSocketErrorCode() (which reads errno / WSAGetLastError()), but recv() returning 0 is not an error — it is an EOF indication — and the OS does not update errno in this case. The result is a std::system_error with a stale, misleading error code from a previous syscall.

Reproduction

A minimal TCP server that accepts a connection, reads the client Hello, then immediately closes:

int client_fd = accept(server_fd, nullptr, nullptr);
// optionally recv() the Hello
close(client_fd);

Client code:

clickhouse::ClientOptions opts;
opts.SetHost("127.0.0.1");
opts.SetPort(port);
clickhouse::Client client(opts);  // throws

Actual exception message:

closed: Operation now in progress

Expected exception message:

Connection closed by peer

"Operation now in progress" is EINPROGRESS — completely unrelated to a connection close.

Root Cause

In socket.cpp, SocketInput::DoRead:

size_t SocketInput::DoRead(void* buf, size_t len) {
    const ssize_t ret = ::recv(s_, (char*)buf, (int)len, 0);

    if (ret > 0) {
        return (size_t)ret;
    }

    if (ret == 0) {
        throw std::system_error(getSocketErrorCode(), getErrorCategory(), "closed");
        //                      ^^^^^^^^^^^^^^^^^^^ BUG: errno is stale
    }

    throw std::system_error(getSocketErrorCode(), getErrorCategory(), "can't receive string data");
}

When recv() returns 0, the POSIX specification does not require errno to be set. The value of errno remains whatever it was from the last syscall that failed. In this case, the typical call sequence is:

  1. SocketConnectconnect() in non-blocking mode → errno set to EINPROGRESS
  2. Poll() succeeds → getsockopt(SO_ERROR) returns 0 → socket switched to blocking
  3. SendHello()send() succeeds → errno unchanged (success doesn't clear errno)
  4. ReceiveHello()recv() returns 0 (peer closed) → errno still EINPROGRESS
  5. getSocketErrorCode() returns EINPROGRESS → exception says "Operation now in progress"

Depending on timing and platform, the stale value could be any previous error code, making the exception message non-deterministic and misleading.

Impact

  • Debugging difficulty: The misleading error code sends developers on the wrong path. "Operation now in progress" suggests a non-blocking socket issue, not a closed connection.
  • Error handling: Callers catching std::system_error and inspecting .code().value() get an incorrect error code, making programmatic retry/recovery logic unreliable.
  • Non-determinism: The stale errno value varies depending on which syscall last set it, making the bug platform- and timing-dependent.

Suggested Fix

For the recv() == 0 case, use a well-defined error code instead of reading errno:

if (ret == 0) {
    throw std::system_error(
        ECONNRESET, getErrorCategory(), "connection closed by peer");
}

Or on Windows:

if (ret == 0) {
#if defined(_win_)
    throw std::system_error(
        WSAECONNRESET, windowsErrorCategory::category(), "connection closed by peer");
#else
    throw std::system_error(
        ECONNRESET, std::system_category(), "connection closed by peer");
#endif
}

Alternatively, a custom error code/category could be used to distinguish a clean close (FIN) from a reset (RST), but at minimum the stale getSocketErrorCode() call must be removed from this path.

Environment

  • Library: clickhouse-cpp (all current versions)
  • Affected platforms: All (Linux, macOS, Windows)
  • Affected file: clickhouse/base/socket.cpp, SocketInput::DoRead

Hướng dẫn đóng góp

Chưa lập chỉ mục được hướng dẫn đóng góp cho kho mã nguồn này

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Hướng nghiên cứu

Bắt đầu tại clickhouse/base/socket.cpp, trong SocketInput::DoRead, và lần theo nhánh recv() == 0 cùng với các nhóm lỗi dành riêng cho từng nền tảng hiện có. Thay thế việc tra cứu lỗi socket đã lỗi thời bằng một lỗi kết nối đã đóng được định nghĩa, sau đó xác minh rằng bản tái hiện trong Client báo cáo thông báo kết nối đã đóng thay vì giá trị errno trước đó.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
cpp
Lĩnh vực
networking
Loại issue
Lỗi
Độ khó
2/5
Thời gian dự kiến
1-3 giờ
Mức độ hoạt động
Ít trao đổi
Độ rõ ràng
Đặc tả rõ ràng
Mức phù hợp với người mới
72/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.