Skip to content

Commit e4f1897

Browse files
authored
Merge branch 'main' into ff-fix-benchmark-non-failure
2 parents bbc9b45 + c329d1e commit e4f1897

File tree

3 files changed

+121
-24
lines changed

3 files changed

+121
-24
lines changed

Sources/NIOPosix/BaseSocketChannel.swift

Lines changed: 73 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,14 @@ private struct SocketChannelLifecycleManager {
2424
case fresh
2525
case preRegistered // register() has been run but the selector doesn't know about it yet
2626
case fullyRegistered // fully registered, ie. the selector knows about it
27-
case activated
27+
case preActivation // activated, but we have not fired channelActivated yet
28+
case fullyActivated // fully activated, i.e., the signal has been fired
2829
case closed
2930
}
3031

3132
private enum Event {
32-
case activate
33+
case beginActivation
34+
case finishActivation
3335
case beginRegistration
3436
case finishRegistration
3537
case close
@@ -52,9 +54,11 @@ private struct SocketChannelLifecycleManager {
5254
didSet {
5355
self.eventLoop.assertInEventLoop()
5456
switch (oldValue, self.currentState) {
55-
case (_, .activated):
57+
case (_, .preActivation):
5658
self.isActiveAtomic.store(true, ordering: .relaxed)
57-
case (.activated, _):
59+
case (.preActivation, .fullyActivated):
60+
() // Stay active
61+
case (.preActivation, _), (.fullyActivated, _):
5862
self.isActiveAtomic.store(false, ordering: .relaxed)
5963
default:
6064
()
@@ -99,8 +103,13 @@ private struct SocketChannelLifecycleManager {
99103

100104
// we need to return a closure here and to not suffer from a potential allocation for that this must be inlined
101105
@inline(__always)
102-
internal mutating func activate() -> ((EventLoopPromise<Void>?, ChannelPipeline) -> Void) {
103-
self.moveState(event: .activate)
106+
internal mutating func beginActivation() -> ((EventLoopPromise<Void>?, ChannelPipeline) -> Void) {
107+
self.moveState(event: .beginActivation)
108+
}
109+
110+
@inline(__always)
111+
internal mutating func finishActivation() -> ((EventLoopPromise<Void>?, ChannelPipeline) -> Void) {
112+
self.moveState(event: .finishActivation)
104113
}
105114

106115
// MARK: private API
@@ -132,11 +141,10 @@ private struct SocketChannelLifecycleManager {
132141
}
133142

134143
// origin: .fullyRegistered
135-
case (.fullyRegistered, .activate):
136-
self.currentState = .activated
144+
case (.fullyRegistered, .beginActivation):
145+
self.currentState = .preActivation
137146
return { promise, pipeline in
138147
promise?.succeed(())
139-
pipeline.syncOperations.fireChannelActive()
140148
}
141149

142150
// origin: .preRegistered || .fullyRegistered
@@ -147,29 +155,61 @@ private struct SocketChannelLifecycleManager {
147155
pipeline.syncOperations.fireChannelUnregistered()
148156
}
149157

150-
// origin: .activated
151-
case (.activated, .close):
158+
// origin: .preActivation
159+
case (.preActivation, .finishActivation):
160+
self.currentState = .fullyActivated
161+
return { promise, pipeline in
162+
assert(promise == nil)
163+
pipeline.syncOperations.fireChannelActive()
164+
}
165+
166+
case (.preActivation, .close):
167+
self.currentState = .closed
168+
return { promise, pipeline in
169+
promise?.succeed(())
170+
// Don't fire channelInactive here.
171+
pipeline.syncOperations.fireChannelUnregistered()
172+
}
173+
174+
case (.preActivation, .beginActivation) where self.supportsReconnect:
175+
return { promise, pipeline in
176+
promise?.succeed(())
177+
}
178+
179+
// origin: .fullyActivated
180+
case (.fullyActivated, .close):
152181
self.currentState = .closed
153182
return { promise, pipeline in
154183
promise?.succeed(())
155184
pipeline.syncOperations.fireChannelInactive()
156185
pipeline.syncOperations.fireChannelUnregistered()
157186
}
158187

159-
// origin: .activated
160-
case (.activated, .activate) where self.supportsReconnect:
188+
case (.fullyActivated, .beginActivation) where self.supportsReconnect:
161189
return { promise, pipeline in
162190
promise?.succeed(())
163191
}
164192

193+
case (.fullyActivated, .finishActivation) where self.supportsReconnect:
194+
return { promise, pipeline in
195+
assert(promise == nil)
196+
}
197+
165198
// bad transitions
166-
case (.fresh, .activate), // should go through .registered first
167-
(.preRegistered, .activate), // need to first be fully registered
199+
case (.fresh, .beginActivation), // should go through .registered first
200+
(.fresh, .finishActivation), // should go through .registerd first
201+
(.preRegistered, .beginActivation), // need to first be fully registered
202+
(.preRegistered, .finishActivation), // need to first be fully registered
168203
(.preRegistered, .beginRegistration), // already registered
169204
(.fullyRegistered, .beginRegistration), // already registered
170-
(.activated, .activate), // already activated
171-
(.activated, .beginRegistration), // already fully registered (and activated)
172-
(.activated, .finishRegistration), // already fully registered (and activated)
205+
(.fullyRegistered, .finishActivation), // need to activate first
206+
(.preActivation, .beginActivation), // already activated
207+
(.preActivation, .beginRegistration), // already fully registered (and activated)
208+
(.preActivation, .finishRegistration), // already fully registered (and activated)
209+
(.fullyActivated, .beginActivation), // need to activate first
210+
(.fullyActivated, .beginRegistration), // already fully registered
211+
(.fullyActivated, .finishRegistration), // already fully registered
212+
(.fullyActivated, .finishActivation), // already signaled
173213
(.fullyRegistered, .finishRegistration), // already fully registered
174214
(.fresh, .finishRegistration), // need to register lazily first
175215
(.closed, _): // already closed
@@ -184,15 +224,20 @@ private struct SocketChannelLifecycleManager {
184224
// MARK: convenience properties
185225
internal var isActive: Bool {
186226
self.eventLoop.assertInEventLoop()
187-
return self.currentState == .activated
227+
switch self.currentState {
228+
case .preActivation, .fullyActivated:
229+
return true
230+
case .fresh, .preRegistered, .fullyRegistered, .closed:
231+
return false
232+
}
188233
}
189234

190235
internal var isPreRegistered: Bool {
191236
self.eventLoop.assertInEventLoop()
192237
switch self.currentState {
193238
case .fresh, .closed:
194239
return false
195-
case .preRegistered, .fullyRegistered, .activated:
240+
case .preRegistered, .fullyRegistered, .preActivation, .fullyActivated:
196241
return true
197242
}
198243
}
@@ -202,7 +247,7 @@ private struct SocketChannelLifecycleManager {
202247
switch self.currentState {
203248
case .fresh, .closed, .preRegistered:
204249
return false
205-
case .fullyRegistered, .activated:
250+
case .fullyRegistered, .preActivation, .fullyActivated:
206251
return true
207252
}
208253
}
@@ -1366,7 +1411,13 @@ class BaseSocketChannel<SocketType: BaseSocketProtocol>: SelectableChannel, Chan
13661411
return
13671412
}
13681413
}
1369-
self.lifecycleManager.activate()(promise, self.pipeline)
1414+
self.lifecycleManager.beginActivation()(promise, self.pipeline)
1415+
guard self.lifecycleManager.isActive else {
1416+
// the channel got closed in the promise succeed closure
1417+
return
1418+
}
1419+
// if the channel wasn't closed, continue to fully activate
1420+
self.lifecycleManager.finishActivation()(nil, self.pipeline)
13701421
guard self.lifecycleManager.isOpen else {
13711422
// in the user callout for `channelActive` the channel got closed.
13721423
return

Tests/NIOPosixTests/ChannelNotificationTest.swift

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,4 +534,50 @@ class ChannelNotificationTest: XCTestCase {
534534
XCTAssertNoThrow(try serverChannel.close().wait())
535535
XCTAssertNoThrow(try serverChannel.closeFuture.wait())
536536
}
537+
538+
func testNoActiveInactiveNotificationsWhenDirectlyClosed() throws {
539+
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
540+
defer {
541+
XCTAssertNoThrow(try group.syncShutdownGracefully())
542+
}
543+
544+
class OrderVerificationHandler: ChannelInboundHandler {
545+
typealias InboundIn = ByteBuffer
546+
547+
init() {
548+
// nop
549+
}
550+
551+
public func channelActive(context: ChannelHandlerContext) {
552+
XCTFail("There should be no events when the channel is closed directly.")
553+
}
554+
555+
public func channelInactive(context: ChannelHandlerContext) {
556+
XCTFail("There should be no events when the channel is closed directly.")
557+
}
558+
}
559+
560+
let serverChannel = try assertNoThrowWithValue(
561+
ServerBootstrap(group: group)
562+
.serverChannelOption(.socketOption(.so_reuseaddr), value: 1)
563+
.bind(host: "127.0.0.1", port: 0).wait()
564+
)
565+
566+
let connectFuture = ClientBootstrap(group: group)
567+
.channelInitializer { channel in
568+
channel.eventLoop.makeCompletedFuture {
569+
try channel.pipeline.syncOperations.addHandler(OrderVerificationHandler())
570+
}
571+
}
572+
.connect(to: serverChannel.localAddress!)
573+
574+
// Close immediately in the promise callback. The channel must still become active before it becomes inactive!
575+
let closeFuture = connectFuture.flatMap { channel in
576+
channel.close()
577+
}
578+
579+
XCTAssertNoThrow(try closeFuture.wait())
580+
581+
XCTAssertNoThrow(try serverChannel.close().wait())
582+
}
537583
}

scripts/check-cxx-interop-compatibility.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ fatal() { error "$@"; exit 1; }
2222
log "Checking for Cxx interoperability compatibility..."
2323

2424
source_dir=$(pwd)
25-
working_dir=$(mktemp -d)
25+
working_dir=$(mktemp -d "/tmp/tmp_swift_package_XXXXXXXXXX")
2626
project_name=$(basename "$working_dir")
27-
source_file=Sources/$project_name/$(echo "$project_name" | tr . _).swift
27+
source_file=Sources/$project_name/$project_name.swift
2828
library_products=$( swift package dump-package | jq -r '.products[] | select(.type.library != null) | .name')
2929
package_name=$(swift package dump-package | jq -r '.name')
3030

0 commit comments

Comments
 (0)