Skip to content
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

Merged
merged 1 commit into from May 18, 2018
Merged

Conversation

weissi
Copy link
Member

@weissi weissi commented May 9, 2018

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

Result:

  • handlers can now support quiescing

@weissi weissi force-pushed the jw-signal-handling branch 2 times, most recently from 7d54102 to 5fac928 Compare May 14, 2018 15:12
// 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")
Copy link
Contributor

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.

@@ -204,6 +204,13 @@ public struct CircularBuffer<E>: CustomStringConvertible, AppendableCollection {
return nextIndex
}

/// Removes all members from the
Copy link
Contributor

Choose a reason for hiding this comment

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

Truncated comment.

@@ -362,3 +362,8 @@ public enum ChannelEvent: Equatable {
/// Output portion of the `Channel` was closed.
case outputClosed
}

public struct ChannelShouldQuiesceUserEvent {
Copy link
Contributor

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:
Copy link
Contributor

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
Copy link
Contributor

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:
Copy link
Contributor

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?

Copy link
Member Author

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.

Copy link
Member Author

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:
Copy link
Contributor

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:
Copy link
Contributor

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.

switch self {
case .idle:
self = .requestAndResponseEndPending
default:
Copy link
Contributor

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?

Copy link
Contributor

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.

Copy link
Member Author

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

@weissi weissi changed the title [DO NOT MERGE] WIP: signal handling Channel quiescing support May 15, 2018




Copy link
Contributor

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.

Copy link
Member Author

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...

Copy link
Member Author

Choose a reason for hiding this comment

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

fixed

/// 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 {
Copy link
Member

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 ?

Copy link
Member Author

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

/// 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 {
Copy link
Member

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 ?

Copy link
Member Author

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 ?

Copy link
Contributor

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.

@@ -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() {
Copy link
Member

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

Copy link
Member Author

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 {
Copy link
Member

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

Copy link
Member Author

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?

Copy link
Member

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

@weissi weissi force-pushed the jw-signal-handling branch 2 times, most recently from c345489 to 23eedc9 Compare May 16, 2018 14:05
//
//===----------------------------------------------------------------------===//

import XCTest
Copy link
Member Author

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

@weissi weissi added the 🔼 needs-minor-version-bump For PRs that when merged cause a bump of the minor version, ie. 1.x.0 -> 1.(x+1).0 label May 16, 2018
@weissi weissi added this to the 1.7.0 milestone May 16, 2018
/// processing the request currently in flight.
public struct ChannelShouldQuiesceEvent {
public init() {
}
Copy link
Contributor

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.

Copy link
Member Author

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

Copy link
Contributor

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.

@Lukasa
Copy link
Contributor

Lukasa commented May 16, 2018

Gotta fix those tests up, but I'm happy.

@weissi weissi force-pushed the jw-signal-handling branch 2 times, most recently from f6250e4 to efd1029 Compare May 17, 2018 13:14
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
@normanmaurer normanmaurer merged commit cea8476 into apple:master May 18, 2018
@weissi weissi deleted the jw-signal-handling branch May 19, 2018 11:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🔼 needs-minor-version-bump For PRs that when merged cause a bump of the minor version, ie. 1.x.0 -> 1.(x+1).0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants