gitproto: tighten SpooledBody encapsulation and trim docs · Entire
gitproto: tighten SpooledBody encapsulation and trim docs
756eda3→main·
Soph·1mo ago·2 files·+21 added/-36 removed
Three small refinements:
Encapsulate SpooledBody's request wiring as (*SpooledBody).applyTo,
so PostRPCStreamBody doesn't reach into unexported size/path fields
to build req.ContentLength + req.GetBody.Trim the multi-paragraph Cloudflare/idle-write narrative on
PushObjects and PostRPCStreamBody. The story now lives once on
SpooledBody (the type that actually captures the workaround);
the other call sites point there.Fix the countingWriter comment to say why the counter is atomic
(concurrent read from the progress ticker) rather than restating
what the type does.
Sessions
0b0206eed178View transcript
Changes
2
internal/gitproto
Mpush.go+6/-22
Msmarthttp.go+15/-14
216 unmodified lines
// PushObjects pushes locally-materialized objects to the target.
//
// The receive-pack body (update-request header + pack) is written to a
// temp file before the POST so the upload goes out in one continuous
// burst. go-git's encoder runs delta selection synchronously before
// writing any pack bytes, which on big repos stalls the request body
// for tens of seconds — long enough for CDN edges like Cloudflare's to
// hit their idle-write timeout and close the connection mid-upload.
// Spooling collapses encoding and writing into one phase from the
// network's point of view, so the body bytes stream out without gaps.
//
// As a side benefit the spooled body carries a known length, so the
// POST sends Content-Length instead of Transfer-Encoding: chunked
// (matching upstream git's smart-HTTP transport), and req.GetBody lets
// Go's transport retry transient connection failures.
//
// The materialized strategy already requires the full source object
// closure to be local before encoding begins, so a temp file on upload
// doesn't change its fundamental shape. Relay paths (PushPack) keep
// streaming source bytes through to target with chunked encoding —
// source pack data flows steadily, there's no stall to engineer
// around, and the "streaming proxy" property git-sync is built around
// is preserved.
// The encoded body (update-request + pack) is spooled to a temp file
// before the POST so the upload streams without a mid-stream stall.
// See SpooledBody for why this matters. Relay paths (PushPack) keep
// streaming source bytes directly and don't need this.
func PushObjects(
ctx context.Context,
conn Conn,
) {
// Implementation omitted for brevity
}
// countingWriter wraps an io.Writer and tracks total bytes written.
// Reads of the count are safe to call concurrently with Write.
// The count is read by the progress ticker concurrently with the
// encoder's writes, so the counter is atomic.
type countingWriter struct {
w io.Writer
n atomic.Int64
}
// PostRPCStreamBody sends a POST to the given service using a streaming request body.
// Caller must close the returned ReadCloser.
//
// The body is sent as-is. Streaming bodies (io.MultiReader, io.PipeReader)
// produce a chunked request — that's the right shape for relay paths,
// where source pack bytes flow steadily from source through to target.
// Callers whose body would otherwise stall mid-stream (e.g. the
// materialized push path, where the encoder's delta-selection phase
// produces no bytes for tens of seconds) spool the full payload first
// and pass a *SpooledBody; PostRPCStreamBody sets req.ContentLength and
// req.GetBody from its fields so the upload goes out in one continuous
// burst and Go's transport can auto-retry transient connection failures.
// The body is sent as-is — streaming bodies produce a chunked request,
// which is the right shape for relay paths. A *SpooledBody triggers
// fixed-length encoding and replayable retries; see SpooledBody.
func (c *HTTPConn) PostRPCStreamBody(
ctx context.Context,
service string,
body io.Reader,
v2 bool,
phase string,
) (io.ReadCloser, error) {
// Implementation omitted for brevity
}
// applyTo wires the spooled body's known length and a fresh-reader
// factory into req. GetBody lets Go's transport replay the request
// body on transient connection failures by reopening the temp file.
func (s *SpooledBody) applyTo(req *http.Request) {
// Implementation omitted for brevity
}
// ApplyAuth applies the given auth method to an HTTP request. Errors from
// the Authorizer (e.g. transient signing failures) are surfaced as request
// failures by leaving the Authorization header unset; the upstream server
// Implementation omitted for brevity