Adding SwiftData model operations in Task{} block caused EXC_BAD_ACCESS using swift-async-dns-resolver

56 views Asked by At

I am using Apple's swift-async-dns-resolver to collect records used for a mail troubleshooting free utility app (MX, NS, DMARC, SPF). The resolves are working. When I added the SwiftData updates to the async Task, it started randomly crashing with EXC_BAD_ACCESS, so my guess is SwiftData may not be safe in this environment. Here is one of the functions:

/// Query DNS for MX record and update domain.mxRecords and domain.lastUpdate
/// - Parameter domain: SwiftData model record
func updateMxWithDomain(domain: Domain) {
    Task {
        do {
            let resolver = try AsyncDNSResolver()
            let mxRecords = try await resolver.queryMX(name: domain.domainName)
            if mxRecords.count > 0 {
                domain.mxRecords = mxRecords.map { "\($0.host) (\($0.priority))" }
            } else {
                domain.mxRecords = ["None found"]
            }
            domain.lastUpdate = Date()
        } catch {
            debugLog(object: error)
            domain.mxRecords = ["Error: \(error)"]
        }
    }
}

Is there a better way to write this function? Is there a discussion on using SwiftData with data collected in with async functions?

Thank you.

1

There are 1 answers

0
Kent On

After enabling Xcode concurrency check (@JoakimDanielson), I received warnings that the SwiftData model was not "Sendable". As @loremipsum noted, I re-wrote the func as async and removed the floating Task. Working func is:

/// Query DNS for MX record retrun an array of MX Strings
/// - Parameter domainName: domain string to query
func updateMxWithDomainName(domainName: String) async -> [String] {
    var mxRecords: [MXRecord]
    do {
        let resolver = try AsyncDNSResolver()
        mxRecords = try await resolver.queryMX(name: domainName)
        debugLog(object: mxRecords)
    } catch {
        mxRecords = [MXRecord(host: "\(error)", priority: 0)]
    }
    let mxStrings = mxRecords.map { "\($0.host) (\($0.priority))" }
    return mxStrings
}

It's called from a Button task that has safe access the SwitData model:

Button {
  Task {
    domain.mxRecords = await updateMxWithDomainName(domainName: domain.domainName)
    domain.lastUpdate = Date()
  }
} label: {
    Label("RefreshOne", systemImage: "arrow.clockwise")
}