tweaks
5195c69 · gmorell · 2026-05-22 19:36
Files changed
modified
lib/git_gud_web/controllers/runner_controller.ex
+20
−5
@@ -527,11 +527,9 @@ defmodule GitGudWeb.RunnerController do
| 527 | 527 | |
| 528 | 528 | # Loud failure trumps a silent framing bug. Empty protobuf |
| 529 | 529 | # bodies look valid by spec but break Connect-Go's unary |
| 530 | − # decoder AND, downstream, the HTTP/1.1 keepalive stream | |
| 531 | − # (next request reads mid-stream — see Bandit.HTTPError | |
| 532 | − # `Request line HTTP error`). Each RPC handler is expected | |
| 533 | − # to populate at least one non-default field; this catches | |
| 534 | − # any new endpoint that forgets. | |
| 530 | + # decoder. Each RPC handler is expected to populate at | |
| 531 | + # least one non-default field; this catches any new | |
| 532 | + # endpoint that forgets. | |
| 535 | 533 | if byte_size(body) == 0 do |
| 536 | 534 | require Logger |
| 537 | 535 |
@@ -543,6 +541,7 @@ defmodule GitGudWeb.RunnerController do
| 543 | 541 | |
| 544 | 542 | conn |
| 545 | 543 | |> put_resp_content_type(echoed_ct, nil) |
| 544 | + |> close_runner_conn() | |
| 546 | 545 | |> send_resp(200, body) |
| 547 | 546 | |
| 548 | 547 | {:json, _} -> |
@@ -550,10 +549,26 @@ defmodule GitGudWeb.RunnerController do
| 550 | 549 | |
| 551 | 550 | conn |
| 552 | 551 | |> put_resp_content_type("application/json", nil) |
| 552 | + |> close_runner_conn() | |
| 553 | 553 | |> send_resp(200, body) |
| 554 | 554 | end |
| 555 | 555 | end |
| 556 | 556 | |
| 557 | + # Disable HTTP/1.1 keep-alive for runner-protocol responses. | |
| 558 | + # | |
| 559 | + # Bandit + forgejo-runner's Connect-Go HTTP/1.1 client get into | |
| 560 | + # framing trouble when our content-length disagrees with bytes- | |
| 561 | + # on-wire by even one byte: the next request on the same socket | |
| 562 | + # parses from inside the previous response body, the request line | |
| 563 | + # looks like garbage, Phoenix returns 404, and the runner reports | |
| 564 | + # the "unimplemented: 404 Not Found" error. The runner only sends | |
| 565 | + # one request per logical operation anyway, so the keep-alive | |
| 566 | + # savings are minimal; force `Connection: close` to make every | |
| 567 | + # response its own TCP session and the failure class disappears. | |
| 568 | + defp close_runner_conn(conn) do | |
| 569 | + Plug.Conn.put_resp_header(conn, "connection", "close") | |
| 570 | + end | |
| 571 | + | |
| 557 | 572 | # ── helpers ────────────────────────────────────────────────────────── |
| 558 | 573 | |
| 559 | 574 | defp authenticate(conn) do |
modified
lib/git_gud_web/plugs/twirp.ex
+6
−0
@@ -36,6 +36,12 @@ defmodule GitGudWeb.Plugs.Twirp do
| 36 | 36 | |
| 37 | 37 | conn |
| 38 | 38 | |> put_resp_content_type("application/json", nil) |
| 39 | + # Match the runner-protocol success path: force `Connection: | |
| 40 | + # close` so the runner opens a fresh TCP connection for its | |
| 41 | + # next request. Eliminates the HTTP/1.1 keep-alive framing- | |
| 42 | + # cascade where one mis-sized response corrupts the parse of | |
| 43 | + # the next request line. | |
| 44 | + |> put_resp_header("connection", "close") | |
| 39 | 45 | |> send_resp(status, body) |
| 40 | 46 | end |
| 41 | 47 |
Parents: 296556b