realm / realm-swift

Realm is a mobile database: a replacement for Core Data & SQLite
https://realm.io
Apache License 2.0
16.28k stars 2.14k forks source link

Xcode 16: Write Blocks Risk Data Races Warnings #8628

Open bdkjones opened 3 months ago

bdkjones commented 3 months ago

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

sync-by-unito[bot] commented 3 months ago

➤ PM Bot commented:

Jira ticket: RCOCOA-2391

tgoyne commented 3 months ago

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.

bdkjones commented 3 months ago

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

bdkjones commented 3 months ago

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

Screenshot 2024-06-18 at 21 14 31

bdkjones commented 3 months ago

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 commented 3 months ago

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 commented 2 months ago

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.

tgoyne commented 2 months ago

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.

bdkjones commented 2 months ago

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`
    }
}
tgoyne commented 2 months ago

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.

bdkjones commented 2 months ago

@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!