lizining1231 commented on issue #3694:
URL: https://github.com/apache/dubbo-go/issues/3694#issuecomment-5398185747

   > Thanks for the proposal. The direction is reasonable, but I would not take 
it as one broad change yet.
   > 
   > The main risk is concurrency: `StreamingClientConn` allows `Send`, 
`RequestHeader`, and `CloseRequest` to race with each other, so any replacement 
for `io.Pipe` needs an explicit write-side snapshot/lock boundary, not only a 
response-side `Read`/`Close` guard. Otherwise `CloseRequest` can observe a 
partially written request body.
   > 
   > The performance claim also needs a tighter proof. The current unary 
marshaler still allocates via `codec.Marshal` before anything reaches the 
transport, so the claimed copy reduction is not yet established. I would want 
an alloc benchmark, not just a transport rewrite, before calling that part 
solved.
   > 
   > There is also a body lifetime issue to pin down. `net/http.Client.Do` may 
close request bodies asynchronously after it returns, so buffer reuse needs a 
precise rule across transport errors, redirects/retries, and context cancel. 
Without that, pool return can become racy.
   > 
   > My suggestion is to split this into two steps: first a unary-only fast 
path behind a default-off flag that preserves current behavior for gRPC and 
streaming, then a separate pass for marshal/allocation reduction with race and 
alloc benchmarks.
   
   Thank you for the detailed review. My responses point by point are as 
follows:
   
   1. **Regarding the overly large change scope: split into two medium-sized 
PRs**
   
      Adjust the change scope by splitting this round of work into two PRs. The 
first PR will only implement the unary fast path (`unaryFastPathCall`), with 
the feature flag off by default. The gRPC protocol and streaming path remain 
unchanged (`duplexHTTPCall`), the marshaler/unmarshaler and bufferPool are 
fully reused, and only the send path itself is replaced. Allocation 
optimizations (including the `codec.Marshal` layer) are explicitly deferred to 
the second phase and will not be mixed into the first step.
   
   2. **Regarding the write concurrency boundary issue: add fixes**
   
      Add a shared write lock for `Write` and `CloseWrite`. The 
`StreamingClientConn` interface semantically allows concurrent 
`Send`/`RequestHeader`/`CloseRequest`. The unary normal call sequence is serial 
(the request body `Write` completes fully before `CloseWrite` triggers sending, 
and `sendOnce` guarantees `makeRequest` executes exactly once). However, to 
satisfy the interface's concurrency semantics, we will add explicit 
synchronization on the write side in the implementation: `Write` and 
`CloseWrite` share a write lock (taking a snapshot of the request body), 
ensuring that `CloseRequest` does not observe a partially written request body 
under concurrency. This change will be submitted with the code, along with 
`-race` concurrency tests (exactly-once send, reject writes after failure).
   
   3. **Regarding the missing allocation test data: supplement here**
   
      Attached are simple in-stack A/B test results within the protocol 
(httptest h2c server, both A and B sides share 
marshaler/unmarshaler/bufferPool, only the send path differs, count=3 taking 
the median):
      | payload | duplex allocs/op | fastpath allocs/op | allocs change | B/op 
change |
      
|---------|------------------|--------------------|---------------|-------------|
      | 128B    | 141              | 140                | ≈0%           | -10%  
      |
      | 1024B   | 139              | 139                | (flat)    | -13%      
  |
      | 16384B  | 147              | 145                | -1%           | -9%   
      |
      | 2MiB    | 372              | 324                | -13%          | -4%   
      |
   
      The situation is as stated — in-stack allocs are essentially flat. The 
protocol-layer duplex and fastpath share bufferPool and marshaler. The 
allocation reduction from the fast path mainly comes from the `io.Pipe` itself, 
the per-request goroutine stack, and the body wrapper layer 
(`io.NopCloser+bytes.Reader` → zero-allocation `unaryRequestBody`); 
`codec.Marshal` allocations indeed occur before reaching the transport and are 
not within the scope of this phase's reduction.
   
   4. **Regarding the body lifecycle and pool return: supplement the boundary 
conditions**
   
      Clarify the design for this risk and supplement the boundary conditions. 
Buffer return is not placed in `makeRequest` or `CloseRead`, but is delegated 
to the transport's exactly-once callback when the request body `Close()` is 
called. You correctly pointed out that after `net/http.Client.Do` returns, the 
request body may be closed asynchronously — indeed, `x/net/http2` calls 
`reqBody.Close()` via an independent goroutine in `closeReqBodyLocked` 
(transport.go L381), and the transport's background goroutine may still be 
reading the request body when `Do` returns. Therefore, return cannot occur at 
the `Do` return site. Instead, it is placed in the transport's `Close()` 
callback on the request body (invoked exactly once either after normal 
completion of reading, or when the server responds early/aborts via 
`closeReqBodyLocked`), and the buffer is returned in that callback. 
Additionally, the following boundary conditions/tests are supplemented:
   
      - Normal read completion: exactly one return, no double return;
      - Server early response (non-2xx abort): the transport interrupts request 
body writing and calls `Close`, the buffer is still returned without leakage;
      - Redirect/retry: `net/http` closes the request body on redirect, which 
also falls into the `Close` callback;
      - Context cancellation: the transport aborts request body writing and 
closes the body, same path as above;
      - Return and in-flight `Read` are serialized by the `unaryRequestBody` 
mutex lock to prevent data races and cross-request contamination.
   
      Accompanying `-race` tests: abort return, concurrent read-close, 
read-after-close (use-after-return), exactly-once return.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to