Skip to content

feat(Gax): add stall control for resumable uploads - #9682

Open
bshaffer wants to merge 5 commits into
mainfrom
scotty-stall-control
Open

bshaffer wants to merge 5 commits into
mainfrom
scotty-stall-control

Conversation

@bshaffer

Copy link
Copy Markdown
Contributor

Adds stall control support to Resumable Uploads according to go/cloudsdk-scotty-stall-control.

Changes

  • Adds transferStallMinimumRate (MiB/s) and transferStallTimeout (seconds) options to startUpload().
  • Calculates chunk lag and tracks stall timeout clock across chunk transfers.
  • Computes per-chunk timeouts and per-attempt timeouts (min(chunk_timeout, 2 * chunk_size / rate)).
  • Preserves chunk deadlines across retry and recovery requests.
  • Converts chunk deadline timeouts to an ApiException ("Upload stalled.") when global deadline is not set or not exceeded.
  • Omits the default 10-minute global deadline when stall control is enabled.

@bshaffer
bshaffer requested a review from a team as a code owner September 11, 2026 22:04
@bshaffer bshaffer added the next release PRs to be included in the next release label Sep 15, 2026
Comment on lines +165 to +166
$totalTimeoutMillis = $resumableUploadOptions['totalTimeoutMillis']
?? ($stallControlEnabled ? null : self::DEFAULT_TOTAL_TIMEOUT_MILLIS);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit:

Not sure I love the coalescing operator AND a tertiary operator together. It feels a bit loaded.

@Hectorhammett Hectorhammett left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have mostly nits. Approving them as I leave them to your discretion @bshaffer

if ($state->isStallControlEnabled()) {
$chunkTimeout = $state->calculateNextChunkTimeout($chunkSizeMiB);
if ($globalDeadlineMs !== null) {
$globalRemaining = ($globalDeadlineMs / 1000.0) - $now;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an absolute minute nit, but I like the idea of moving this 1000.0 into a constant:

$globalRemaining = ($globalDeadlineMs / self::MILLS_IN_A_SECOND)

I just like having consistent naming and text that actually reminds immediately what are we doing for numbers. Of course this is extremely minute, so not blocking this review on this of course.

$this->buffer = null;
}

if ($this->buffer === '') {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

if (empty($this->buffer)) {
    $this->buffer = null;
}

I know they are equivalent, but I believe that using empty is a more clear convention of not wanting it to be empty.

$options['credentials'] = new \Google\ApiCore\InsecureCredentialsWrapper();
} else {
$options['hasEmulator'] = true;
$options['credentials'] = new \Google\ApiCore\InsecureCredentialsWrapper();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use the use clause instead of using the path to use the class.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

next release PRs to be included in the next release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants