mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 18:25:19 +00:00
Improve Tor dormant resume and restart flow
This commit is contained in:
@@ -68,6 +68,7 @@ final class TorManager: ObservableObject {
|
|||||||
private var controlMonitorStarted = false
|
private var controlMonitorStarted = false
|
||||||
private var pathMonitor: NWPathMonitor?
|
private var pathMonitor: NWPathMonitor?
|
||||||
private var isAppForeground: Bool = true
|
private var isAppForeground: Bool = true
|
||||||
|
private var isDormant: Bool = false
|
||||||
private var lastRestartAt: Date? = nil
|
private var lastRestartAt: Date? = nil
|
||||||
// Global policy gate: only allow Tor to start when true
|
// Global policy gate: only allow Tor to start when true
|
||||||
private(set) var allowAutoStart: Bool = false
|
private(set) var allowAutoStart: Bool = false
|
||||||
@@ -83,6 +84,7 @@ final class TorManager: ObservableObject {
|
|||||||
guard isAppForeground else { return }
|
guard isAppForeground else { return }
|
||||||
guard !didStart else { return }
|
guard !didStart else { return }
|
||||||
didStart = true
|
didStart = true
|
||||||
|
isDormant = false
|
||||||
isStarting = true
|
isStarting = true
|
||||||
lastError = nil
|
lastError = nil
|
||||||
// Announce initial start so UI can show a status message
|
// Announce initial start so UI can show a status message
|
||||||
@@ -492,18 +494,39 @@ final class TorManager: ObservableObject {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if !claimed { return }
|
if !claimed { return }
|
||||||
|
if await self.resumeTorIfPossible() {
|
||||||
|
await MainActor.run {
|
||||||
|
self.restarting = false
|
||||||
|
self.isStarting = false
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
await self.restartTor()
|
await self.restartTor()
|
||||||
await MainActor.run { self.restarting = false }
|
await MainActor.run { self.restarting = false }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func goDormantOnBackground() {
|
func goDormantOnBackground() {
|
||||||
// Stricter model: fully stop Tor when app backgrounds to save power
|
// Prefer Tor's DORMANT mode so we can resume on foreground without a full restart.
|
||||||
// and avoid half-suspended states. We'll restart cleanly on .active.
|
// If the control port is unreachable, fall back to a hard shutdown.
|
||||||
Task.detached { [weak self] in
|
Task.detached { [weak self] in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
|
let signaled = await self.controlSendSignal("DORMANT")
|
||||||
|
if signaled {
|
||||||
|
SecureLogger.info("TorManager: signalled DORMANT", category: .session)
|
||||||
|
await MainActor.run {
|
||||||
|
self.isDormant = true
|
||||||
|
self.isReady = false
|
||||||
|
self.socksReady = false
|
||||||
|
self.isStarting = false
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
SecureLogger.warning("TorManager: DORMANT signal failed; shutting down", category: .session)
|
||||||
_ = tor_host_shutdown()
|
_ = tor_host_shutdown()
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
|
self.isDormant = false
|
||||||
self.isReady = false
|
self.isReady = false
|
||||||
self.socksReady = false
|
self.socksReady = false
|
||||||
self.bootstrapProgress = 0
|
self.bootstrapProgress = 0
|
||||||
@@ -525,14 +548,28 @@ final class TorManager: ObservableObject {
|
|||||||
self.bootstrapProgress = 0
|
self.bootstrapProgress = 0
|
||||||
self.bootstrapSummary = ""
|
self.bootstrapSummary = ""
|
||||||
self.isStarting = true
|
self.isStarting = true
|
||||||
|
self.isDormant = false
|
||||||
self.lastRestartAt = Date()
|
self.lastRestartAt = Date()
|
||||||
}
|
}
|
||||||
// Prefer clean shutdown via owning controller FD; join the tor thread
|
// Prefer clean shutdown via owning controller FD; join the tor thread
|
||||||
_ = tor_host_shutdown()
|
_ = tor_host_shutdown()
|
||||||
// As a fallback, try control signal if needed (harmless if tor already down)
|
// As a fallback, try control signal if needed (harmless if tor already down)
|
||||||
_ = await controlSendSignal("SHUTDOWN")
|
_ = await controlSendSignal("SHUTDOWN")
|
||||||
|
// Allow Tor thread to fully terminate before re-starting.
|
||||||
|
var waited = 0
|
||||||
|
while tor_host_is_running() != 0 && waited < 40 {
|
||||||
|
try? await Task.sleep(nanoseconds: 100_000_000) // 100ms
|
||||||
|
waited += 1
|
||||||
|
}
|
||||||
|
if waited >= 40 {
|
||||||
|
SecureLogger.warning("TorManager: tor_host_is_running still true before restart", category: .session)
|
||||||
|
}
|
||||||
|
// Allow control monitor and start logic to reinitialize cleanly
|
||||||
|
await MainActor.run {
|
||||||
|
self.controlMonitorStarted = false
|
||||||
|
self.didStart = false
|
||||||
|
}
|
||||||
// Now start fresh
|
// Now start fresh
|
||||||
await MainActor.run { self.didStart = false }
|
|
||||||
await MainActor.run { self.startIfNeeded() }
|
await MainActor.run { self.startIfNeeded() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -598,6 +635,62 @@ final class TorManager: ObservableObject {
|
|||||||
return (text?.contains("250")) == true
|
return (text?.contains("250")) == true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func resumeTorIfPossible() async -> Bool {
|
||||||
|
let wasDormant = await MainActor.run { self.isDormant }
|
||||||
|
let pendingReady = await MainActor.run { self.socksReady && !self.isReady }
|
||||||
|
let needsWake = wasDormant || pendingReady
|
||||||
|
if !needsWake {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
let activated = await controlSendSignal("ACTIVE")
|
||||||
|
let pinged = await controlPingBootstrap(timeout: 3.0)
|
||||||
|
if !activated && !pinged {
|
||||||
|
SecureLogger.warning("TorManager: ACTIVE signal failed", category: .session)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if let info = await controlGetBootstrapInfo() {
|
||||||
|
await MainActor.run {
|
||||||
|
self.bootstrapProgress = info.progress
|
||||||
|
self.bootstrapSummary = info.summary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await MainActor.run {
|
||||||
|
self.isDormant = false
|
||||||
|
self.isStarting = true
|
||||||
|
self.socksReady = false
|
||||||
|
}
|
||||||
|
|
||||||
|
let firstReady = await waitForSocksReady(timeout: 12.0)
|
||||||
|
if firstReady {
|
||||||
|
await MainActor.run {
|
||||||
|
self.socksReady = true
|
||||||
|
self.isStarting = false
|
||||||
|
}
|
||||||
|
SecureLogger.info("TorManager: resumed Tor via ACTIVE signal", category: .session)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if pinged {
|
||||||
|
let secondReady = await waitForSocksReady(timeout: 20.0)
|
||||||
|
await MainActor.run {
|
||||||
|
self.socksReady = secondReady
|
||||||
|
self.isStarting = !secondReady
|
||||||
|
}
|
||||||
|
if secondReady {
|
||||||
|
SecureLogger.info("TorManager: resumed Tor after extended wait", category: .session)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await MainActor.run { self.isStarting = false }
|
||||||
|
}
|
||||||
|
|
||||||
|
SecureLogger.warning("TorManager: ACTIVE resume failed; will restart", category: .session)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
private func controlExchange(lines: [String], timeout: TimeInterval) async -> String? {
|
private func controlExchange(lines: [String], timeout: TimeInterval) async -> String? {
|
||||||
guard let cookiePath = dataDirectoryURL()?.appendingPathComponent("control_auth_cookie"),
|
guard let cookiePath = dataDirectoryURL()?.appendingPathComponent("control_auth_cookie"),
|
||||||
let cookie = try? Data(contentsOf: cookiePath) else { return nil }
|
let cookie = try? Data(contentsOf: cookiePath) else { return nil }
|
||||||
|
|||||||
Reference in New Issue
Block a user