-
Notifications
You must be signed in to change notification settings - Fork 420
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
Implement connectivity state property and observers #196
Changes from 2 commits
0bd14d8
76faf09
255ce88
4e94ef4
0891482
e853032
4ca615a
ad6e022
0d25ef8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -103,3 +103,8 @@ cgrpc_completion_queue *cgrpc_channel_completion_queue(cgrpc_channel *channel) { | |
grpc_connectivity_state cgrpc_channel_check_connectivity_state(cgrpc_channel *channel, int try_to_connect) { | ||
return grpc_channel_check_connectivity_state(channel->channel, try_to_connect); | ||
} | ||
|
||
void cgrpc_channel_watch_connectivity_state(cgrpc_channel *channel, cgrpc_completion_queue * completion_queue, grpc_connectivity_state last_observed_state, double deadline, void *tag) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Please remove the space after the asterisk. |
||
gpr_timespec deadline_seconds = cgrpc_deadline_in_seconds_from_now(deadline); | ||
return grpc_channel_watch_connectivity_state(channel->channel, last_observed_state, deadline_seconds, completion_queue, tag); | ||
} |
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -15,6 +15,7 @@ | |||
*/ | ||||
#if SWIFT_PACKAGE | ||||
import CgRPC | ||||
import Dispatch | ||||
#endif | ||||
import Foundation | ||||
|
||||
|
@@ -31,10 +32,9 @@ public class Channel { | |||
|
||||
/// Default host to use for new calls | ||||
public var host: String | ||||
|
||||
public var connectivityState: ConnectivityState? { | ||||
return ConnectivityState.fromCEnum(cgrpc_channel_check_connectivity_state(underlyingChannel, 0)) | ||||
} | ||||
|
||||
/// Connectivity state observers | ||||
private var observers: [ConnectivityObserver] = [] | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: rename this to |
||||
|
||||
/// Initializes a gRPC channel | ||||
/// | ||||
|
@@ -81,4 +81,145 @@ public class Channel { | |||
let underlyingCall = cgrpc_channel_create_call(underlyingChannel, method, host, timeout)! | ||||
return Call(underlyingCall: underlyingCall, owned: true, completionQueue: completionQueue) | ||||
} | ||||
|
||||
public func connectivityState(tryToConnect: Bool = false) -> ConnectivityState { | ||||
return ConnectivityState.connectivityState(cgrpc_channel_check_connectivity_state(underlyingChannel, tryToConnect ? 1 : 0)) | ||||
} | ||||
|
||||
public func subscribe(sourceState: ConnectivityState, tryToConnect: Bool = false, callback: @escaping (ConnectivityState) -> ()) { | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would suggest removing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would you mind adding a comment to this method, especially to explain what |
||||
var observer = observers.first(where: { $0.state == sourceState }) | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd suggest using a trailing closure here. |
||||
|
||||
if observer == nil { | ||||
let newObserver = ConnectivityObserver(state: sourceState, underlyingChannel: underlyingChannel, tryToConnect: tryToConnect) | ||||
observers.append(newObserver) | ||||
observer = newObserver | ||||
} | ||||
|
||||
observer?.callbacks.append(callback) | ||||
observer?.polling = true | ||||
} | ||||
} | ||||
|
||||
private extension Channel { | ||||
class ConnectivityObserver: Equatable { | ||||
let state: ConnectivityState | ||||
let queue: CompletionQueue | ||||
let underlyingChannel: UnsafeMutableRawPointer | ||||
let underlyingCompletionQueue: UnsafeMutableRawPointer | ||||
private(set) var tryToConnect: Bool | ||||
var callbacks: [(ConnectivityState) -> ()] = [] | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Access to |
||||
private var lastState: ConnectivityState | ||||
|
||||
var polling: Bool = false { | ||||
didSet { | ||||
if polling == true && oldValue == false { | ||||
run() | ||||
} | ||||
} | ||||
} | ||||
|
||||
init(state: ConnectivityState, underlyingChannel: UnsafeMutableRawPointer, tryToConnect: Bool) { | ||||
self.state = state | ||||
self.underlyingChannel = underlyingChannel | ||||
self.tryToConnect = tryToConnect | ||||
self.underlyingCompletionQueue = cgrpc_completion_queue_create_for_next() | ||||
self.queue = CompletionQueue(underlyingCompletionQueue: self.underlyingCompletionQueue, name: "Connectivity State") | ||||
self.lastState = ConnectivityState.connectivityState(cgrpc_channel_check_connectivity_state(self.underlyingChannel, 0)) | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: I would add a
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||||
} | ||||
|
||||
deinit { | ||||
queue.shutdown() | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks to me like this will never get deallocated, as you are acquiring a strong reference to |
||||
} | ||||
|
||||
private func run() { | ||||
DispatchQueue.global().async { [weak self] in | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please use the thread-spawning pattern now used in e.g.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||||
guard let `self` = self, let underlyingState = self.lastState.underlyingState else { return } | ||||
|
||||
while self.polling { | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Replace There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Got rid of |
||||
guard !self.callbacks.isEmpty && !self.tryToConnect else { | ||||
self.polling = false | ||||
break | ||||
} | ||||
|
||||
defer { self.tryToConnect = false } | ||||
|
||||
let deadline: TimeInterval = 0.2 | ||||
cgrpc_channel_watch_connectivity_state(self.underlyingChannel, self.underlyingCompletionQueue, underlyingState, deadline, nil) | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't |
||||
let event = self.queue.wait(timeout: deadline) | ||||
|
||||
if event.success == 1 || self.tryToConnect { | ||||
let newState = ConnectivityState.connectivityState(cgrpc_channel_check_connectivity_state(self.underlyingChannel, self.tryToConnect ? 1 : 0)) | ||||
|
||||
guard newState != self.lastState else { continue } | ||||
defer { self.lastState = newState } | ||||
|
||||
if self.lastState == self.state { | ||||
self.callbacks.forEach({ $0(newState) }) | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Please use a trailing closure here. |
||||
} | ||||
} | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd suggest to handle queue shutdown at this point as well and exit the loop in that case. FYI, this loop retains There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I might miss something here, but I don't think this loop retains |
||||
} | ||||
} | ||||
} | ||||
|
||||
static func == (lhs: ConnectivityObserver, rhs: ConnectivityObserver) -> Bool { | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Space before paren. Also, I would remove this method altogether — two observers on different channels would clearly not be equal just because their states are equal. It also doesn't seem to be used. |
||||
return lhs.state == rhs.state | ||||
} | ||||
} | ||||
} | ||||
|
||||
extension Channel { | ||||
public enum ConnectivityState { | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks! This is much better than what I had added before. |
||||
/// Channel has just been initialized | ||||
case initialized | ||||
/// Channel is idle | ||||
case idle | ||||
/// Channel is connecting | ||||
case connecting | ||||
/// Channel is ready for work | ||||
case ready | ||||
/// Channel has seen a failure but expects to recover | ||||
case transientFailure | ||||
/// Channel has seen a failure that it cannot recover from | ||||
case shutdown | ||||
/// Channel connectivity state is unknown | ||||
case unknown | ||||
|
||||
fileprivate static func connectivityState(_ value: grpc_connectivity_state) -> ConnectivityState { | ||||
switch value { | ||||
case GRPC_CHANNEL_INIT: | ||||
return .initialized | ||||
case GRPC_CHANNEL_IDLE: | ||||
return .idle | ||||
case GRPC_CHANNEL_CONNECTING: | ||||
return .connecting | ||||
case GRPC_CHANNEL_READY: | ||||
return .ready | ||||
case GRPC_CHANNEL_TRANSIENT_FAILURE: | ||||
return .transientFailure | ||||
case GRPC_CHANNEL_SHUTDOWN: | ||||
return .shutdown | ||||
default: | ||||
return .unknown | ||||
} | ||||
} | ||||
|
||||
fileprivate var underlyingState: grpc_connectivity_state? { | ||||
switch self { | ||||
case .initialized: | ||||
return GRPC_CHANNEL_INIT | ||||
case .idle: | ||||
return GRPC_CHANNEL_IDLE | ||||
case .connecting: | ||||
return GRPC_CHANNEL_CONNECTING | ||||
case .ready: | ||||
return GRPC_CHANNEL_READY | ||||
case .transientFailure: | ||||
return GRPC_CHANNEL_TRANSIENT_FAILURE | ||||
case .shutdown: | ||||
return GRPC_CHANNEL_SHUTDOWN | ||||
default: | ||||
return nil | ||||
} | ||||
} | ||||
} | ||||
} |
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Please remove the space after the asterisk.