Skip to content

HTTPConnectionPool.calculateBackoff(failedAttempt:) crashes on 32-bit platforms due to use of randomElement #847

Closed
@jessezamora

Description

@jessezamora

On our armv7 (32-bit) embedded Linux board we're using Swift 6.0 and the AsyncHTTPClient. For a while we noticed that performing retries with the HTTPClient would crash about 26-27 total retries with the following error:

Swift/Integers.swift:1610: Fatal error: Distance is not representable in Int
Current stack trace:
0    libswiftCore.so                    0x00000000b6dbf008 _swift_stdlib_reportFatalErrorInFile + 108
1    libswiftCore.so                    0x00000000b69995d8 <unavailable> + 1431000
2    libswiftCore.so                    0x00000000b699ad2c <unavailable> + 1436972
3    libswiftCore.so                    0x00000000b6b7c44c _assertionFailure(_:_:file:line:flags:) + 228
4    libswiftCore.so                    0x00000000b6af42fc <unavailable> + 2851580
5    libswiftCore.so                    0x00000000b6babcd8 ClosedRange<>.distance(from:to:) + 1032
6    libswiftCore.so                    0x00000000b69b915c <unavailable> + 1560924
7    libswiftCore.so                    0x00000000b6b6b684 Collection.count.getter + 184
8    libswiftCore.so                    0x00000000b69b0a84 <unavailable> + 1526404
9    libswiftCore.so                    0x00000000b6b9b990 Collection.randomElement() + 20
10   calculate-backoff-32bit            0x000000007fdb09ac <unavailable> + 8403372
11   calculate-backoff-32bit            0x000000007fdafc38 <unavailable> + 8399928
12   libc.so.6                          0x00000000b6385e4d __libc_start_main + 149

I set out to figure out "why" with gdb and backtraces and found that the HTTPConnectionPool.calculateBackoff is the main culprit, and crashes on the use of .randomElement:

let jitter: TimeAmount = .nanoseconds((-jitterRange...jitterRange).randomElement()!)

After further investigation into the Swift stdlib as to why it crashes, it seems that the randomElement method is implemented using Int.random:

public func randomElement<T: RandomNumberGenerator>(
    using generator: inout T
  ) -> Element? {
    guard !isEmpty else { return nil }
    let random = Int.random(in: 0 ..< count, using: &generator)
    let idx = index(startIndex, offsetBy: random)
    return self[idx]
  }

On 32-bit systems, this means that if you're trying to get a randomElement on a range that is larger than what fits in Int32 (same as Int), then the code will crash. This seems like maybe a problem or oversight, that can be reported separately to swiftlang/swift.

However, there is a workaround that works just as well and avoids this issue. May I suggest changing this .randomElement to using Int64.random instead?

let jitter: TimeAmount = .nanoseconds(Int64.random(in: -jitterRange...jitterRange))

This theoretically should provide the same result as .randomElement, and works correctly on 32-bit systems.

@Lukasa @gregcotten

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions