Skip to content

Commit 9856cde

Browse files
committed
fix(resolve): git 依赖的两个网络步骤都没有重试 —— 一次瞬时故障让整个构建红
error: git clone of 'https://github.com/…' failed: Cloning into '/home/runner/.mcpp/git/63269d80b47f71e6'... git 一个字都没说,那正是连接在传输中途断掉的样子。2026-08-23 实测两次:一次在 CI, 一次在本机是 TLS connect error: … unexpected eof while reading。 ⚠️⚠️ 而第一版只给 clone 加了重试 —— 用一个不存在的仓库做探针,它在**一秒**内就失败 了,因为先跑的那一步是 git ls-remote,而它仍然是裸的。**在一条路径的一半上加重试, 是一个「报告自己已被加上」的重试。** ⇒ 提成一个共用的 run_with_network_retry,两处都用它。 ⚠️ 三次尝试,最后一次的失败**原样上报**:错的 URL 和不存在的分支与瞬时故障失败方式 完全相同,所以它分辨不了、也不去分辨 —— 一次永久性失败的代价是三秒,而报告与从前 一字不差。把真错误藏在重试后面是更坏的交换。 ⚠️ 回调在失败的一次之后运行:clone 需要把残留目录删掉,否则 git 下一次会报 「already exists and is not an empty directory」—— 第二个、不同的错误,而它对第一个 只字不提。 实测:永久性失败仍然打印 remote: Repository not found,耗时 6s(两次退避);正常 路径无额外开销;92 个单元测试全过。
1 parent ea7c434 commit 9856cde

1 file changed

Lines changed: 48 additions & 2 deletions

File tree

src/build/prepare.cppm

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -756,6 +756,46 @@ sysroot_override(const mcpp::manifest::Manifest& m,
756756
// shape this codebase keeps paying for. A board package that got the right
757757
// answer as a root project and a stale one as a dependency would fail only in
758758
// the consuming build, which is the harder direction to debug.
759+
// ⚠️⚠️ A NETWORK STEP OF A BUILD, RETRIED — AND IT HAD NO RETRY AT ALL.
760+
//
761+
// A dependency resolved by `git` is fetched on every machine that has not
762+
// cached it, and a transport that hiccups once failed the whole build:
763+
//
764+
// error: git clone of 'https://github.com/…' failed:
765+
// Cloning into '/home/runner/.mcpp/git/63269d80b47f71e6'...
766+
//
767+
// — no message from git, which is what a connection that dies mid-transfer
768+
// looks like. Measured twice on 2026-08-23: once in continuous integration and
769+
// once locally as `TLS connect error: … unexpected eof while reading`.
770+
//
771+
// ⚠️ THREE ATTEMPTS, AND THE LAST FAILURE IS REPORTED UNCHANGED. A wrong URL
772+
// and a missing branch fail exactly as a transient fault does, so this cannot
773+
// tell them apart and does not try: a permanent failure costs three seconds and
774+
// produces the message it always did. Hiding a real error behind a retry is the
775+
// worse trade, which is why the count is small and the report is untouched.
776+
//
777+
// ⚠️ BOTH NETWORK STEPS, not one. The first version retried only the clone —
778+
// and a probe with a nonexistent repository failed in ONE second, because the
779+
// step that runs first is `git ls-remote` and it was still bare. A retry on
780+
// half of a path is a retry that reports success at having been added.
781+
//
782+
// `between` runs after a failed attempt: the clone needs the partial directory
783+
// removed, or git's next attempt fails with "already exists and is not an empty
784+
// directory" — a second, different error that says nothing about the first.
785+
mcpp::platform::process::RunResult run_with_network_retry(
786+
std::string_view command,
787+
const std::function<void()>& between = {}) {
788+
mcpp::platform::process::RunResult r{};
789+
for (int attempt = 1; attempt <= 3; ++attempt) {
790+
r = mcpp::platform::process::capture(command);
791+
if (r.exit_code == 0) return r;
792+
if (between) between();
793+
if (attempt < 3)
794+
std::this_thread::sleep_for(std::chrono::seconds(attempt));
795+
}
796+
return r;
797+
}
798+
759799
void fill_target_build_env(mcpp::build::BuildProgramEnv& e,
760800
const mcpp::toolchain::Toolchain* tc)
761801
{
@@ -4275,7 +4315,9 @@ prepare_build(bool print_fingerprint,
42754315
std::format("mcpp.lock records no commit for branch "
42764316
"'{}'", spec.gitRev),
42774317
"resolve");
4278-
auto r = mcpp::platform::process::capture(std::format(
4318+
// The FIRST network step of a git dependency, and therefore
4319+
// the one a transient fault is most likely to meet.
4320+
auto r = run_with_network_retry(std::format(
42794321
"git ls-remote {} {} 2>&1",
42804322
mcpp::platform::shell::quote(spec.git),
42814323
mcpp::platform::shell::quote(
@@ -4357,7 +4399,11 @@ prepare_build(bool print_fingerprint,
43574399
mcpp::platform::shell::quote(gitRoot.string()),
43584400
mcpp::platform::shell::quote(gitRoot.string()),
43594401
mcpp::platform::shell::quote(resolvedGitRev));
4360-
auto r = mcpp::platform::process::capture(cloneCmd);
4402+
// See `run_with_network_retry` for why, and for what the
4403+
// callback is removing between attempts.
4404+
auto r = run_with_network_retry(cloneCmd, [&] {
4405+
std::filesystem::remove_all(gitRoot, ec);
4406+
});
43614407
if (r.exit_code != 0) {
43624408
std::filesystem::remove_all(gitRoot, ec);
43634409
return std::unexpected(std::format(

0 commit comments

Comments
 (0)