Skip to content

feat(storage): add progress stall timeout for S3 uploads#3306

Open
burak-initialcode wants to merge 2 commits intoaws-amplify:mainfrom
burak-initialcode:upload-timeouts
Open

feat(storage): add progress stall timeout for S3 uploads#3306
burak-initialcode wants to merge 2 commits intoaws-amplify:mainfrom
burak-initialcode:upload-timeouts

Conversation

@burak-initialcode
Copy link
Copy Markdown

@burak-initialcode burak-initialcode commented Apr 27, 2026

Issue

#3302

Description

Adds a configurable progress stall timeout to S3 storage uploads. When upload progress does not advance for the configured interval (e.g. due to network issues), the upload is automatically cancelled and the operation's onError consumer receives an error instead of hanging indefinitely.

Problem: On poor or unstable networks, S3 uploads can stall indefinitely without the client receiving any progress updates.

Solution:

  • New public ProgressStallTimeout sealed class in :core (Disabled / Interval(seconds)), mirroring the iOS API.
  • New public ProgressStallTimeoutException (AmplifyException subclass) in :core so consumers can pattern-match on the cause of the surfaced StorageException.
  • The plugin-wide default is set on AWSS3StoragePluginConfiguration.progressStallTimeout and can be overridden per upload via AWSS3StorageUploadFileOptions / AWSS3StorageUploadInputStreamOptions (null defers to the plugin default; Disabled explicitly opts out for that operation).
  • The resolved interval is threaded through the upload pipeline into WorkData. SinglePartUploadWorker and PartUploadTransferWorker decorate their ProgressListeners with a coroutine-based StallDetectingProgressListener. On stall, the worker cancels the upload job, marks the transfer FAILED (workers explicitly skip retry for ProgressStallTimeoutException), and surfaces the typed cause via Throwable.toStorageUploadException(...).
  • Default: Disabled (opt-in). Existing behavior is preserved.

Configuration:

// Plugin default: 30 seconds
Amplify.addPlugin(
    AWSS3StoragePlugin(
        AWSS3StoragePluginConfiguration {
            progressStallTimeout = ProgressStallTimeout.Interval(30)
        }
    )
)

// Override for a large upload: 120 seconds
val options = AWSS3StorageUploadFileOptions.builder()
    .progressStallTimeout(ProgressStallTimeout.Interval(120))
    .build()

Amplify.Storage.uploadFile(StoragePath.fromString("public/big.zip"), file, options)

How did you test these changes?

  • New unit tests:
    • StallDetectingProgressListenerTest — coroutine-based timer logic (no progress, progress resets timer, close cancels, disabled threshold no-op, etc.) using kotlinx-coroutines-test (StandardTestDispatcher + advanceTimeBy).
    • StorageExceptionExtensionsTest — verifies toStorageUploadException preserves the typed ProgressStallTimeoutException cause (direct and nested) and falls back to the default message for unrelated throwables.
    • AWSS3StoragePluginConfigurationTest — defaults, builder, and override semantics for progressStallTimeout.
    • AWSS3StorageUploadFileOptionsTest / AWSS3StorageUploadInputStreamOptionsTest — option propagation, null-defers-to-plugin semantics, builder + Java interop.
  • Updated existing operation/component unit tests for the new 6-arg StorageService.uploadFile / uploadInputStream overloads.
  • New instrumentation tests in AWSS3StoragePathUploadTest (mirrors the iOS PR coverage retained after reviewer feedback):
    • testUploadSmallFileWithProgressStallTimeoutOptionCompletesSuccessfully — single-part happy path with the stall timer set.
    • testUploadLargeFileWithProgressStallTimeoutOptionCompletesSuccessfully — multipart happy path with the stall timer set.
  • ./gradlew :core:test :aws-storage-s3:test — all passing locally.
  • ./gradlew ktlintCheck checkstyle apiDump — all passing; API dump shows additive-only changes (minor bump).

Documentation update required?

  • No
  • Yes (Please include a PR link for the documentation update)

General Checklist

  • Added Unit Tests
  • Added Integration Tests
  • Security oriented best practices and standards are followed (e.g. using input sanitization, principle of least privilege, etc)
  • Ensure commit message has the appropriate scope (e.g fix(storage): message, feat(auth): message, chore(all): message)

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@burak-initialcode burak-initialcode requested review from a team as code owners April 27, 2026 10:21
Add a configurable progress stall timeout to S3 storage uploads. When
upload progress does not advance for the specified interval (e.g. due to
network issues), the upload is automatically cancelled and the operation's
onError consumer receives a typed StorageException whose cause is
ProgressStallTimeoutException, instead of hanging indefinitely.

ProgressStallTimeout (sealed: Disabled / Interval(seconds)) is exposed on
AWSS3StoragePluginConfiguration as the plugin-wide default and can be
overridden per upload via AWSS3StorageUploadFileOptions and
AWSS3StorageUploadInputStreamOptions. The resolved interval is threaded
through the upload pipeline into WorkData so SinglePartUploadWorker and
PartUploadTransferWorker can decorate their ProgressListeners with a
StallDetectingProgressListener; on stall the worker cancels the upload
job, marks the transfer FAILED (no retry), and surfaces the typed cause.

The default remains Disabled (opt-in), preserving existing behavior.

fixes aws-amplify#3302
Cover the happy path for both single-part and multipart S3 uploads when a
ProgressStallTimeout is set on the upload options. The stall timer must
not break a successful upload; these tests assert that the option flows
end-to-end through the worker pipeline without affecting normal completion.

Mirrors the unit-tested integration coverage retained in the iOS PR.
@burak-initialcode
Copy link
Copy Markdown
Author

@harsh62 any progress on this one?

@jvh-aws
Copy link
Copy Markdown
Contributor

jvh-aws commented Apr 29, 2026

@burak-initialcode I am currently looking into the PR. Will let you know once I have an update.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants