Skip to content

Add MockTools for testing HTTP1ConnectionPool.StateMachine #417

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

Merged
merged 4 commits into from
Sep 8, 2021

Conversation

fabianfett
Copy link
Member

No description provided.

compiles again

Compiles

Better names.
}
}

var isLeased: Bool {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is leased the best adjective here? This sort of seems to mean "is in use".

case .http1(leased: false, let lastIdle):
self.state = .http1(leased: true, lastIdle: lastIdle)
case .http2(let streams, let used) where used >= streams:
throw Errors.connectionNotIdle
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we have a more granular error for this?

let eventLoop: EventLoop

private(set) var state: State = .starting
private(set) var isParked: Bool = false
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bit odd that this isn't part of the state. Reading the code suggests that only idle connections can be parked, so it seems like we should be computing that from the state to avoid the risk of this information falling out of step.

var newestParkedConnection: Connection? {
self.connections.values
.filter { $0.isParked }
.max(by: { $0.lastIdle! < $1.lastIdle! })
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My comment about parked falling out to step seems relevant here: you can park an HTTP/2 connection, so this force-unwrap can fail. The same is true for the few below this comment. This doesn't seem like it's what we intended.

struct MockConnectionPool {
typealias Connection = HTTPConnectionPool.Connection

enum Errors: Error {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hashable?


enum State {
case starting
case http1(leased: Bool, lastIdle: NIODeadline)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

availableSince ;)

enum State {
case starting
case http1(leased: Bool, lastIdle: NIODeadline)
case http2(streams: Int, used: Int)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is streams max concurrent streams?

private struct MockConnectionState {
typealias ID = HTTPConnectionPool.Connection.ID

enum State {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hashable?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could make this Hashable, but TBH really his should be private. So the benefit of it being Hashable is not very large. :)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, it isn't large. I was thinking along the lines of killing some of the switches in the various is<State> computed properties.

throw Errors.connectionNotIdle
}

guard !self.isParked else {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I find guard not x else { return ... } much less clear than if x { return ... }

Comment on lines 188 to 195
case .http1(let leased, _):
if leased {
throw Errors.connectionNotIdle
}
case .http2(_, let used):
if used > 0 {
throw Errors.connectionNotIdle
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to transition to closed here on the happy path?

func newestParkedConnection(for eventLoop: EventLoop) -> Connection? {
self.connections.values
.filter { $0.eventLoop === eventLoop && $0.isParked }
.sorted(by: { $0.lastIdle! > $1.lastIdle! })
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is redundant with the max(by:) below

Suggested change
.sorted(by: { $0.lastIdle! > $1.lastIdle! })


mutating func createConnection(_ connectionID: Connection.ID, on eventLoop: EventLoop) throws {
guard self.connections[connectionID] == nil else {
throw Errors.connectionIDAlreadyUsed
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we really need to distinguish this from Errors.connectionExists?

@fabianfett fabianfett requested review from glbrntt and Lukasa September 7, 2021 14:03
@fabianfett fabianfett added this to the HTTP/2 support milestone Sep 8, 2021
Copy link
Collaborator

@glbrntt glbrntt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good aside from a couple of trivial things

if let required = request.requiredEventLoop, required !== self.eventLoop {
throw Errors.connectionDoesNotFulfillEventLoopRequirement
}
if used + 1 > maxStreams {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems odd to do the addition twice.

Suggested change
if used + 1 > maxStreams {
if used >= maxStreams {

self.connections.values.filter { $0.isParked }.count
}

var leased: Int {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be used now?

}

extension MockConnectionPool {
mutating func randomStartingConnection() -> Connection.ID? {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are the random<State>Connection functions mutating?

if let required = request.requiredEventLoop, required !== self.eventLoop {
throw Errors.connectionDoesNotFulfillEventLoopRequirement
}
if maxStreams < 1 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this state is real: a HTTP/2 connection is not allowed to have max streams less than 1.

if used == 1 {
self.state = .http2(.idle(maxConcurrentStreams: maxStreams, parked: false, lastIdle: .now()))
} else {
self.state = .http2(.inUse(maxConcurrentStreams: maxStreams, used: used))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't used be decremented here?

@fabianfett fabianfett requested a review from Lukasa September 8, 2021 09:06
@fabianfett fabianfett merged commit 05e570d into swift-server:main Sep 8, 2021
@fabianfett fabianfett deleted the ff-add-mockutils branch September 8, 2021 15:45
@fabianfett fabianfett added the semver/none No version bump required. label Sep 9, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
semver/none No version bump required.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants