@MainActor-isolated property called not on the Main Actor

Recently I was trying to understand the rules of isolation domain inheritance and wrote a bunch of tests to play with, and all of them behaved as expected except for this one:

import SwiftUI

@MainActor
var isolatedGlobalSyncProperty: Void {
  MainActor.assertIsolated("isolated global sync property called not on the Main Actor")
}

@main
struct SwiftConcurrencyApp: App {
  var content: some View {
    Text("Hello")
      .task {
        await isolatedGlobalSyncProperty
      }
  }

  var body: some Scene {
    WindowGroup {
      content
    }
  }
}

Output:

SwiftConcurrency/SwiftConcurrencyApp.swift:12: Fatal error: Incorrect actor executor assumption; Expected 'UnownedSerialExecutor(executor: (Opaque Value))' executor. isolated global sync property called not on the Main Actor

Shouldn't isolatedGlobalSyncProperty be called on the Main Actor since it's marked with @MainActor?

1 Like

Firstly, with strict concurrency checks fully on, the line

        await isolatedGlobalSyncProperty

gives a warning "No 'async' operations occur within 'await' expression" which means I presume the compiler doesn't think the global requires isolation and probably just ignores the @MainActor directive.

Second, I'm not getting the error you are describing. I'm on Swift 5.10. Are you sure you can reproduce it with the code you posted?

Hm, I don't see any warnings and definitely get the error. I'm testing on Xcode 15.3 (15E204a) with Strict Concurrency Checking – Complete.

Oh, interesting, I just ran it again and got the error, but that's because .task { ... } is executed on a different thread, I can see it in the debugger. However, why the compiler even allows this call is not clear at all. So back to square one.

P.S. the reason I was getting the warning and not the run-time error was because I copied your content into the main body. Try it, you will see different behavior. Now this looks more like a compiler bug because the warning is legitimate and should be issued in your case too.

Warning is given in this case:

	var body: some Scene {
		WindowGroup {
			Text("Hello")
				.task {
					await isolatedGlobalSyncProperty
				}
		}
	}

In your case body is MainActor-isolated, hence the task is too, so there is no need for await before another MainActor-isolated call, that's why the warning appears.

In my case, the task is non-isolated because it's defined in non-isolated content property, so await is needed before calling isolatedGlobalSyncProperty, so no warning.

But why is isolatedGlobalSyncProperty called on Main Actor in your case, and not in mine :thinking:

(With my limited knowledge of concurrency)

If you change the global property to e.g.:

@MainActor
var isolatedGlobalSyncProperty = {
	MainActor.assertIsolated("isolated global sync property called not on the Main Actor")
	return 0
}()

then it is executed on MainActor. In fact removing @MainActor causes the compiler to issue some reasonable warnings.

I think what happens in your case is, the compiler thinks there's no need for isolation since nothing in the body requires it, nothing breaks concurrency and therefore can be executed on any thread safely.

I should probably file a bug report somewhere.

GitHub issue: MainActor-isolated property not called on the Main Actor · Issue #72633 · apple/swift · GitHub