-
Notifications
You must be signed in to change notification settings - Fork 645
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
Reuse detected keepalive of http_parser as long as possible #299
Conversation
Also @tanner0101 This gives some perf win in my benchmarks. Let me know what you think about this idea @tanner0101 @weissi @Lukasa |
Sources/NIOHTTP1/HTTPTypes.swift
Outdated
fileprivate enum HTTPHeadersModificationState { | ||
case modifiedNonContinuous | ||
case modified | ||
case notmodified |
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.
Why did you choose for notmodified
over notModified
?
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.
no idea.. let me fix :)
Sources/NIOHTTP1/HTTPTypes.swift
Outdated
switch newState { | ||
case .modified where self.state == .notmodified: | ||
self.state = newState | ||
case .modifiedNonContinuous: |
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 to not group this with the above case
?
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.
good idea :)
4362e38
to
3e6ecc0
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.
So I generally think this is a good idea. I did wonder whether we should also check headers being added/removed to see if they modify the state, but ultimately I don't think it's worthwhile.
Sources/NIOHTTP1/HTTPTypes.swift
Outdated
fatalError("Should never be called with \(newState)") | ||
default: | ||
break | ||
} |
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 tend to think this kind of method is nicer on the enum. Also no need to say "ifNeeded" in the name.
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.
In fact, it may be nicer just to have functions that explain what they are, e.g. state.appendedHeader()
and state.removedHeader()
. Then you can have some computed vars that use the right names, e.g state.continuous
and state.wasModified
.
3e6ecc0
to
9fd09f5
Compare
@Lukasa addressed... good idea |
Sources/NIOHTTP1/HTTPTypes.swift
Outdated
|
||
mutating func addedHeader() { | ||
switch self { | ||
case .notModified: |
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 about explicitly stating an if-else?
if case .notModified = self {
self = .modified
}
without the (then unnecessary) return
?
9fd09f5
to
7c150b0
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.
Some minor nits.
Sources/NIOHTTP1/HTTPTypes.swift
Outdated
@@ -38,13 +38,17 @@ public struct HTTPRequestHead: Equatable { | |||
/// The header fields for this HTTP request. | |||
public var headers: HTTPHeaders | |||
|
|||
/// The keepAlive that may be parsed by http_parser before. This is only safe to be used as long as | |||
/// the user did not modify the headers yet. |
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's update this comment slightly:
/// The keepAlive value that was detected by http_parser. This can only be used safely
/// as long as the user has not made modifications to this header block.
Sources/NIOHTTP1/HTTPTypes.swift
Outdated
@@ -202,6 +211,31 @@ private extension UInt8 { | |||
} | |||
} | |||
|
|||
fileprivate enum HTTPHeadersModificationState { |
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.
Mind adding some quick doc comments here?
Sources/NIOHTTP1/HTTPTypes.swift
Outdated
} | ||
|
||
mutating func removedHeader() { | ||
if self != .modifiedNonContinuous { |
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.
Why doesn't this use if case
?
7c150b0
to
a3923da
Compare
@Lukasa addressed |
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'd prefer to have the state tracking in one place and not to store a potentially wrong keepAlive
value
Sources/NIOHTTP1/HTTPTypes.swift
Outdated
case modifiedNonContinuous | ||
/// Was modified and the underlying storage is continous. | ||
case modified | ||
|
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: why a space here?
Sources/NIOHTTP1/HTTPTypes.swift
Outdated
@@ -38,13 +38,17 @@ public struct HTTPRequestHead: Equatable { | |||
/// The header fields for this HTTP request. | |||
public var headers: HTTPHeaders | |||
|
|||
/// The keepAlive value that was detected by http_parser. This can only be used safely | |||
/// as long as the user has not made modifications to this header block. | |||
private let keepAlive: Bool? |
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 don't really like having a state variable here that is only valid when also consulting another variable. I'd say the whole state should live in the headers. Couldn't we architect it this way: We allow (as an internal
constructor) to pre-seed the headers with a keep alive state. Whenever the headers change, we invalidate that.
So the keep-alive state in the headers could be one of the following:
keepAlive
: no header scan needed, I know I have aConnection: keep-alive
headerclose
: no header scan needed, I know I have aConnection: close
headerdefault
: no header scan needed, I know I had neitherConnection: keep-alive
norConnection: close
needsScan
: header scan needed
so internally we could pre-seed it with either keepAlive
, close
, or default
, depending on what http_parser tells us. Then on any modification we change it to needsScan
.
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.
@weissi sounds good... will do later today :)
a3923da
to
90ef967
Compare
Sources/NIOHTTP1/HTTPTypes.swift
Outdated
// We know we should close the connection. | ||
case close | ||
// We need to scan the headers to find out if keep alive is used or not | ||
case needScan(continuous: Bool) |
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.
Why is there a continuous flag here? You have a HTTPHeadersModificationState
that tracks this.
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.
Because I forgot to remove the modification state... it’s all tracked via this one now
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.
ok removed
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 don't like this much. I'd rather this bool was moved out of this enum: it just has nothing to do with what this enum is tracking.
If we do that, we can then name the third case of this enum unknown
, and then the implementation of HTTPHeaders.isKeepAlive
gets a lot nicer because it can just return .unknown
when it doesn't know the answer instead of being passed the HTTP version.
90ef967
to
ab4fb9b
Compare
@swift-nio-bot test this please |
Sources/NIOHTTP1/HTTPTypes.swift
Outdated
// We know we should close the connection. | ||
case close | ||
// We need to scan the headers to find out if keep alive is used or not | ||
case needScan(continuous: Bool) |
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 don't like this much. I'd rather this bool was moved out of this enum: it just has nothing to do with what this enum is tracking.
If we do that, we can then name the third case of this enum unknown
, and then the implementation of HTTPHeaders.isKeepAlive
gets a lot nicer because it can just return .unknown
when it doesn't know the answer instead of being passed the HTTP version.
ab4fb9b
to
0561283
Compare
@normanmaurer ping, what’s up with this friend? |
@weissi I need to investigate as it actually resulted in a slowdown after rebasing |
@normanmaurer you probably want to use the isolated (no network) perf test to verify that. Otherwise there’ll be too much noise I suspect |
@swift-nio-bot test this please |
0561283
to
39ed307
Compare
Alright updated please take a look again @weissi @Lukasa Before:
with this pr:
|
And @tanner0101 |
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.
Some minor nits, and it'd be nice to have some testing of these behaviours, but otherwise fine.
Sources/NIOHTTP1/HTTPDecoder.swift
Outdated
@@ -15,6 +15,14 @@ | |||
import NIO | |||
import CNIOHTTPParser | |||
|
|||
private extension UnsafeMutablePointer where Pointee == http_parser { | |||
|
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.
No need for this whitespace.
Sources/NIOHTTP1/HTTPTypes.swift
Outdated
private let connectionUtf8 = "connection".utf8 | ||
|
||
|
||
// Keep state of keep alive state. |
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: keep track, not keep state.
@Lukasa yep will add some tests just to ensure we correctly set it to "unknown" when we add / remove connection header. |
39ed307
to
a15b13f
Compare
Motivation: To detect if keepalive is used we need to search the headers (if HTTP/1.1 is used). This may be expensive depending how many headers are present. http_parser itself detects already if keep alive should be used and so we can re-use this as long as the user did not modify the headers. Modifications: Reuse keepalive parsed by http_parser as long as the headers were not modified. Result: Less overhead as long as the headers are not modified.
a15b13f
to
ec97a92
Compare
@Lukasa added some tests. |
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.
case .keepAlive: | ||
return true | ||
case .unknown: | ||
guard let connection = self["connection"].first?.lowercased() else { |
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.
whilst you're working on this: worth fixing this implementation of follow-up?
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 me do a follow up
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.
thanks! LGTM, just one question in there,.
Motivation:
To detect if keepalive is used we need to search the headers (if HTTP/1.1 is used). This may be expensive depending how many headers are present. http_parser itself detects already if keep alive should be used and so we can re-use this as long as the user did not modify the headers.
Modifications:
Reuse keepalive parsed by http_parser as long as the headers were not modified.
Result:
Less overhead as long as the headers are not modified.