Skip to content

Commit 47e531d

Browse files
glbrnttMrMage
authored andcommitted
Always use the same event loop for a client connection (#562)
* Always use the same event loop for a client connection Motivation: It might be useful for other applications interacting with gRPC to know that a client connection will always use the same event loop, event if it reconnects. Modifications: Use the same event loop when attempting reconnections. Result: A connection will only ever use one event loop. * fix endif
1 parent edd9bf0 commit 47e531d

File tree

5 files changed

+152
-7
lines changed

5 files changed

+152
-7
lines changed

Package.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,8 @@ let package = Package(
3434
.package(url: "https://github.com/apple/swift-nio-http2.git", from: "1.5.0"),
3535
// TLS via SwiftNIO
3636
.package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.4.0"),
37-
// Support for Network.framework where possible. Note: from 1.0.2 the package
38-
// is essentially an empty import on platforms where it isn't supported.
39-
.package(url: "https://github.com/apple/swift-nio-transport-services.git", from: "1.0.2"),
37+
// Support for Network.framework where possible.
38+
.package(url: "https://github.com/apple/swift-nio-transport-services.git", from: "1.1.0"),
4039

4140
// Official SwiftProtobuf library, for [de]serializing data to send on the wire.
4241
.package(url: "https://github.com/apple/swift-protobuf.git", from: "1.5.0"),

Sources/GRPC/ClientConnection.swift

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ public class ClientConnection {
109109

110110
self.channel = ClientConnection.makeChannel(
111111
configuration: self.configuration,
112+
eventLoop: self.configuration.eventLoopGroup.next(),
112113
connectivity: self.connectivity,
113114
backoffIterator: self.configuration.connectionBackoff?.makeIterator(),
114115
logger: self.logger
@@ -230,6 +231,7 @@ extension ClientConnection {
230231
self.logger.debug("client connection channel closed, creating a new one")
231232
self.channel = ClientConnection.makeChannel(
232233
configuration: self.configuration,
234+
eventLoop: channel.eventLoop,
233235
connectivity: self.connectivity,
234236
backoffIterator: self.configuration.connectionBackoff?.makeIterator(),
235237
logger: self.logger
@@ -258,11 +260,13 @@ extension ClientConnection {
258260
/// `ConnectionBackoffIterator` is provided.
259261
///
260262
/// - Parameter configuration: The configuration to start the connection with.
263+
/// - Parameter eventLoop: The event loop to use for this connection.
261264
/// - Parameter connectivity: A connectivity state monitor.
262265
/// - Parameter backoffIterator: An `Iterator` for `ConnectionBackoff` providing a sequence of
263266
/// connection timeouts and backoff to use when attempting to create a connection.
264267
private class func makeChannel(
265268
configuration: Configuration,
269+
eventLoop: EventLoop,
266270
connectivity: ConnectivityStateMonitor,
267271
backoffIterator: ConnectionBackoffIterator?,
268272
logger: Logger
@@ -277,7 +281,7 @@ extension ClientConnection {
277281

278282
let bootstrap = self.makeBootstrap(
279283
configuration: configuration,
280-
group: configuration.eventLoopGroup,
284+
eventLoop: eventLoop,
281285
timeout: timeoutAndBackoff?.timeout,
282286
connectivityMonitor: connectivity,
283287
logger: logger
@@ -330,6 +334,7 @@ extension ClientConnection {
330334
return eventLoop.scheduleTask(in: .seconds(timeInterval: timeout)) {
331335
ClientConnection.makeChannel(
332336
configuration: configuration,
337+
eventLoop: eventLoop,
333338
connectivity: connectivity,
334339
backoffIterator: backoffIterator,
335340
logger: logger
@@ -345,12 +350,12 @@ extension ClientConnection {
345350
/// handlers detailed in the documentation for `ClientConnection`.
346351
///
347352
/// - Parameter configuration: The configuration to prepare the bootstrap with.
348-
/// - Parameter group: The `EventLoopGroup` to use for the bootstrap.
353+
/// - Parameter eventLoop: The `EventLoop` to use for the bootstrap.
349354
/// - Parameter timeout: The connection timeout in seconds.
350355
/// - Parameter connectivityMonitor: The connectivity state monitor for the created channel.
351356
private class func makeBootstrap(
352357
configuration: Configuration,
353-
group: EventLoopGroup,
358+
eventLoop: EventLoop,
354359
timeout: TimeInterval?,
355360
connectivityMonitor: ConnectivityStateMonitor,
356361
logger: Logger
@@ -367,7 +372,7 @@ extension ClientConnection {
367372
}
368373
}
369374

370-
let bootstrap = PlatformSupport.makeClientBootstrap(group: group)
375+
let bootstrap = PlatformSupport.makeClientBootstrap(group: eventLoop)
371376
// Enable SO_REUSEADDR and TCP_NODELAY.
372377
.channelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
373378
.channelOption(ChannelOptions.socket(IPPROTO_TCP, TCP_NODELAY), value: 1)
@@ -385,6 +390,7 @@ extension ClientConnection {
385390
logger.info("setting connect timeout to \(timeout) seconds")
386391
return bootstrap.connectTimeout(.seconds(timeInterval: timeout))
387392
} else {
393+
logger.info("no connect timeout provided")
388394
return bootstrap
389395
}
390396
}

Sources/GRPC/PlatformSupport.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,9 @@ public enum PlatformSupport {
165165
if let tsGroup = group as? NIOTSEventLoopGroup {
166166
logger.debug("Network.framework is available and the group is correctly typed, creating a NIOTSConnectionBootstrap")
167167
return NIOTSConnectionBootstrap(group: tsGroup)
168+
} else if let qosEventLoop = group as? QoSEventLoop {
169+
logger.debug("Network.framework is available and the group is correctly typed, creating a NIOTSConnectionBootstrap")
170+
return NIOTSConnectionBootstrap(group: qosEventLoop)
168171
}
169172
logger.debug("Network.framework is available but the group is not typed for NIOTS, falling back to ClientBootstrap")
170173
}
@@ -186,6 +189,9 @@ public enum PlatformSupport {
186189
if let tsGroup = group as? NIOTSEventLoopGroup {
187190
logger.debug("Network.framework is available and the group is correctly typed, creating a NIOTSListenerBootstrap")
188191
return NIOTSListenerBootstrap(group: tsGroup)
192+
} else if let qosEventLoop = group as? QoSEventLoop {
193+
logger.debug("Network.framework is available and the group is correctly typed, creating a NIOTSListenerBootstrap")
194+
return NIOTSListenerBootstrap(group: qosEventLoop)
189195
}
190196
logger.debug("Network.framework is available but the group is not typed for NIOTS, falling back to ServerBootstrap")
191197
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import Foundation
2+
import GRPC
3+
import NIO
4+
import NIOTransportServices
5+
import XCTest
6+
7+
class PlatformSupportTests: GRPCTestCase {
8+
var group: EventLoopGroup!
9+
10+
override func tearDown() {
11+
XCTAssertNoThrow(try self.group?.syncShutdownGracefully())
12+
}
13+
14+
func testMakeEventLoopGroupReturnsMultiThreadedGroupForPosix() {
15+
self.group = PlatformSupport.makeEventLoopGroup(
16+
loopCount: 1,
17+
networkPreference: .userDefined(.posix)
18+
)
19+
20+
XCTAssertTrue(self.group is MultiThreadedEventLoopGroup)
21+
}
22+
23+
func testMakeEventLoopGroupReturnsNIOTSGroupForNetworkFramework() {
24+
// If we don't have Network.framework then we can't test this.
25+
#if canImport(Network)
26+
guard #available(macOS 10.14, *) else { return }
27+
28+
self.group = PlatformSupport.makeEventLoopGroup(
29+
loopCount: 1,
30+
networkPreference: .userDefined(.networkFramework)
31+
)
32+
33+
XCTAssertTrue(self.group is NIOTSEventLoopGroup)
34+
#endif
35+
}
36+
37+
func testMakeClientBootstrapReturnsClientBootstrapForMultiThreadedGroup() {
38+
self.group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
39+
let bootstrap = PlatformSupport.makeClientBootstrap(group: self.group)
40+
XCTAssertTrue(bootstrap is ClientBootstrap)
41+
}
42+
43+
func testMakeClientBootstrapReturnsClientBootstrapForEventLoop() {
44+
self.group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
45+
let eventLoop = self.group.next()
46+
let bootstrap = PlatformSupport.makeClientBootstrap(group: eventLoop)
47+
XCTAssertTrue(bootstrap is ClientBootstrap)
48+
}
49+
50+
func testMakeClientBootstrapReturnsNIOTSConnectionBootstrapForNIOTSGroup() {
51+
// If we don't have Network.framework then we can't test this.
52+
#if canImport(Network)
53+
guard #available(macOS 10.14, *) else { return }
54+
55+
self.group = NIOTSEventLoopGroup(loopCount: 1)
56+
let bootstrap = PlatformSupport.makeClientBootstrap(group: self.group)
57+
XCTAssertTrue(bootstrap is NIOTSConnectionBootstrap)
58+
#endif
59+
}
60+
61+
func testMakeClientBootstrapReturnsNIOTSConnectionBootstrapForQoSEventLoop() {
62+
// If we don't have Network.framework then we can't test this.
63+
#if canImport(Network)
64+
guard #available(macOS 10.14, *) else { return }
65+
66+
self.group = NIOTSEventLoopGroup(loopCount: 1)
67+
68+
let eventLoop = self.group.next()
69+
XCTAssertTrue(eventLoop is QoSEventLoop)
70+
71+
let bootstrap = PlatformSupport.makeClientBootstrap(group: eventLoop)
72+
XCTAssertTrue(bootstrap is NIOTSConnectionBootstrap)
73+
#endif
74+
}
75+
76+
func testMakeServerBootstrapReturnsServerBootstrapForMultiThreadedGroup() {
77+
self.group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
78+
let bootstrap = PlatformSupport.makeServerBootstrap(group: self.group)
79+
XCTAssertTrue(bootstrap is ServerBootstrap)
80+
}
81+
82+
func testMakeServerBootstrapReturnsServerBootstrapForEventLoop() {
83+
self.group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
84+
85+
let eventLoop = self.group.next()
86+
let bootstrap = PlatformSupport.makeServerBootstrap(group: eventLoop)
87+
XCTAssertTrue(bootstrap is ServerBootstrap)
88+
}
89+
90+
func testMakeServerBootstrapReturnsNIOTSListenerBootstrapForNIOTSGroup() {
91+
// If we don't have Network.framework then we can't test this.
92+
#if canImport(Network)
93+
guard #available(macOS 10.14, *) else { return }
94+
95+
self.group = NIOTSEventLoopGroup(loopCount: 1)
96+
let bootstrap = PlatformSupport.makeServerBootstrap(group: self.group)
97+
XCTAssertTrue(bootstrap is NIOTSListenerBootstrap)
98+
#endif
99+
}
100+
101+
func testMakeServerBootstrapReturnsNIOTSListenerBootstrapForQoSEventLoop() {
102+
// If we don't have Network.framework then we can't test this.
103+
#if canImport(Network)
104+
guard #available(macOS 10.14, *) else { return }
105+
106+
self.group = NIOTSEventLoopGroup(loopCount: 1)
107+
108+
let eventLoop = self.group.next()
109+
XCTAssertTrue(eventLoop is QoSEventLoop)
110+
111+
let bootstrap = PlatformSupport.makeServerBootstrap(group: eventLoop)
112+
XCTAssertTrue(bootstrap is NIOTSListenerBootstrap)
113+
#endif
114+
}
115+
}

Tests/GRPCTests/XCTestManifests.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,24 @@ extension LengthPrefixedMessageReaderTests {
384384
]
385385
}
386386

387+
extension PlatformSupportTests {
388+
// DO NOT MODIFY: This is autogenerated, use:
389+
// `swift test --generate-linuxmain`
390+
// to regenerate.
391+
static let __allTests__PlatformSupportTests = [
392+
("testMakeClientBootstrapReturnsClientBootstrapForEventLoop", testMakeClientBootstrapReturnsClientBootstrapForEventLoop),
393+
("testMakeClientBootstrapReturnsClientBootstrapForMultiThreadedGroup", testMakeClientBootstrapReturnsClientBootstrapForMultiThreadedGroup),
394+
("testMakeClientBootstrapReturnsNIOTSConnectionBootstrapForNIOTSGroup", testMakeClientBootstrapReturnsNIOTSConnectionBootstrapForNIOTSGroup),
395+
("testMakeClientBootstrapReturnsNIOTSConnectionBootstrapForQoSEventLoop", testMakeClientBootstrapReturnsNIOTSConnectionBootstrapForQoSEventLoop),
396+
("testMakeEventLoopGroupReturnsMultiThreadedGroupForPosix", testMakeEventLoopGroupReturnsMultiThreadedGroupForPosix),
397+
("testMakeEventLoopGroupReturnsNIOTSGroupForNetworkFramework", testMakeEventLoopGroupReturnsNIOTSGroupForNetworkFramework),
398+
("testMakeServerBootstrapReturnsNIOTSListenerBootstrapForNIOTSGroup", testMakeServerBootstrapReturnsNIOTSListenerBootstrapForNIOTSGroup),
399+
("testMakeServerBootstrapReturnsNIOTSListenerBootstrapForQoSEventLoop", testMakeServerBootstrapReturnsNIOTSListenerBootstrapForQoSEventLoop),
400+
("testMakeServerBootstrapReturnsServerBootstrapForEventLoop", testMakeServerBootstrapReturnsServerBootstrapForEventLoop),
401+
("testMakeServerBootstrapReturnsServerBootstrapForMultiThreadedGroup", testMakeServerBootstrapReturnsServerBootstrapForMultiThreadedGroup),
402+
]
403+
}
404+
387405
extension ServerDelayedThrowingTests {
388406
// DO NOT MODIFY: This is autogenerated, use:
389407
// `swift test --generate-linuxmain`
@@ -468,6 +486,7 @@ public func __allTests() -> [XCTestCaseEntry] {
468486
testCase(HTTP1ToRawGRPCServerCodecTests.__allTests__HTTP1ToRawGRPCServerCodecTests),
469487
testCase(ImmediatelyFailingProviderTests.__allTests__ImmediatelyFailingProviderTests),
470488
testCase(LengthPrefixedMessageReaderTests.__allTests__LengthPrefixedMessageReaderTests),
489+
testCase(PlatformSupportTests.__allTests__PlatformSupportTests),
471490
testCase(ServerDelayedThrowingTests.__allTests__ServerDelayedThrowingTests),
472491
testCase(ServerErrorTransformingTests.__allTests__ServerErrorTransformingTests),
473492
testCase(ServerThrowingTests.__allTests__ServerThrowingTests),

0 commit comments

Comments
 (0)