How Swift runs an async executable

Updates:

  1. Jul 1, 2021
    Took throws out of the _runAsyncMain signature. I simplified the code to skip error handling, so I should leave it out of the signature too.

Here’s one of the simplest async/await programs you can write in Swift (using an @main program entry point):

@main struct Main {
  static func main() async {
    print("Sleeping")
    await Task.sleep(1_000_000_000) // Sleep for 1 second
    print("Done")
  }
}

How is this program executed? Async functions, like our main() method, can only be called from an async context. Who creates this context on launch, and how?

The Swift runtime does, with the help of the compiler. The compiler creates an entry point for the program that looks something like this:

_runAsyncMain {
  await Main.main()
}

_runAsyncMain is a function that’s defined in the Swift concurrency runtime library. You can read the full implementation in the Swift repo. Here, I’ve simplified it to its essence (without error handling or OS differences):

public func _runAsyncMain(_ asyncFun: @escaping () async -> ()) {
  Task.detached {
    await asyncFun()
    exit(0)
  }
  _asyncMainDrainQueue()
}

So the Swift runtime creates a detached task as the execution context for our main method. It’s this hidden task that becomes the parent task of all other structured child tasks our code might create.

_runAsyncMain then proceeds by calling _asyncMainDrainQueue (implementation), another runtime function that passes control to GCD by calling dispatchMain or CFRunLoopRun (on Apple platforms).

_asyncMainDrainQueue never returns. The program will run until our main method returns, and then exit. That’s it.