Skip to content

Request is stranded if last connection fails with minimumConnectionCount == 0 and no other request is enqueued after #657

Description

@ptoffy

Describe the issue

If minimumConnectionCount is 0 and there's a request being processed, with no other requests enqueued afterwards and the connection that the work is being done on fails, no subsequent connection is opened to continue the work and the request is left stranded

Vapor version

Not using Vapor

Operating system and version

MacOS 26.4.1 (25E253)

Swift version

Swift Package Manager - Swift 6.3.2

Steps to reproduce

@available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *)
@Test func testLastConnectionDyingWithQueuedRequestCreatesReplacement() {
    var configuration = PoolConfiguration()
    configuration.minimumConnectionCount = 0
    configuration.maximumConnectionSoftLimit = 1
    configuration.maximumConnectionHardLimit = 1
    configuration.keepAliveDuration = nil

    var stateMachine = TestPoolStateMachine(
        configuration: configuration,
        generator: .init(),
        timerCancellationTokenType: MockTimerCancellationToken.self,
        clock: MockClock()
    )

    // first connection
    let request1 = MockRequest(connectionType: MockConnection.self)
    guard case .makeConnection = stateMachine.leaseConnection(request1).connection else {
        Issue.record()
        return
    }
    let connection1 = MockConnection(id: 0)
    #expect(stateMachine.connectionEstablished(connection1, maxStreams: 1).request == .leaseConnection(.init(element: request1), connection1))

    let request2 = MockRequest(connectionType: MockConnection.self)
    #expect(stateMachine.leaseConnection(request2) == .none())

    // connection dies while request2 is queued
    let closedAction = stateMachine.connectionClosed(connection1)
    #expect(closedAction.request == .none)
    guard case .makeConnection(let replacementRequest, _) = closedAction.connection else {
        Issue.record("Expected a replacement connection for the queued request, got \(closedAction.connection)") // fails here
        return
    }

    // the replacement serves the queued request
    let connection2 = MockConnection(id: replacementRequest.connectionID)
    #expect(stateMachine.connectionEstablished(connection2, maxStreams: 1).request == .leaseConnection(.init(element: request2), connection2))
}

Outcome

The work is completed correctly by creating a new request

Additional notes

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    ConnectionPoolFeatures and bugs that are related to the impl in ConnectionPoolModule

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions