Skip to content

Commit 2fd06dc

Browse files
slavabulgakovMrMage
authored andcommitted
Fix crash during Channel with subscription is destroying (#328)
* Fix crash during Channel destroying * Add channel crash test case * Fix review issues * Fix review issues * Fix review issues
1 parent 19bd5c6 commit 2fd06dc

File tree

2 files changed

+54
-0
lines changed

2 files changed

+54
-0
lines changed

Sources/SwiftGRPC/Core/Channel.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ private extension Channel {
132132
private let underlyingCompletionQueue: UnsafeMutableRawPointer
133133
private let callback: (ConnectivityState) -> Void
134134
private var lastState: ConnectivityState
135+
private var hasBeenShutdown = false
136+
private let stateMutex: Mutex = Mutex()
135137

136138
init(underlyingChannel: UnsafeMutableRawPointer, currentState: ConnectivityState, callback: @escaping (ConnectivityState) -> ()) {
137139
self.underlyingChannel = underlyingChannel
@@ -151,11 +153,19 @@ private extension Channel {
151153

152154
spinloopThreadQueue.async {
153155
while true {
156+
guard (self.stateMutex.synchronize{ !self.hasBeenShutdown }) else {
157+
return
158+
}
159+
154160
guard let underlyingState = self.lastState.underlyingState else { return }
155161

156162
let deadline: TimeInterval = 0.2
157163
cgrpc_channel_watch_connectivity_state(self.underlyingChannel, self.underlyingCompletionQueue, underlyingState, deadline, nil)
158164
let event = self.completionQueue.wait(timeout: deadline)
165+
166+
guard (self.stateMutex.synchronize{ !self.hasBeenShutdown }) else {
167+
return
168+
}
159169

160170
switch event.type {
161171
case .complete:
@@ -178,6 +188,9 @@ private extension Channel {
178188
}
179189

180190
func shutdown() {
191+
stateMutex.synchronize {
192+
hasBeenShutdown = true
193+
}
181194
completionQueue.shutdown()
182195
}
183196
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2018, gRPC Authors All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
import XCTest
17+
@testable import SwiftGRPC
18+
19+
class ChannelCrashTests: BasicEchoTestCase {
20+
override var defaultTimeout: TimeInterval { return 0.4 }
21+
22+
func testDanglingConnectivityObserversDontCrash() {
23+
let completionHandlerExpectation = expectation(description: "completion handler called")
24+
25+
client?.channel.subscribe { connectivityState in
26+
print("ConnectivityState: \(connectivityState)")
27+
}
28+
29+
let request = Echo_EchoRequest(text: "foo bar baz foo bar baz")
30+
_ = try! client!.expand(request) { callResult in
31+
print("callResult.statusCode: \(callResult.statusCode)")
32+
completionHandlerExpectation.fulfill()
33+
}
34+
35+
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300)) {
36+
self.client = nil // Deallocating the client
37+
}
38+
39+
waitForExpectations(timeout: 0.5)
40+
}
41+
}

0 commit comments

Comments
 (0)