-
Notifications
You must be signed in to change notification settings - Fork 123
[ConnectionPool] HTTP1StateMachine #416
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
23fadc0
to
395573d
Compare
@@ -375,6 +375,11 @@ extension HTTPConnectionPool { | |||
self.connections[index].lease() | |||
} | |||
|
|||
mutating func parkConnection(at index: Int) -> (Connection.ID, EventLoop) { | |||
assert(self.connections[index].isIdle) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any reason this shouldn't be a precondition
?
} | ||
} | ||
|
||
mutating func executeRequestOnPreferredEventLoop(_ request: Request, eventLoop: EventLoop) -> Action { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason this isn't private
?
) | ||
} | ||
|
||
mutating func executeRequestOnRequiredEventLoop(_ request: Request, eventLoop: EventLoop) -> Action { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason this isn't private
?
let requestAction: StateMachine.RequestAction = .scheduleRequestTimeout( | ||
request.connectionDeadline, | ||
for: requestID, | ||
on: request.preferredEventLoop |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to look this up here? We have an event loop in scope already.
let requestAction: StateMachine.RequestAction = .scheduleRequestTimeout( | ||
request.connectionDeadline, | ||
for: requestID, | ||
on: request.preferredEventLoop |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to look this up here? We have an event loop in scope already.
Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+HTTP1StateMachine.swift
Outdated
Show resolved
Hide resolved
return .none | ||
} | ||
|
||
assert(self.state == .running, "If we are shutting down, we must not have any idle connections") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason this isn't a precondition
?
|
||
// If we have remaining request queued, we should fail all of them with a cancelled | ||
// error. | ||
let waitingRequests = self.queue.removeAll().map { ($0, $0.id) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The intermediate tuple here seems a bit excessive: do we really need it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I create the tuple here to keep consistency between the two request actions:
case failRequest(Request, Error, cancelTimeout: Request.ID?)
case failRequests([(Request, cancelTimeout: Request.ID?)], Error)
If we fail multiple requests at the same time, we will always need to cancel their timeouts. So I preferred the beauty in having same looking Actions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Chatted offline: Fixed now
return .none | ||
|
||
case .shutDown: | ||
preconditionFailure("It the pool is already shutdown, all connections must have been torn down.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit:
preconditionFailure("It the pool is already shutdown, all connections must have been torn down.") | |
preconditionFailure("If the pool is already shutdown, all connections must have been torn down.") |
enum HTTPTypeStateMachine { | ||
case http1(HTTP1StateMachine) | ||
|
||
case _modifying |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We almost certainly shouldn't need this case. I know I haven't provided this feedback much prior to here, so don't feel compelled to remove it now, but we should probably go through these state machines and rip out the _modifying
states wherever we have then. Recent Swift versions do not CoW here.
395573d
to
bdd23e1
Compare
993fd7c
to
1d99def
Compare
1d99def
to
0890c4b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Partial review, will finish tomorrow.)
@@ -375,6 +375,11 @@ extension HTTPConnectionPool { | |||
self.connections[index].lease() | |||
} | |||
|
|||
mutating func parkConnection(at index: Int) -> (Connection.ID, EventLoop) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't actually a mutating
func
// - 29 failed attempts: ~60s (max out) | ||
|
||
let start = Double(TimeAmount.milliseconds(100).nanoseconds) | ||
let backoffNanoseconds = Int64(start * pow(1.25, Double(self.failedConsecutiveConnectionAttempts))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let backoffNanoseconds = Int64(start * pow(1.25, Double(self.failedConsecutiveConnectionAttempts))) | |
let backoffNanoseconds = Int64(start * pow(1.25, Double(attempt))) |
return .none | ||
} | ||
|
||
private mutating func calculateBackoff(for attempt: Int) -> TimeAmount { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't think this is actually mutating?
} | ||
|
||
// if we are not at max connections, we may want to create a new connection | ||
if self.connections.startingGeneralPurposeConnections >= self.queue.count { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this counting the wrong thing? This seems to risk starting a general purpose connection because we have lots of loop specific work, but without consulting what that loop specific work is.
at index: Int, | ||
context: HTTP1Connections.FailedConnectionContext | ||
) -> Action { | ||
if context.connectionsStartingForUseCase < self.queue.count(for: nil) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps file an issue for future enhancement (no sense gluing it into this PR), but I don't love this spelling. Probably self.queue.generalPurposeCount
would be clearer.
private var connections: HTTP1Connections | ||
private var failedConsecutiveConnectionAttempts: Int = 0 | ||
|
||
private var queue: RequestQueue |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: requests
or requestQueue
provides a little more context
} | ||
|
||
mutating func failedToCreateNewConnection(_ error: Error, connectionID: Connection.ID) -> Action { | ||
defer { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor, but I find it a bit counter-intuitive to defer
the increment here. It means that the first time we call this function we'll pass 0
to calculateBackoff
which says that for 1 failed attempt backoff will 100ms. While that's true it's out of sync with the value of attempt
we pass to it.
I think it'd be clearer to not defer the increment and to reframe calculateBackoff
to be in terms failed attempts (rather than just "attempts") and just subtract 1 from the attempts when computing the unjittered backoff.
case http1(HTTP1StateMachine) | ||
} | ||
|
||
var state: HTTPTypeStateMachine |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assuming HTTPTypeStateMachine
will only have http1
and http2
cases (at least for now!) then httpVersion
/httpType
or similar might be a clearer name here since we're really just delegating to the state machine each case holds.
guard !self.isShuttingDown else { | ||
preconditionFailure("Shutdown must only be called once") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: precondition(!self.isShuttingDown, "...")
// in 10% of the cases, we require an explicit EventLoop. | ||
// let elRequired = (0..<10).randomElement().flatMap { $0 == 0 ? true : false }! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the deal here? (also in testBestConnectionIsPicked
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Follow up to #415, #417.
For the new
HTTPConnectionPool
a newStateMachine
is handy.