TunnelsManager: Handle waiting on a stale tunnel

If we have a stale tunnel on which we don't get status updates we rely
on a timer to update the status (see commit 34a7e5b).  Previously, if
the user tries to activate another tunnel, that resulted in both tunnels
waiting indefinitely. This commit fixes that.
This commit is contained in:
Roopesh Chander 2018-12-18 19:45:00 +05:30
parent efc593f7da
commit fe69fe57e4
1 changed files with 23 additions and 13 deletions

View File

@ -24,6 +24,7 @@ class TunnelsManager {
weak var tunnelsListDelegate: TunnelsManagerListDelegate? weak var tunnelsListDelegate: TunnelsManagerListDelegate?
weak var activationDelegate: TunnelsManagerActivationDelegate? weak var activationDelegate: TunnelsManagerActivationDelegate?
private var statusObservationToken: AnyObject? private var statusObservationToken: AnyObject?
private var waiteeObservationToken: AnyObject?
init(tunnelProviders: [NETunnelProviderManager]) { init(tunnelProviders: [NETunnelProviderManager]) {
tunnels = tunnelProviders.map { TunnelContainer(tunnel: $0) }.sorted { $0.name < $1.name } tunnels = tunnelProviders.map { TunnelContainer(tunnel: $0) }.sorted { $0.name < $1.name }
@ -205,6 +206,7 @@ class TunnelsManager {
if let tunnelInOperation = tunnels.first(where: { $0.status != .inactive }) { if let tunnelInOperation = tunnels.first(where: { $0.status != .inactive }) {
wg_log(.info, message: "Tunnel '\(tunnel.name)' waiting for deactivation of '\(tunnelInOperation.name)'") wg_log(.info, message: "Tunnel '\(tunnel.name)' waiting for deactivation of '\(tunnelInOperation.name)'")
tunnel.status = .waiting tunnel.status = .waiting
activateWaitingTunnelOnDeactivation(of: tunnelInOperation)
if tunnelInOperation.status != .deactivating { if tunnelInOperation.status != .deactivating {
startDeactivation(of: tunnelInOperation) startDeactivation(of: tunnelInOperation)
} }
@ -232,6 +234,18 @@ class TunnelsManager {
tunnels.forEach { $0.refreshStatus() } tunnels.forEach { $0.refreshStatus() }
} }
private func activateWaitingTunnelOnDeactivation(of tunnel: TunnelContainer) {
waiteeObservationToken = tunnel.observe(\.status) { [weak self] tunnel, _ in
guard let self = self else { return }
if tunnel.status == .inactive {
if let waitingTunnel = self.tunnels.first(where: { $0.status == .waiting }) {
waitingTunnel.startActivation(activationDelegate: self.activationDelegate)
}
self.waiteeObservationToken = nil
}
}
}
private func startObservingTunnelStatuses() { private func startObservingTunnelStatuses() {
guard statusObservationToken == nil else { return } guard statusObservationToken == nil else { return }
@ -268,13 +282,6 @@ class TunnelsManager {
} }
tunnel.refreshStatus() tunnel.refreshStatus()
// In case some other tunnel is waiting for this tunnel to get deactivated
if session.status == .disconnected || session.status == .invalid {
if let waitingTunnel = self.tunnels.first(where: { $0.status == .waiting }) {
waitingTunnel.startActivation(activationDelegate: self.activationDelegate)
}
}
} }
} }
@ -308,18 +315,21 @@ class TunnelContainer: NSObject {
var isAttemptingActivation = false { var isAttemptingActivation = false {
didSet { didSet {
if isAttemptingActivation { if isAttemptingActivation {
self.activationTimer?.invalidate()
let activationTimer = Timer(timeInterval: 5 /* seconds */, repeats: true) { [weak self] _ in let activationTimer = Timer(timeInterval: 5 /* seconds */, repeats: true) { [weak self] _ in
guard let self = self else { return } guard let self = self else { return }
self.refreshStatus() wg_log(.debug, message: "Status update notification timeout for tunnel '\(self.name)'. Tunnel status is now '\(self.tunnelProvider.connection.status)'.")
if self.status == .inactive || self.status == .active { switch self.tunnelProvider.connection.status {
self.isAttemptingActivation = false // This also invalidates the timer case .connected, .disconnected, .invalid:
self.activationTimer?.invalidate()
self.activationTimer = nil
default:
break
} }
self.refreshStatus()
} }
self.activationTimer = activationTimer self.activationTimer = activationTimer
RunLoop.main.add(activationTimer, forMode: .default) RunLoop.main.add(activationTimer, forMode: .default)
} else {
activationTimer?.invalidate()
activationTimer = nil
} }
} }
} }