Skip to content

cpworker: control socket does not frame requests by newline; split or pipelined requests get no reply #261

Description

@vaderyang

Environment

  • CP version: v0.9.4 (f925e5f6)

Description

The control protocol is newline-delimited JSON over a unix stream socket (docs/REFACTOR-CPCTL.md §1). The reference client, cpgolib/cpworker/unix_client.go, appends \n to each request. A stream socket has no message boundaries, so the server has to split requests on the newline. cpworker instead treats each read() as a single request.

As a result:

  • A request split across several writes gets no reply. This happens with a slow or large write, or with any client that writes in pieces.
  • Only the first of several requests in one write is answered; the others are dropped. This includes {"version":"v1"}\n{"command":"ping"}\n sent together.

Today's Go client happens to write one line and wait for the reply, which hides the problem. Any other client, or a larger request, runs into it.

Steps to reproduce

The failing tests are in TestControlProtocol, cpworker/tests/integration/control_protocol_test.go (branch feat/verification-coverage):

  • request_split_across_writes: no reply within 5 s.
  • pipelined_requests_in_one_write: reply 2 of 3 is missing.
  • handshake_in_the_same_write_as_first_request: reply 2 is missing.

Expected

Buffer each connection's input and answer every complete line, in order.

Actual

Each read() is treated as one request.


中文原文

测试环境

  • CP 版本:v0.9.4(f925e5f6)

问题描述

控制协议是 unix 流 socket 上的换行分隔 JSON(docs/REFACTOR-CPCTL.md §1;参考客户端 cpgolib/cpworker/unix_client.go 在每个请求后追加 \n)。流 socket 没有消息边界,服务端应按换行切分;cpworker 却把每次 read() 当作一条完整请求:

  • 拆成多次写入的请求得不到应答(慢速/大请求或分段写入的客户端)。
  • 一次写入多条请求只应答第一条,其余丢弃,包括同时发送的 {"version":"v1"}\n{"command":"ping"}\n。

现有 Go 客户端恰好"写一行、等应答",掩盖了问题;其他客户端或更大的请求会遇到。

复现

feat/verification-coverage 分支 cpworker/tests/integration/control_protocol_test.go 的 TestControlProtocol:

  • request_split_across_writes:5 秒内无应答。
  • pipelined_requests_in_one_write:3 个应答缺第 2 个。
  • handshake_in_the_same_write_as_first_request:缺第 2 个应答。

期望

按连接缓冲输入,按顺序应答每一整行。

实际

每次 read() 当作一条请求。

Activity

  1. vaderyang commented on Sep 30, 2026

    @vaderyang
    Author

    A likely root cause for the split-request case. When unix_client_recv (cpworker/src/unix-manager.c:491) waits for the rest of a request, it calls

    ret = select(client->fd, &select_set, NULL, NULL, &tv);

    The first argument of select(2) is the highest descriptor plus one. As written, client->fd itself is never examined. select returns 0 after each 500 ms timeout, and after three tries the connection is closed with "incomplete client message". The continuation of a split request is therefore never read, whatever the framing. The call should be select(client->fd + 1, ...). Newline framing, as described above, is still needed for pipelined requests.

    中文原文

    分段请求问题的可能根因:unix_client_recv 等待请求后续数据时调用 select(client->fd, ...),select 第一个参数应为最大描述符 +1,因此 client->fd 本身从未被检查,三次 500 ms 超时后以 "incomplete client message" 关闭连接,续传数据永远读不到。应为 select(client->fd + 1, ...);流水线请求仍需按换行分帧。

  2. timmy21 commented on Oct 8, 2026

    @timmy21
    Contributor

    Fixed in #304 (58d78c6): a request split across writes is now read to its newline (select was passed fd instead of fd + 1, and the newline check used the wrong length). Pipelined requests stay unsupported: the protocol is strict request/response, and the only client, cpgolib/cpworker/unix_client.go, waits for each reply before it sends the next request. The constraint is documented at unix_client_recv.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions