Skip to content

wtx-grpc: return the reply instead of having it cleared - #1300

Merged
MDA2AV merged 2 commits into
mainfrom
fix/wtx-grpc-empty-reply
Aug 24, 2026
Merged

wtx-grpc: return the reply instead of having it cleared#1300
MDA2AV merged 2 commits into
mainfrom
fix/wtx-grpc-empty-reply

Conversation

@MDA2AV

@MDA2AV MDA2AV commented Aug 24, 2026

Copy link
Copy Markdown
Owner

Both wtx gRPC entries answered HTTP/2 200, content-type: application/grpc, grpc-status: 0 — with an empty body. The reply message never went out.

Cause

The handler serializes SumReply into state.req.msg_data.body and returns wtx::Result<()>. In wtx's server framework the handler's return value finalizes the response, and ResFinalizer for () is:

fn finalize_response(self, req: &mut Request<MsgBufferString>) -> Result<StatusCode, E> {
  req.clear();          // wipes what the handler just wrote
  Ok(StatusCode::Ok)
}

So the framework cleared the buffer the handler had filled. Returning VerbatimParams instead carries the status and leaves the body alone:

fn finalize_response(self, _: &mut Request<MsgBufferString>) -> Result<StatusCode, E> {
  Ok(self.0)
}

That is exactly what wtx-http2 already does — it returns VerbatimParams and JsonReply — which is why that entry was never affected by this.

Not a version problem

0.48.1 and 0.49.0 behave identically; I built and probed both. Worth knowing separately: the version pin is inert. The Dockerfile copies Cargo.toml without Cargo.lock, so cargo re-resolves on every build — the committed lock says 0.48.1 while the image builds 0.49.0, and the two are inconsistent enough that cargo --locked refuses outright.

Why it went unnoticed

h2load counts HTTP/2 200 responses and never inspects the gRPC payload, so an empty reply benchmarks perfectly and publishes RPS.

date event
2026-07-30 unary-grpc results published
2026-08-05 handler rewritten onto Http2ServerFramework (#1056)
2026-08-14 validate.sh gains real gRPC body assertions (#1123)
2026-08-24 disabled (#1291)

The numbers published between July and August were for a server returning no reply body.

Result

Both entries re-enabled and validated:

wtx-grpc      PASS [GetSum over h2c] (a=363 b=893 -> 1256)
              PASS [GetSum over h2c (second random pair)] (a=523 b=830 -> 1353)
wtx-grpc-tls  PASS [GetSum over h2+TLS] (a=395 b=537 -> 932)
              PASS [GetSum over h2+TLS (second random pair)] (a=974 b=558 -> 1532)

They need a re-benchmark before publishing again — the old unary-grpc numbers were removed with #1291 and should not come back as they were.

Related: #1299 restores the wtx-http2 / wtx-ws results that #1291 deleted alongside these, since all five wtx entries share one result file.

@MDA2AV
MDA2AV force-pushed the fix/wtx-grpc-empty-reply branch from 5ff4489 to 24c7ac8 Compare August 24, 2026 15:53
@MDA2AV

MDA2AV commented Aug 24, 2026

Copy link
Copy Markdown
Owner Author

/benchmark-multiple --save

@github-actions

Copy link
Copy Markdown
Contributor

👋 Benchmark request received. A collaborator will review and approve the run.

@MDA2AV

MDA2AV commented Aug 24, 2026

Copy link
Copy Markdown
Owner Author

/benchmark-multiple --save

@github-actions

Copy link
Copy Markdown
Contributor

👋 Benchmark request received. A collaborator will review and approve the run.

@github-actions

Copy link
Copy Markdown
Contributor

Benchmark Results

Frameworks: 2 | Test: all tests | Failed: 2

wtx-grpc

No results captured.

Log tail
#14 2.955    Compiling httparena-wtx-grpc v0.1.0 (/app)
#14 3.266    Compiling tokio-macros v2.7.2
#14 3.511    Compiling tokio v1.53.1
#14 6.306    Compiling wtx v0.49.0
#14 17.96     Finished `release` profile [optimized] target(s) in 17.56s
#14 DONE 18.0s

#15 [stage-1 2/2] COPY --from=build /app/target/release/httparena-wtx-grpc /server
#15 DONE 0.1s

#16 exporting to image
#16 exporting layers
#16 exporting layers 0.2s done
#16 exporting manifest sha256:f480e609d01b1e72a6cb060015326a7be506f715c7c07803d9f7c38a894ad739 0.0s done
#16 exporting config sha256:905830f5e239c570be315651a5fa65896b9ac67d47296ff111e2e2a1a6f30c60 0.0s done
#16 exporting attestation manifest sha256:dfb5a826401567db95de7f06c7659e6c0e38ddb2e8e73c56669e8cdb3f059956 0.0s done
#16 exporting manifest list sha256:b0d383698e9582f15d983daa3680ec1681c3338f12501be1b0d60f47277b0269
#16 exporting manifest list sha256:b0d383698e9582f15d983daa3680ec1681c3338f12501be1b0d60f47277b0269 0.0s done
#16 naming to docker.io/library/httparena-wtx-grpc:latest done
#16 unpacking to docker.io/library/httparena-wtx-grpc:latest 0.0s done
#16 DONE 0.4s
[info] tuning host for benchmark runs
[info] CPU governor → performance
[info] setting kernel socket limits
[info] setting UDP buffer sizes for QUIC
[info] setting loopback MTU to 1500 (realistic Ethernet)
[info] restarting docker daemon
[info] dropping kernel caches
[info] skip: wtx-grpc does not subscribe to baseline
[info] skip: wtx-grpc does not subscribe to pipelined
[info] skip: wtx-grpc does not subscribe to limited-conn
[info] skip: wtx-grpc does not subscribe to json
[info] skip: wtx-grpc does not subscribe to json-comp
[info] skip: wtx-grpc does not subscribe to json-tls
[info] skip: wtx-grpc does not subscribe to upload
[info] skip: wtx-grpc does not subscribe to api-4
[info] skip: wtx-grpc does not subscribe to api-16
[info] skip: wtx-grpc does not subscribe to static
[info] skip: wtx-grpc does not subscribe to static-tls
[info] skip: wtx-grpc does not subscribe to async-db
[info] skip: wtx-grpc does not subscribe to crud
[info] skip: wtx-grpc does not subscribe to fortunes
[info] skip: wtx-grpc does not subscribe to baseline-h2
[info] skip: wtx-grpc does not subscribe to static-h2
[info] skip: wtx-grpc does not subscribe to baseline-h2c
[info] skip: wtx-grpc does not subscribe to json-h2c
[info] skip: wtx-grpc does not subscribe to baseline-h3
[info] skip: wtx-grpc does not subscribe to static-h3
[info] skip: wtx-grpc does not subscribe to gateway-64
[info] skip: wtx-grpc does not subscribe to gateway-h3
[info] skip: wtx-grpc does not subscribe to production-stack

==============================================
=== wtx-grpc / unary-grpc / 256c (tool=h2load) ===
==============================================
[info] waiting for server...
/home/diogo/actions-runner/_work/HttpArena/HttpArena/scripts/lib/framework.sh: line 248: GHZ: unbound variable
httparena-bench-wtx-grpc
httparena-bench-wtx-grpc
[info] restoring loopback MTU to 65536

wtx-grpc-tls

No results captured.

Log tail
#14 20.92     Finished `release` profile [optimized] target(s) in 20.48s
#14 DONE 20.9s

#7 [stage-1 1/2] FROM docker.io/library/debian:bookworm-slim@sha256:4724b8cc51e33e398f0e2e15e18d5ec2851ff0c2280647e1310bc1642182655d
#7 CACHED

#15 [stage-1 2/2] COPY --from=build /app/target/release/httparena-wtx-grpc-tls /server
#15 DONE 0.1s

#16 exporting to image
#16 exporting layers
#16 exporting layers 0.2s done
#16 exporting manifest sha256:82e88475507f50f8b68d1d2c62f5b33b96604045a8000f8a4fa921acdc3416da 0.0s done
#16 exporting config sha256:0c476de56a1683b11bdf6d439197538a8346a44cabab8420bf688c595e32c3f2 0.0s done
#16 exporting attestation manifest sha256:a5671ae18c63ef5bc798eb470a9c70d6c8f454011508c71844fdfb8a27e3c68f 0.0s done
#16 exporting manifest list sha256:b5a67a4112f330bd50dbdcc5bd8d96fef2f62ca81c3f310dbe39959a4538afcf
#16 exporting manifest list sha256:b5a67a4112f330bd50dbdcc5bd8d96fef2f62ca81c3f310dbe39959a4538afcf 0.0s done
#16 naming to docker.io/library/httparena-wtx-grpc-tls:latest done
#16 unpacking to docker.io/library/httparena-wtx-grpc-tls:latest 0.0s done
#16 DONE 0.4s
[info] tuning host for benchmark runs
[info] CPU governor → performance
[info] setting kernel socket limits
[info] setting UDP buffer sizes for QUIC
[info] setting loopback MTU to 1500 (realistic Ethernet)
[info] restarting docker daemon
[info] dropping kernel caches
[info] skip: wtx-grpc-tls does not subscribe to baseline
[info] skip: wtx-grpc-tls does not subscribe to pipelined
[info] skip: wtx-grpc-tls does not subscribe to limited-conn
[info] skip: wtx-grpc-tls does not subscribe to json
[info] skip: wtx-grpc-tls does not subscribe to json-comp
[info] skip: wtx-grpc-tls does not subscribe to json-tls
[info] skip: wtx-grpc-tls does not subscribe to upload
[info] skip: wtx-grpc-tls does not subscribe to api-4
[info] skip: wtx-grpc-tls does not subscribe to api-16
[info] skip: wtx-grpc-tls does not subscribe to static
[info] skip: wtx-grpc-tls does not subscribe to static-tls
[info] skip: wtx-grpc-tls does not subscribe to async-db
[info] skip: wtx-grpc-tls does not subscribe to crud
[info] skip: wtx-grpc-tls does not subscribe to fortunes
[info] skip: wtx-grpc-tls does not subscribe to baseline-h2
[info] skip: wtx-grpc-tls does not subscribe to static-h2
[info] skip: wtx-grpc-tls does not subscribe to baseline-h2c
[info] skip: wtx-grpc-tls does not subscribe to json-h2c
[info] skip: wtx-grpc-tls does not subscribe to baseline-h3
[info] skip: wtx-grpc-tls does not subscribe to static-h3
[info] skip: wtx-grpc-tls does not subscribe to gateway-64
[info] skip: wtx-grpc-tls does not subscribe to gateway-h3
[info] skip: wtx-grpc-tls does not subscribe to production-stack
[info] skip: wtx-grpc-tls does not subscribe to unary-grpc

==============================================
=== wtx-grpc-tls / unary-grpc-tls / 256c (tool=h2load) ===
==============================================
[info] waiting for server...
/home/diogo/actions-runner/_work/HttpArena/HttpArena/scripts/lib/framework.sh: line 248: GHZ: unbound variable
httparena-bench-wtx-grpc-tls
httparena-bench-wtx-grpc-tls
[info] restoring loopback MTU to 65536

@MDA2AV
MDA2AV force-pushed the fix/wtx-grpc-empty-reply branch from 24c7ac8 to 0284f52 Compare August 24, 2026 16:14
Both gRPC entries answered HTTP/2 200 with content-type application/grpc and
grpc-status 0, and an empty body -- the reply message never went out.

The handler serializes SumReply into state.req.msg_data.body and returns
wtx::Result<()>. In wtx's server framework the handler's return value
finalizes the response, and ResFinalizer for () is:

    fn finalize_response(self, req: &mut Request<MsgBufferString>) -> ... {
      req.clear();
      Ok(StatusCode::Ok)
    }

so the framework wiped the buffer the handler had just written. Returning
VerbatimParams instead carries the status and leaves the body alone, which is
what wtx-http2 already does -- it returns VerbatimParams and JsonReply, and
that is why it was never affected.

Not a version regression: 0.48.1 and 0.49.0 behave identically. Worth noting
the pin is inert anyway, since the Dockerfile copies Cargo.toml without
Cargo.lock, so cargo re-resolves on every build; the committed lock says
0.48.1 while the image builds 0.49.0, and the two are inconsistent enough
that cargo --locked refuses outright.

It went unnoticed because h2load counts HTTP/2 200s and never looks at the
gRPC payload, so an empty reply benchmarks perfectly. The entries published
unary-grpc numbers from 2026-07-30; the body assertion only arrived on
2026-08-14 in #1123, and it is what caught this.

Both re-enabled. Validated: wtx-grpc 2/0 (GetSum over h2c, both random
pairs), wtx-grpc-tls 2/0 (GetSum over h2+TLS).
@MDA2AV
MDA2AV force-pushed the fix/wtx-grpc-empty-reply branch from 0284f52 to ebef65b Compare August 24, 2026 16:21
@MDA2AV

MDA2AV commented Aug 24, 2026

Copy link
Copy Markdown
Owner Author

/benchmark-multiple --save

@github-actions

Copy link
Copy Markdown
Contributor

👋 Benchmark request received. A collaborator will review and approve the run.

@github-actions

Copy link
Copy Markdown
Contributor

Benchmark Results

Frameworks: 2 | Test: all tests

wtx-grpc

Test Conn RPS CPU Mem Δ RPS Δ Mem
unary-grpc 256 3,990,708 6379.2% 727MiB NEW NEW
unary-grpc 1024 3,620,970 6383.3% 1022MiB NEW NEW

wtx-grpc-tls

Test Conn RPS CPU Mem Δ RPS Δ Mem
unary-grpc-tls 256 3,688,532 5485.4% 726MiB NEW NEW
unary-grpc-tls 1024 3,625,918 6380.6% 984MiB NEW NEW

@MDA2AV
MDA2AV merged commit b33887a into main Aug 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant