Reputation: 323
I have a command line app that does the following:
The above works fine in debug mode. However, when I build for release and try to run directly or from launchd, it always times out. The most relevant code is in main.swift
which goes below.
private func getTransmissionClient() -> Transmission? {
let client = Transmission(
baseURL: serverConfig.server,
username: serverConfig.username,
password: serverConfig.password)
var cancellables = Set<AnyCancellable>()
let group = DispatchGroup()
group.enter()
print("[INFO] Connecting to client")
client.request(.rpcVersion)
.sink(
receiveCompletion: { _ in group.leave() },
receiveValue: { rpcVersion in
print("[INFO]: Successfully Connected! RPC Version: \(rpcVersion)")
})
.store(in: &cancellables)
let wallTimeout = DispatchWallTime.now() +
DispatchTimeInterval.seconds(serverConfig.secondsTimeout ?? 15)
let res = group.wait(wallTimeout: wallTimeout)
if res == DispatchTimeoutResult.success {
return client
} else {
return nil
}
}
public func updateTransmission() throws {
print("[INFO] [\(Date())] Starting Transmission Update")
let clientOpt = getTransmissionClient()
guard let client = clientOpt else {
print("[ERROR] Failed to connect to transmission client")
exit(1)
}
var cancellables = Set<AnyCancellable>()
let items = try store.getPendingDownload()
print("[INFO] [\(Date())] Adding \(items.count) new items to transmission")
let group = DispatchGroup()
for item in items {
let linkComponents = "\(item.link)".components(separatedBy: "&")
assert(linkComponents.count > 0, "Link seems wrong")
group.enter()
client.request(.add(url: item.link))
.sink(receiveCompletion: { completion in
if case let .failure(error) = completion {
print("[Failure] \(item.title)")
print("[Failure] Details: \(error)")
}
group.leave()
}, receiveValue: { _ in
print("[Success] \(item.title)")
do {
try self.store.update(item: item, with: .downloaded)
} catch {
print("[Error] Couldn't save new status to DB")
}
})
.store(in: &cancellables)
}
let wallTimeout = DispatchWallTime.now() +
DispatchTimeInterval.seconds(serverConfig.secondsTimeout ?? 15)
let res = group.wait(wallTimeout: wallTimeout)
if res == DispatchTimeoutResult.success {
print("Tasks successfully submitted")
} else {
print("Timed out")
exit(1)
}
}
Oddly enough, the code seemed to work fine before I added the database. The DispatchGroup was already there, as well as the Transmission-Swift client. I guess something that I did is being "optimized away" by the compiler? This is just speculation though after seeing some other questions on StackOverflow, but I am still not clear on it.
I am using macOS 10.15 and Swift 5.2.2.
Full code available in github (link to specific commit that has the bug)
Upvotes: 0
Views: 115
Reputation: 323
I asked for help on Swift Forums at https://forums.swift.org/t/not-connecting-to-rpc-server-in-release-mode-but-works-fine-in-debug-mode/36251 and here is the gist of it:
This commit fixes it and it basically does the following:
diff --git a/Sources/TorrentRSS/TorrentRSS.swift b/Sources/TorrentRSS/TorrentRSS.swift
index 17e1a6b..0b80cd5 100644
--- a/Sources/TorrentRSS/TorrentRSS.swift
+++ b/Sources/TorrentRSS/TorrentRSS.swift
@@ -63,6 +63,10 @@ public struct TorrentRSS {
DispatchTimeInterval.seconds(serverConfig.secondsTimeout ?? 15)
let res = group.wait(wallTimeout: wallTimeout)
+ for cancellable in cancellables {
+ cancellable.cancel()
+ }
+
if res == DispatchTimeoutResult.success {
return client
} else {
@@ -117,6 +121,11 @@ public struct TorrentRSS {
let wallTimeout = DispatchWallTime.now() +
DispatchTimeInterval.seconds(serverConfig.secondsTimeout ?? 15)
let res = group.wait(wallTimeout: wallTimeout)
+
+ for cancellable in cancellables {
+ cancellable.cancel()
+ }
+
if res == DispatchTimeoutResult.success {
print("Tasks successfully submitted")
} else {
Calling cancellable explicitly avoids the object being disposed of before it should. That specific location is where I meant to dispose of the object, not any sooner.
Upvotes: 0