realm-swift icon indicating copy to clipboard operation
realm-swift copied to clipboard

Xcode 16: Write Blocks Risk Data Races Warnings

Open bdkjones opened this issue 1 year ago • 11 comments

How frequently does the bug occur?

Always

Description

I'm sure you guys are all over this, but opening a Realm project with Xcode 16 generates roughly 87 bajillion new warnings for simple code like this:

// Assume we're on an Actor and have an actor-isolated realm opened named `actorRealm`:
let newFoo: Foo = Foo()

try actorRealm.write {
   actorRealm.add(newFoo)    // Warning: Sending 'newFoo' risks causing data races; this is an error in the Swift 6 language mode
}

I see some changes in https://github.com/realm/realm-swift/pull/8618 that look like they might address this, but I'm throwing this here anyway just in case.

Stacktrace & log output

This warning appears essentially everywhere that *anything* is used in a write block.

Can you reproduce the bug?

Always

Reproduction Steps

Open a Realm 10.51.0-based project in Xcode 16.

Version

10.51.0

What Atlas Services are you using?

Atlas Device Sync

Are you using encryption?

No

Platform OS and version(s)

macOS 14.5

Build environment

Xcode version: 16.0 beta (16A5171c) Dependency manager and version: SPM

bdkjones avatar Jun 18 '24 05:06 bdkjones

➤ PM Bot commented:

Jira ticket: RCOCOA-2391

sync-by-unito[bot] avatar Jun 18 '24 05:06 sync-by-unito[bot]

Everything except for the warning from async Realm.init(actor:) should be fixed by #8618. That warning remains because an isolated init currently just crashes the compiler; if that goes unfixed by the time Xcode 16 makes it out of beta we'll probably need to add a Realm.open() static function or something.

tgoyne avatar Jun 18 '24 16:06 tgoyne

Just a heads up: I updated to 10.52.0 and these warnings are not resolved.

bdkjones avatar Jun 19 '24 04:06 bdkjones

Here's what I'm seeing in Xcode 16 beta 1 with 10.52.0:

Screenshot 2024-06-18 at 21 14 31

bdkjones avatar Jun 19 '24 04:06 bdkjones

A thought: I haven't actually switched to Swift 6 language mode in this project because there are a few other blockers unrelated to Realm.

I see some switches on the compiler version in the latest Realm update, so perhaps these are false warnings that evaporate with no errors once Swift 6 is enabled? I'll try that when I get back to my Mac this evening.

If that's the case, the ubiquitous warnings when the Swift version is <6 are going to mislead a lot of people and are worth resolving.

bdkjones avatar Jun 19 '24 19:06 bdkjones

Ha, I tried building in Swift 6 Language Mode but Xcode just spat out 13 "Swift compiler failed with a non-zero exit code" errors that have absolutely zero context—there's no associated lines or files, so I have no idea what the "errors" are.

This seems like a really bad inflection point in Swift. People are just going to use the 6.0 compiler in 5.0 language mode for the next seven years. Anyway, good luck!

bdkjones avatar Jun 20 '24 04:06 bdkjones

Is there something wrong with my approach to using an actor-isolated Realm, or is this just waiting on a resolution to the realm-on-actor init pattern issue?

I'd like to keep all of the "fetching" work outside of the write transaction so that the write lock isn't held any longer than necessary, but I'm unsure how else to resolve these issues.

bdkjones avatar Jul 02 '24 20:07 bdkjones

Getting these warnings from the synchronous write function seems like just a weird compiler bug as there's no sending involved at all? I tried to reproduce it in a small sample and couldn't.

Building an array of items to delete outside of the write transaction might actually be resulting in more time spent in the write transaction than not doing that. Passing a List directly to realm.delete() hits an optimized code path that should be faster than deleting the same array of objects.

tgoyne avatar Jul 04 '24 02:07 tgoyne

I tried to reproduce it in a small sample and couldn't.

Did your sample use an actor-isolated Realm opened with the pattern that the compiler now hates? I do not see these warnings in any other context within my app; only when using the actor-isolated Realm as follows:

actor ModelActor
{
    private var actorRealm: Realm!    // implicitly unwrapped so we can pass `self` to -Realm(actor:) within init

    init(config: Realm.Configuration) async throws
    {
        actorRealm = try await Realm(configuration: config, actor: self)
    }

    func foo() 
    {
        // Here is where the issues manifest for me, using `actorRealm`
    }
}

bdkjones avatar Jul 04 '24 03:07 bdkjones

Ah, try let actorRealm = actorRealm outside of the call to realm.write(). One of the surprising magic things is that capturing an actor in a closure will implicitly isolate that closure to the actor. If you instead capture just the Realm rather than the actor it should produce a nonisolated closure.

tgoyne avatar Jul 04 '24 03:07 tgoyne

@tgoyne

Ah, interesting! It was necessary to do let actorRealm = actorRealm! or the compiler complained that all uses of actorRealm inside the closure had to be unwrapped. But that has indeed eliminated the warnings. It's a little more friction, but workable!

bdkjones avatar Jul 04 '24 03:07 bdkjones