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

Conversation

Projects
None yet
4 participants
@weissi
Copy link
Contributor

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 weissi:jw-signal-handling branch 2 times, most recently from 7d54102 to 5fac928 May 14, 2018

// 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")

This comment has been minimized.

@Lukasa

Lukasa May 14, 2018

Contributor

This text would probably need updating.

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

/// Removes all members from the

This comment has been minimized.

@Lukasa

Lukasa May 14, 2018

Contributor

Truncated comment.

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

public struct ChannelShouldQuiesceUserEvent {

This comment has been minimized.

@Lukasa

Lukasa May 14, 2018

Contributor

Needs docs.

if self.lifecycleState == .noLongerAcceptingEvents && self.state == .idle {
ctx.close(promise: nil)
}
default:

This comment has been minimized.

@Lukasa

Lukasa May 14, 2018

Contributor

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

This comment has been minimized.

@Lukasa

Lukasa May 14, 2018

Contributor

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:

This comment has been minimized.

@Lukasa

Lukasa May 14, 2018

Contributor

Is this over-broad? Should it just be when lifecycleState is quiescing?

This comment has been minimized.

@weissi

weissi May 14, 2018

Author Contributor

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

This comment has been minimized.

@weissi

weissi May 14, 2018

Author Contributor

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:

This comment has been minimized.

@Lukasa

Lukasa May 14, 2018

Contributor

No need for this default.

default:
ctx.write(data, promise: promise)
}
default:

This comment has been minimized.

@Lukasa

Lukasa May 14, 2018

Contributor

No need for this default.

@weissi weissi force-pushed the weissi:jw-signal-handling branch from 5fac928 to 6fe5a6e May 14, 2018

switch self {
case .idle:
self = .requestAndResponseEndPending
default:

This comment has been minimized.

@Lukasa

Lukasa May 15, 2018

Contributor

Might be better to explicitly enumerate all the states here?

This comment has been minimized.

@BasThomas

BasThomas May 15, 2018

Contributor

Or use an if case .idle = self, as it seems just one case is evaluated.

This comment has been minimized.

@weissi

weissi May 15, 2018

Author Contributor

@BasThomas we want to crash in the other cases; @Lukasa ok

@weissi weissi force-pushed the weissi:jw-signal-handling branch from 6fe5a6e to 5761eba May 15, 2018

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





This comment has been minimized.

@Lukasa

Lukasa May 15, 2018

Contributor

Bunch of whitespace down here.

This comment has been minimized.

@weissi

weissi May 15, 2018

Author Contributor

must have fallen asleep on my space key...

This comment has been minimized.

@weissi

weissi May 15, 2018

Author Contributor

fixed

@weissi weissi force-pushed the weissi:jw-signal-handling branch from 5761eba to 572c430 May 15, 2018

/// 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 {

This comment has been minimized.

@normanmaurer

normanmaurer May 15, 2018

Member

nit: consider using an enum ?

This comment has been minimized.

@weissi

weissi May 15, 2018

Author Contributor

@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 {

This comment has been minimized.

@normanmaurer

normanmaurer May 15, 2018

Member

Also do we just want to call it: ChannelShouldQuiesceEvent ?

This comment has been minimized.

@weissi

weissi May 15, 2018

Author Contributor

I followed TLSUserEvent but there's also ChannelEvent so there's prior art for both. I don't mind which one. @Lukasa ?

This comment has been minimized.

@Lukasa

Lukasa May 16, 2018

Contributor

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() {

This comment has been minimized.

@normanmaurer

normanmaurer May 15, 2018

Member

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

This comment has been minimized.

@weissi

weissi May 15, 2018

Author Contributor

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 {

This comment has been minimized.

@normanmaurer

This comment has been minimized.

@weissi

weissi May 15, 2018

Author Contributor

@normanmaurer ok, will do that tomorrow. Also the 'wrong' impl first?

This comment has been minimized.

@normanmaurer

normanmaurer May 16, 2018

Member

@weissi let me just merge my pr and then you can reuse the headers.isKeepAlive

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

//
//===----------------------------------------------------------------------===//
import XCTest

This comment has been minimized.

@weissi

weissi May 16, 2018

Author Contributor

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 this to the 1.7.0 milestone May 16, 2018

/// processing the request currently in flight.
public struct ChannelShouldQuiesceEvent {
public init() {
}

This comment has been minimized.

@Lukasa

Lukasa May 16, 2018

Contributor

This init is still here and probably not needed.

This comment has been minimized.

@weissi

weissi May 16, 2018

Author Contributor

@Lukasa no, we need a public init and the default one is an internal init

This comment has been minimized.

@Lukasa

Lukasa May 16, 2018

Contributor

Ah sorry, you're right.

@Lukasa

Lukasa approved these changes May 16, 2018

@Lukasa

This comment has been minimized.

Copy link
Contributor

Lukasa commented May 16, 2018

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

@weissi weissi force-pushed the weissi:jw-signal-handling branch 2 times, most recently from f6250e4 to efd1029 May 17, 2018

Channel quiescing support
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

@weissi weissi force-pushed the weissi:jw-signal-handling branch from efd1029 to a49ba57 May 17, 2018

@normanmaurer normanmaurer merged commit cea8476 into apple:master May 18, 2018

2 checks passed

pull request validation (4.0.3) Build finished.
Details
pull request validation (4.1) Build finished.
Details

@weissi weissi deleted the weissi:jw-signal-handling branch May 19, 2018

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment