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
Channel quiescing support #399
Conversation
7d54102
to
5fac928
Compare
// We got a response while still receiving a request, which we have to | ||
// wait for. | ||
self = .requestEndPending | ||
case .requestEndPending: | ||
case .requestEndPending, .idle: | ||
preconditionFailure("Received second response") |
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 text would probably need updating.
Sources/NIO/CircularBuffer.swift
Outdated
@@ -204,6 +204,13 @@ public struct CircularBuffer<E>: CustomStringConvertible, AppendableCollection { | |||
return nextIndex | |||
} | |||
|
|||
/// Removes all members from the |
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.
Truncated comment.
Sources/NIO/Channel.swift
Outdated
@@ -362,3 +362,8 @@ public enum ChannelEvent: Equatable { | |||
/// Output portion of the `Channel` was closed. | |||
case outputClosed | |||
} | |||
|
|||
public struct ChannelShouldQuiesceUserEvent { |
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.
Needs docs.
if self.lifecycleState == .noLongerAcceptingEvents && self.state == .idle { | ||
ctx.close(promise: nil) | ||
} | ||
default: |
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 default
here, we know what the third enum case is.
ctx.fireChannelRead(data) | ||
} | ||
|
||
private func deliverOneError(ctx: ChannelHandlerContext, error: Error) { | ||
if (self.state == .idle || self.state == .requestEndPending) && error is HTTPParserError { | ||
self.state = .responseEndPending |
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.
Would be nice to document this logic.
if case .end = self.unwrapOutboundIn(data) { | ||
|
||
switch self.unwrapOutboundIn(data) { | ||
case .head(var head) where self.lifecycleState != .acceptingEvents: |
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 over-broad? Should it just be when lifecycleState
is quiescing
?
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.
@Lukasa I think != .acceptingEvents
which is the same as saying .quiescing || .noLongerAcceptingEvents
is correct. The reason is that we should add a connection: close
header whenever we're quiescing. The .quescing
case however means that we're continuing to accept request parts (like .body
and .end
). .noLongerAcceptingEvents
means that we're quiescing and we have already seen the request .end
.
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 renamed the cases to .quiescingWaitingForRequestEnd
and .quiescingLastRequestEndReceived
which should make this clearer
ctx.write(data).then { | ||
ctx.close() | ||
}.cascade(promise: promise ?? ctx.channel.eventLoop.newPromise()) | ||
default: |
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 default.
default: | ||
ctx.write(data, promise: promise) | ||
} | ||
default: |
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 default.
5fac928
to
6fe5a6e
Compare
switch self { | ||
case .idle: | ||
self = .requestAndResponseEndPending | ||
default: |
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.
Might be better to explicitly enumerate all the states here?
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.
Or use an if case .idle = self
, as it seems just one case is evaluated.
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.
@BasThomas we want to crash in the other cases; @Lukasa ok
6fe5a6e
to
5761eba
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.
Bunch of whitespace down here.
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.
must have fallen asleep on my space key...
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.
fixed
5761eba
to
572c430
Compare
Sources/NIO/Channel.swift
Outdated
/// The action(s) that should be taken after receiving this event are both application and protocol dependent. If the | ||
/// protocol supports a notion of requests and responses, it might make sense to stop accepting new requests but finish | ||
/// processing the request currently in flight. | ||
public struct ChannelShouldQuiesceUserEvent { |
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: consider using an enum ?
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.
@normanmaurer we need to be able to instanciate that one. So I could do an enum but what would the cases be? I could only think of one case "please quiesce the channel" and I thought that's easier expressed by just using a type. But happy to change. We can't use ChannelEvent
because it's an enum
and extending it would be a major version break
Sources/NIO/Channel.swift
Outdated
/// The action(s) that should be taken after receiving this event are both application and protocol dependent. If the | ||
/// protocol supports a notion of requests and responses, it might make sense to stop accepting new requests but finish | ||
/// processing the request currently in flight. | ||
public struct ChannelShouldQuiesceUserEvent { |
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.
Also do we just want to call it: ChannelShouldQuiesceEvent
?
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 followed TLSUserEvent
but there's also ChannelEvent
so there's prior art for both. I don't mind which one. @Lukasa ?
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 think Event
is better, I think the TLSUserEvent
name wasn't great.
Sources/NIO/CircularBuffer.swift
Outdated
@@ -204,6 +204,13 @@ public struct CircularBuffer<E>: CustomStringConvertible, AppendableCollection { | |||
return nextIndex | |||
} | |||
|
|||
/// Removes all members from the circular buffer whist keeping the capacity. | |||
public mutating func removeAll() { |
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 swift in other places we have removeAll(keepingCapacity:)
. Maybe we should use the same method naming and behaviour ?
https://developer.apple.com/documentation/swift/array/1641473-removeall
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.
will do tomo
extension HTTPResponseHead { | ||
/// Whether this HTTP response is a keep-alive request: that is, whether the | ||
/// connection should remain open after the request is complete. | ||
public var isKeepAlive: 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 think we should implement it kind of the same way as here:
https://github.com/apple/swift-nio/pull/402/files#diff-9361bcb5608a311475f3a33eeadc3a3eR300
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.
@normanmaurer ok, will do that tomorrow. Also the 'wrong' impl first?
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 let me just merge my pr and then you can reuse the headers.isKeepAlive
c345489
to
23eedc9
Compare
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
import XCTest |
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.
note to @Lukasa / @normanmaurer : for a second i thought nextPowerOf2
had a bug but couldn't find the tests so added some
/// processing the request currently in flight. | ||
public struct ChannelShouldQuiesceEvent { | ||
public init() { | ||
} |
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 init is still here and probably not needed.
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.
@Lukasa no, we need a public init
and the default one is an internal init
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.
Ah sorry, you're right.
Gotta fix those tests up, but I'm happy. |
f6250e4
to
efd1029
Compare
Motivation: In certain cases it's useful to quiesce a channel instead of just closing them immediately for example when receiving a signal. This lays the groundwork by introducing the `ChannelShouldQuiesceUserEvent` user event that when received can be interpreted by a ChannelHandler in a protocol & application specific way. Some protocols support tear down and that would be a good place to initiate the tear down. Modifications: - introduce `ChannelShouldQuiesceUserEvent` - handle `ChannelShouldQuiesceUserEvent` in the `AcceptHandler` with closing the server socket - handle `ChannelShouldQuiesceUserEvent` in the `HTTPServerPipelineHandler` by only handling a already in-flight request and then no longer accepting input - added `CircularBuffer.removeAll` (& tests) - added tests for `nextPowerOf2()` Result: - handlers can now support quiescing
efd1029
to
a49ba57
Compare
Motivation:
In certain cases it's useful to quiesce a channel instead of just
closing them immediately for example when receiving a signal.
This lays the groundwork by introducing the
ChannelShouldQuiesceUserEvent
user event that when received can beinterpreted by a ChannelHandler in a protocol & application specific
way. Some protocols support tear down and that would be a good place to
initiate the tear down.
Modifications:
ChannelShouldQuiesceUserEvent
ChannelShouldQuiesceUserEvent
in theAcceptHandler
withclosing the server socket
ChannelShouldQuiesceUserEvent
in theHTTPServerPipelineHandler
by only handling a already in-flightrequest and then no longer accepting input
Result: