Skip to content

Commit 0686ecc

Browse files
fix(preprod): Add timeout to build upload polling loop (#3118)
The polling loop in `build upload` could infinite loop when the server returns a pending state (`Created` or `Assembling`) without ever providing an `artifact_url`. The loop only exited on: - `artifact_url` being set (success) - `state == Error` (error bail) - `state.is_finished()` which only matches `Ok` or `Error` States like `Created`, `Assembling`, and `NotFound` are NOT `is_finished()`, causing infinite polling if the server misbehaves. This adds a 5-minute timeout (`DEFAULT_MAX_WAIT`) and 1-second sleep (`ASSEMBLE_POLL_INTERVAL`) between iterations, matching the existing pattern in `src/utils/chunks/upload.rs:poll_assemble`. Fixes #2942 Closes EME-626 --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 9d16bb3 commit 0686ecc

File tree

2 files changed

+19
-5
lines changed

2 files changed

+19
-5
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
### Fixes
1010

1111
- Fixed a bug where the `dart-symbol-map` command did not accept the `--url` argument ([#3108](https://github.com/getsentry/sentry-cli/pull/3108)).
12+
- Add timeout to `build upload` polling loop to prevent infinite loop when server returns unexpected state ([#3118](https://github.com/getsentry/sentry-cli/pull/3118)).
1213

1314
## 3.1.0
1415

src/commands/build/upload.rs

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use std::borrow::Cow;
22
use std::io::Write as _;
33
use std::path::Path;
4+
use std::thread;
5+
use std::time::Instant;
46

57
use anyhow::{anyhow, bail, Context as _, Result};
68
use clap::{Arg, ArgAction, ArgMatches, Command};
@@ -13,13 +15,14 @@ use zip::{DateTime, ZipWriter};
1315

1416
use crate::api::{Api, AuthenticatedApi, ChunkedBuildRequest, ChunkedFileState, VcsInfo};
1517
use crate::config::Config;
18+
use crate::constants::DEFAULT_MAX_WAIT;
1619
use crate::utils::args::ArgExt as _;
1720
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
1821
use crate::utils::build::{handle_asset_catalogs, ipa_to_xcarchive, is_apple_app, is_ipa_file};
1922
use crate::utils::build::{
2023
is_aab_file, is_apk_file, is_zip_file, normalize_directory, write_version_metadata,
2124
};
22-
use crate::utils::chunks::{upload_chunks, Chunk};
25+
use crate::utils::chunks::{upload_chunks, Chunk, ASSEMBLE_POLL_INTERVAL};
2326
use crate::utils::ci::is_ci;
2427
use crate::utils::fs::get_sha1_checksums;
2528
use crate::utils::fs::TempDir;
@@ -654,7 +657,9 @@ fn upload_file(
654657
// iteration of the loop) we get:
655658
// n. state=error, artifact_url unset
656659

657-
let result = loop {
660+
let assemble_start = Instant::now();
661+
662+
loop {
658663
let response = api.assemble_build(
659664
org,
660665
project,
@@ -685,15 +690,23 @@ fn upload_file(
685690
}
686691

687692
if let Some(artifact_url) = response.artifact_url {
688-
break Ok(artifact_url);
693+
return Ok(artifact_url);
689694
}
690695

691696
if response.state.is_finished() {
692697
bail!("File upload is_finished() but did not succeeded or error");
693698
}
694-
};
695699

696-
result
700+
// Check for timeout to prevent infinite loop
701+
if assemble_start.elapsed() > DEFAULT_MAX_WAIT {
702+
bail!(
703+
"Timeout waiting for build assembly after {} seconds",
704+
DEFAULT_MAX_WAIT.as_secs()
705+
);
706+
}
707+
708+
thread::sleep(ASSEMBLE_POLL_INTERVAL);
709+
}
697710
}
698711

699712
/// Utility function to parse a SHA1 digest, allowing empty strings.

0 commit comments

Comments
 (0)