probe: measure the GitCode upload path (TEMPORARY — do not merge) - #301
Closed
Sunrisepeak wants to merge 2 commits into
Closed
probe: measure the GitCode upload path (TEMPORARY — do not merge)#301Sunrisepeak wants to merge 2 commits into
Sunrisepeak wants to merge 2 commits into
Conversation
`publish-ecosystem`'s GitCode leg has blown its 180s per-asset cap on four
releases now (0.0.94 / 0.0.97 / 0.0.105 / 2026.7.28.2), always on the two
biggest tarballs, always costing ~5min of manual re-upload afterwards. Every
fix so far has been a guess about WHY (raise the cap, skip-don't-retry, batch
verify). This branch measures it instead.
The path under test (tools/gtc `_upload_one`): GET a presigned OBS PUT URL
from the GitCode API, then PUT the whole file body to file.gitcode.com. A
server-side OBS->GitCode callback registers the asset; when only that callback
fails we get `obs_callback ... code:400 ... EOF` for an upload that actually
landed, which is why the probe always re-checks the public download URL and
reports put_ok and serves independently.
Four parallel jobs, one hypothesis each:
baseline network + direction control (down/up, gitcode vs github)
sizes-urllib is throughput flat (bandwidth wall) or collapsing (stall)?
sizes-curl is Python's read-all-then-PUT the bottleneck, or the wire?
concurrency per-connection or per-host cap? mirror_res.sh uploads assets
SERIALLY within a host leg, so if it is per-connection then
parallelising that loop is the entire fix.
Local reference measured before pushing (mainland-CN host -> file.gitcode.com):
8MB in 4.35s = 1.84 MB/s, put=200, serves=206. At that rate the 34.8MB asset
takes ~19s; CI cannot finish it in 180s, so the runner side is >=10x slower.
That is the number these jobs exist to confirm and localise.
Every other workflow is deleted on this branch on purpose: the probe is the
only thing that should run here, and this branch is never merged.
Round 1 (run 30374882109) settled the why: US-East runner -> file.gitcode.com
is a flat ~0.012 MB/s regardless of size and regardless of transport (curl
matches urllib exactly), while the SAME runner downloads from gitcode.com at
3.87 MB/s and uploads to github.com at 16 MB/s, and a mainland-CN host reaches
1.84 MB/s to the same endpoint. So it is inbound rate limiting on the GitCode
side for this egress, not the client and not cross-border bandwidth per se.
At 12 KB/s the 34.8MB asset needs ~48 minutes, which is why four rounds of
tuning MIRROR_UPLOAD_TIMEOUT never worked.
Round 1 ran out of wall-clock (4x8MB serial alone took 2618s) before reaching
the parallel comparison — and that is the measurement that picks the fix:
per-connection budget -> N concurrent uploads scale, and parallelising
mirror_res.sh's serial per-asset loop helps
per-host budget -> parallelism buys nothing and the gitcode leg has
to leave GitHub-hosted runners entirely
scaling/ measures N=1 vs 4 vs 8 on 1MB files so it finishes either way, and
also dumps the upload_url contract to see whether multipart/resumable upload
is even offered. variance/ bounds the spread, which decides whether any fixed
timeout can be safe.
Member
Author
|
Analysis complete — the findings are implemented in #302 (S1: strip the shipped payload, 34.81MB → 4.62MB; S2: concurrent per-asset mirror upload + leg deadline). This branch was measurement-only and is not meant to merge; the full two-round report stays in the PR description for reference. |
Sunrisepeak
added a commit
that referenced
this pull request
Jul 28, 2026
….29.1) (#302) The GitCode mirror leg of `publish-ecosystem` has failed four releases in a row — 0.0.94 / 0.0.97 / 0.0.105 / 2026.7.28.2 — each time on the biggest tarballs, each time costing ~5 minutes of manual `gtc release upload`. The per-asset 180s cap was tuned three times without anyone measuring what it was capping. Probe PR #301 measured it: GitHub US runner -> file.gitcode.com 0.012 MB/s <- the failing path same runner <- file.gitcode.com 3.87 MB/s same runner -> github.com 16 MB/s mainland-CN host -> file.gitcode.com 1.84 MB/s file.gitcode.com is a single Huawei Cloud origin in Beijing, so it is the inbound-to-CN direction that is shaped — not the host (the same runner downloads from it at 3.87 MB/s), not the client (curl and urllib measure identically), and not the runner's egress (16 MB/s to GitHub). The rate also swings ~4.6x run to run, so no fixed per-asset cap can be both safe and useful. At 0.012 MB/s a 34.8MB asset needs ~45 minutes; 180s never had a chance. Two fixes, both measured rather than guessed. S1 — stop shipping 30MB of debug info. Of the 34.8MB linux-x86_64 tarball, almost none was mcpp: the vendored `registry/bin/xlings` shipped at 97.3MB with full debug info (86.9MB on aarch64) against a 4.3MB mcpp. Stripping both takes the tarball to 4.62MB — 7.5x — and the stripped binaries were verified working (`--version`, `new`, `build`, `run`). Two defects made this possible: * the vendored xlings was never stripped at any of the four injection sites, only `cp`'d; * the x86_64 `strip` that DID exist ran before `mcpp pack`, which rebuilds the binary and overwrote it — which is why every release up to and including 2026.7.28.2 shipped an unstripped bin/mcpp while aarch64 (which stages by hand) shipped a stripped one. tools/slim_linux_payload.sh now strips both binaries on the STAGED payload and ASSERTS the result, so a strip that silently stops working fails the release instead of quietly shipping a fat tarball again. The aarch64 leg's best-effort `|| true` strip is now a hard requirement. macOS and Windows are deliberately left alone: their payloads are already 6.1MB / 4.2MB, and stripping a Mach-O invalidates its ad-hoc signature. S2 — mirror a host's assets concurrently, and bound the LEG instead of each asset. The shaping is per-connection: 1/4/8 concurrent 1MB uploads took 76s/80s/93s wall (6.6x aggregate at N=8). Assets within a leg now upload in parallel under MIRROR_MAX_PARALLEL, and MIRROR_UPLOAD_TIMEOUT is replaced by MIRROR_LEG_DEADLINE_GH/GTC. The v0.0.90 rule still holds — nothing is killed and retried inside a round, because the presigned OBS PUT has no multipart/resume (confirmed in #301); exhausting the budget ends the leg and the completeness gate fails loudly as before. Together the gitcode leg goes from ~45 min plus a manual step to ~6 min unattended, and every user's download shrinks 7.5x. Verified locally: slim on the real 2026.7.28.2 payload (101.6MB -> 13.9MB of binaries, 34.81MB -> 4.62MB tarball, stripped mcpp builds and runs an `import std` project); an isolated harness for the concurrent launcher (bounded concurrency, ordered log replay, correct per-asset success/failure); a full real mirror_res.sh run against the existing 2026.7.28.2 tag (all skipped, gate 16/16); and a real parallel-upload run on a throwaway `0.0.0-mirrortest` tag against BOTH hosts (gate 8/8, exit 0), whose artifacts were deleted afterwards with the real release verified intact. NB: release.yml is not exercised by PR CI — it runs on tag/dispatch only — so S1 lands its first real proof at the next release, where the new assertions fail loudly rather than silently.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Temporary diagnostic branch — do not merge. All other workflows are deleted here on purpose so only the probe runs. Two rounds have now answered both the why and the what to do.
Problem
publish-ecosystem's GitCode leg has blown its 180s per-asset cap on four releases — 0.0.94, 0.0.97, 0.0.105, 2026.7.28.2 — always on the biggest tarballs, each time costing ~5 min of manualgtc release upload. Every previous fix guessed at the cause and tunedMIRROR_UPLOAD_TIMEOUT. This branch measured it.Round 1 — why (run
30374882109)Upload from a GitHub-hosted US-East runner to
file.gitcode.comis a flat ~0.012 MB/s, independent of file size and of transport:gtcdoes today)put=-14×8 MB serial: 672 / 650 / 675 / 599 s, wall 2618 s.
Controls:
file.gitcode.comgitcode.comgithub.comfile.gitcode.comSo it is not the Python client (curl matches it exactly) and not cross-border bandwidth in general (the same runner downloads from the same host at 3.87 MB/s). It is inbound rate limiting on the GitCode side for this egress.
At 0.012 MB/s the 34.8 MB asset needs ~48 minutes. The 180s cap was never within an order of magnitude of viable — which is why four rounds of tuning it failed.
Round 2 — what to do (run
30378781240)The budget is per-connection, not per-host. Concurrency scales almost linearly:
Per-connection rate stays 0.012–0.015 MB/s at every N.
But a single file cannot be split. The
upload_urlcontract is a plain OBS V2 single-PUT presigned URL:No
uploads/partNumber/uploadId, and the API exposes no multipart endpoint — the signature cannot cover multipart operations. So intra-file parallelism is unavailable, and the largest asset is an irreducible ~45 min on GH-hosted runners.No fixed timeout can be safe. 8 sequential 1 MB uploads: 0.011, 0.012, 0.012, 0.013, 0.015, 0.025, 0.051 MB/s — a ~4.6× spread. That is exactly why some releases got the mid-size assets in under 180s and others did not.
Concurrency adds an error rate.
n4-3returnedput=502withserves=false— a genuine failure, so any parallel upload path needs bounded retry.The
obs_callbackfalse negative is now explained, not guessed. Thex-obs-callbackheader carries base64 JSON with"callbackUrl": "https://api.gitcode.com/api/v5/repos/xlings-res/mcpp/releases/<tag>/obs_callback?access_token=***". OBS stores the object, then calls that URL; when the callback response can't be read, OBS returns 400 (... code:400 ... EOF) for a PUT whose object did land. Every probe therefore reportsput_okandservesindependently, and this is reproducible.Proposed solution
mirror_res.sh: upload a host leg's assets in parallel (measured 6.6× at N=8). Leg wall time becomes ≈ the largest asset (~45 min) instead of the sum (~100 min).MIRROR_UPLOAD_TIMEOUTcap; use a whole-leg deadline instead (e.g. github 10 min, gitcode 75 min). The per-asset cap is what abandons assets and then fails the completeness gate; with a ~4.6× spread, no per-asset value can be both safe and useful.gtc: re-probe the download URL before reporting an upload failure. The 400/EOF is a confirmed false negative; today it makes_upload_onesys.exit. Add bounded retry for the 502 class.publish-ecosystem: the GitHub leg is fast and should gate the index bump PR; give the GitCode leg its own job with a ~90 min budget so a slow-but-progressing mirror can no longer take the release job down. This removes the manual step entirely — at the cost of one long-running job per release.Not explored: GitCode may have another upload path (their web UI's) beyond the documented v5
upload_urlendpoint thatgtcuses. If one exists with multipart support, item 5 becomes unnecessary.Footprint
Probe assets were random bytes under
probe-<run_id>-*tags onxlings-res/mcpp, deleted by thecleanupjob (verified: all deletes returned 200, and the release list shows no leftoverprobetags).