简体   繁体   中英

Not connecting to RPC server in release mode, but works fine in debug mode

I have a command line app that does the following:

  • downloads an RSS feed with torrent links
  • stores it in a sqlite database and tags them as "added" or "ignored"
  • connects to a transmission server (in my local network)
  • loads items from sqlite marked as "added" and adds to transmission server

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)

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:

  • Debug vs Release bugs are common in the Apple ecosystem.
  • One common reason for the above: the compiler has much more aggressive retain and release patterns in release mode.
  • My problem was exactly that: a certain class was being disposed of earlier than it should and it was exactly the cancellable for the subscription, so my server requests were being cancelled in the middle.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM