Skip to content

[Bug] TokenBucket ignores configured TimeSource and panics on WASM due to hardcoded SystemTime::now() #4459

@srudeepk

Description

@srudeepk

Description

We are running the AWS SDK for Rust in a browser environment (wasm32-unknown-unknown). We explicitly configure a custom, WASM-compatible TimeSource (wrapping js_sys::Date) on the SdkConfig to ensure the SDK never attempts to call SystemTime.

However, after a recent update to aws-smithy-runtime (commit 26c59c2), the SDK ignores this configured time source during the initialization of the default StandardRetryStrategy and panics immediately upon Client construction.

Our web application has been working until we upgraded to aws-smithy-runtime version 1.9.6. Because of this current change we now have to pin our aws-smithy-runtime version to 1.9.5.

The Regression

The commit 26c59c2 introduced a creation_time field to the TokenBucket struct to support time-based refills.

The Default implementation for TokenBucket initializes this field by creating SharedTimeSource. As defined in aws-smithy-async, this default implementation falls back to SystemTimeSource, which unconditionally calls SystemTime::now().

1. The Trigger (TokenBucket):
rust-runtime/aws-smithy-runtime/src/client/retries/token_bucket.rs

impl Default for TokenBucket {
    fn default() -> Self {
        let time_source = SharedTimeSource::default(); // <--- Creates unsafe default source
        Self {
            // ...
            creation_time: time_source.now(), // <--- Panic triggers here
            time_source,
        }
    }
}

2. The Root Cause (SharedTimeSource): in rust-runtime/aws-smithy-async/src/time.rs, SharedTimeSource::default() utilizes TimeSource::default() which in turn defaults to SystemTimeSource and performs the WASM-incompatible call:

impl TimeSource for SystemTimeSource {
    fn now(&self) -> SystemTime {
        SystemTime::now() // <--- Panics on wasm32-unknown-unknown
    }
}

Why this is a Bug

  1. Contract Violation: The SDK offers a .time_source() configuration method on SdkConfig. If a user provides this, the SDK must use it for all time-related operations. Falling back to SystemTime implicitly via Default::default() violates this contract. Our web application has been working until we upgraded to aws-smithy-runtime 1.9.6. Because of this current change we now have to pin to version 1.9.5.

  2. Target Incompatibility: On wasm32-unknown-unknown, SystemTime::now() panics. By initializing eagerly with SystemTime instead of the configured source, the SDK becomes unusable in browser environments.

  3. No Workaround: TokenBucket is in a private module (aws_smithy_runtime::client::retries::token_bucket), so we cannot manually construct it with the correct time source to override this broken default.

Reproduction Steps

  1. Target wasm32-unknown-unknown.

  2. Configure an SDK client (e.g., S3) with a custom TimeSource (using js_sys).

  3. Use BehaviorVersion::latest() (which enables the standard retry strategy).

  4. Result: Panic at runtime during client construction. The custom TimeSource is ignored; SystemTime::now() is called by the TokenBucket default initializer.

Proposed Fix

The TokenBucket (and StandardRetryStrategy) should not use Default::default() if it implies a hard dependency on SystemTime.

Instead, the TokenBucket should be constructed using the TimeSource available in the RuntimeComponents or ConfigBag during the client configuration phase, ensuring that if a user provides a custom source, it is respected throughout the entire client lifecycle.

Context

Commit: 26c59c21b638bbd33a9f46060ff8cc8d4c9df67c (approx Dec 19, 2025)

Component: aws-smithy-runtime / aws-smithy-async

Target: wasm32-unknown-unknown

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinghigh-priorityHigh priority issue

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions