Crash in swift_getObjectType or processDefaultActor when using (nested) async/await with URLSession

Hi,

when using URL session nested in a few async/await calls I get a crash in swift_getObjectType (sometimes in processDefaultActor). Any ideas what could be causing this or hints how to debug/where to look?

For a (contrived - because it was extracted from a larger project) example please see below (see "crashes here" comment for the last call before the crash).

Thanks for any hints in advance! Cheers, Michael

// Crash on: Xcode Version 13.0 beta (13A5155e), macOS 11.4 (20F71), on iPhone Simulator

import CoreData
import SwiftUI

struct ContentView: View {
  @StateObject var dataCoordinator: DataCoordinator = .init()

  var body: some View {
    Button {
      print("GO")
      async {
        try await dataCoordinator.api.getSomething()
      }
    } label: {
      Label("Go", systemImage: "figure.walk")
    }
    .buttonStyle(.bordered)
  }
}

struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
  }
}

// MARK: - Test coding -

class DataCoordinator: ObservableObject {
  let api: API = .init()
  func refreshSomething() async throws {
    try await api.getSomething()
  }
}

// MARK: -

class API {
  var session: URLSession = .init(configuration: .ephemeral)

  func getSomething() async throws {
    let url = URL(string: "https://www.heise.de")!
    let request = URLRequest(url: url)

    let (data, response) = try await _failsafe(request: request)
    print("\(response)")
  }

  private func _failsafe(request: URLRequest) async throws -> (Data, URLResponse) {
    do {
      var (data, response) = try await session.data(for: request)
      let httpResponse = response as! HTTPURLResponse
      var recovered = false

      if httpResponse.allHeaderFields["dsfsfsdsfds"] == nil {
        let login = LoginAsync()
        await login.login(session: session)
        recovered = true
      }

      if recovered {
        let req2 = URLRequest(url: URL(string: "https://www.heise.de")!)
        print("right before crash")
        try await session.data(for: req2) // crashes here with EXC_BAD_ACCESS
        print("right after crash ;-)")
      }

      return (data, response)
    } catch {
      print("\(error)")
      throw error
    }
  }
}

// MARK: -

actor LoginAsync {
  func login(session: URLSession) async {
    let url = URL(string: "https://www.google.com")!
    let request = URLRequest(url: url)

    do {
      let (data, response) = try await session.data(for: request)
    } catch {
      print("\(error)")
    }
  }
}

If this was refactored to use two separate async marked functions calls instead of one, does that work better for you? Then, upon the return of the first, you can take action in your DataCoordinator about how to proceed with your second request.

import SwiftUI

struct ContentView: View {
  @StateObject var dataCoordinator: DataCoordinator = .init()

  var body: some View {
    Button {
      print("GO")
      let handle = async {
        await dataCoordinator.makeDataTaskRequestsAsync()
      }
    } label: {
      Label("Go", systemImage: "figure.walk")
    }
    .buttonStyle(.bordered)
  }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

class DataCoordinator: ObservableObject {
    let api: API = .init()
    func makeDataTaskRequestsAsync() async {
        let url1 = URL(string: "https://www.heise.de")!
        let url2 = URL(string: "https://www.apple.com")!
        let request1 = URLRequest(url: url1)
        let request2 = URLRequest(url: url2)
        do {
            let dataTaskResultOne = try await startDataTaskAsync(request: request1)
            print("Data task result one: \(dataTaskResultOne.1)")
            let dataTaskResultTwo = try await startDataTaskAsync(request: request2)
            print("Data task result two: \(dataTaskResultTwo.1)")
        } catch {
            print("Error: \(error)")
        }
    }

}
class API: NSObject {
    
    private var session: URLSession!

    override init() {
        super.init()
        let config = URLSessionConfiguration.default
        self.session = URLSession(configuration: config)
    }

    func startDataTaskAsync(request: URLRequest) async throws -> (Data, URLResponse) {
        let (data, response) = try await session.data(for: request)
        return (data, response)
    }
}


Something like the following above.

Matt Eaton
DTS Engineering, CoreOS
meaton3@apple.com

Additional info: If (in the original post) LoginAsync is not an actor, but a class the crash is not happening anymore. Really would like to understand what the root-cause of the issue is...

Thanks for the info. If you do want to investigate this further you can open a TSI here and I can dedicate some time into digging into this. Note that while this can be investigated further, the answer here just may be that these features are still and Beta and you need to get a bug report down. If you do end up opening a TSI, please reference this post and I can grab it.

Matt Eaton
DTS Engineering, CoreOS
meaton3@apple.com
Crash in swift_getObjectType or processDefaultActor when using (nested) async/await with URLSession
 
 
Q