Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Results and objects are not isFrozen after calling freeze() #7697

Closed
tfe opened this issue Mar 13, 2022 · 13 comments · Fixed by #7789
Closed

Results and objects are not isFrozen after calling freeze() #7697

tfe opened this issue Mar 13, 2022 · 13 comments · Fixed by #7789
Assignees
Labels
More-information-needed More information is needed to progress. The issue will close automatically in 2 weeks. O-Community T-Bug

Comments

@tfe
Copy link

tfe commented Mar 13, 2022

How frequently does the bug occur?

All the time

Description

I recently implemented a feature that depends on using freeze() to pass a Results object to another thread for further processing, however I'm seeing crashes due to accessing from an incorrect thread.

This was unexpected since freezing the collection should have prevented this error. In debugging I added some diagnostic print statements to see what was going on:

let modifiedObjects = Model.modifiedSince(date: date)
print(modifiedObjects.isFrozen)           // `false`, as expected
let frozenModifiedObjects = modifiedObjects.freeze()
print(frozenModifiedObjects.isFrozen)     // still `false`, not as expected

The same thing happens even if I freeze the objects individually and store them in a regular array:

print(modifiedObjects.first?.isFrozen)           // `false`, as expected
let frozenModifiedObjects: [Model] = modifiedObjects.map { $0.freeze() }
print(frozenModifiedObjects.first?.isFrozen)     // still `false`, not as expected

If I let it continue past this point and access the objects or Results on another thread, it crashes with an incorrect thread exception.

Is this expected behavior? Or am I misunderstanding how freezing objects works?

Stacktrace & log output

No response

Can you reproduce the bug?

Yes, always

Reproduction Steps

I can provide more on this, but first I wanted to make sure I'm not just misunderstanding how the feature is designed to be used.

Version

10.7.5

What SDK flavour are you using?

Local Database only

Are you using encryption?

No, not using encryption

Platform OS and version(s)

iPadOS 15

Build environment

Xcode version: 13.2
Dependency manager and version: Cocoapods

@dianaafanador3
Copy link
Contributor

Hi @tfe Can you please show me your objects query on Model.modifiedSince(date: date), this seems to be working fine on our latest release v10.24.1 and v10.7.5 as well.
Have you tried to update to our latest release?

@sync-by-unito sync-by-unito bot added the Waiting-For-Reporter Waiting for more information from the reporter before we can proceed label Mar 17, 2022
@tfe
Copy link
Author

tfe commented Mar 17, 2022

Hi @dianaafanador3, sure thing, the query function is pasted below along with the others it calls. It's pretty simple.

I did try updating recently but had a rash of unexplained issues [1] [2], and so ended up staying on v10.7.5.

I'm sure freezing is working fine in general on all versions (it's covered in the test suite). Yet I have this reproducible case where it fails every time, so there must exist some edge case where freeze() fails to actually freeze and worse, fails silently. Because it's a silent failure, it results in a crash downstream when accessing objects that are supposed to be frozen.

The question is, under what conditions can that happen? (And then, how to fix or avoid it.)

I do have a copy of the customer's DB file that this is happening with. If you'd like to see it I can get their consent to share it with you.

// Model.swift
static func modifiedSince(date: Date) -> Results<Model> {
    let dm = DataManager.forCurrentThread()
    let nsDate = date as NSDate
    return dm.all(self).filter("endedAt >= %@", nsDate)
}

// DataManager.swift
func all<T: Object>(_ type: T.Type, in db: Database = .main) -> Results<T> {
    return getRealm(for: db).objects(type)
}

// getRealm(for:) returns a Realm instance created by calling realm's `Realm(configuration:)` initializer

[1] #7641
[2] #7629 (comment)

@github-actions github-actions bot added Needs-Attention Reporter has responded. Review comment. and removed Waiting-For-Reporter Waiting for more information from the reporter before we can proceed labels Mar 17, 2022
@leemaguire
Copy link
Contributor

@tfe could you supply us with a sample project that reproduces the issue, we cannot reproduce the issue from our end.

@sync-by-unito sync-by-unito bot added Waiting-For-Reporter Waiting for more information from the reporter before we can proceed and removed Needs-Attention Reporter has responded. Review comment. labels Mar 22, 2022
@tfe
Copy link
Author

tfe commented Mar 25, 2022

@leemaguire Yes, I can get something together for you. One additional bit of info: this seems to only be happening on first open of a Realm created via the writeCopy function. Can you try to reproduce using a Realm freshly created via writeCopy first?

@github-actions github-actions bot added Needs-Attention Reporter has responded. Review comment. and removed Waiting-For-Reporter Waiting for more information from the reporter before we can proceed labels Mar 25, 2022
@sync-by-unito sync-by-unito bot added Waiting-For-Reporter Waiting for more information from the reporter before we can proceed and removed Needs-Attention Reporter has responded. Review comment. labels Mar 29, 2022
@leemaguire
Copy link
Contributor

Hi @tfe did you get a chance to get the reproduction case together? I still cannot reproduce when testing against a freshly frozen realm from writeCopy.

@sync-by-unito sync-by-unito bot added the More-information-needed More information is needed to progress. The issue will close automatically in 2 weeks. label Apr 22, 2022
@dianaafanador3
Copy link
Contributor

Hi @tfe did you get the chance to get the reproduction case?, It will very helpful so we can reproduce this.

@tfe
Copy link
Author

tfe commented May 7, 2022

@dianaafanador3 @leemaguire I have a reproduction case for you (I'll email it to Diana separately, since I have her address). If you run it it should crash on launch, and that will point you to the place where isFrozen is unexpectedly false after calling freeze().

As I mentioned previously, I'm getting a crash on first open of a realm created via writeCopy, so the example project includes an "exported" DB in the bundle (generated by commented code in the same project) which it then imports on launch.

Please let me know if you have any questions about the example project, or if it's not behaving as described here.

@github-actions github-actions bot added Needs-Attention Reporter has responded. Review comment. and removed Waiting-For-Reporter Waiting for more information from the reporter before we can proceed labels May 7, 2022
@sync-by-unito sync-by-unito bot removed the Needs-Attention Reporter has responded. Review comment. label May 9, 2022
@leemaguire
Copy link
Contributor

@tfe Thanks for the reproduction case, I'm looking into the issue now.

@tfe
Copy link
Author

tfe commented May 13, 2022

Thanks @leemaguire! Glad you were able to find the root cause.

@joaocolaco
Copy link

Hi.

It seems this bug resurfaces when freezing a realm opened as read only.
In my case calling freeze() in a realm returns exactly the same realm (that also returns false to .isFrozen).
Changing readOnly to true in the realm configuration makes freeze() work again.

Can this bug be fixed?

@dianaafanador3
Copy link
Contributor

Hi @joaocolaco why are you using freeze with a read-only realm, the purpose of freeze is get an immutable snapshot of the realm data, and that's what a read-only realm is.
I do have a repo test for this particular case, but I do want to know first what is your use case for this.

func testFreezeReadOnlyRealm() throws {
        try autoreleasepool {
            let realm = try Realm()
            let frozenRealm = realm.freeze()
            XCTAssert(frozenRealm.isFrozen)
        }
        var configuration = Realm.Configuration.defaultConfiguration
        configuration.readOnly = true
        let realm = try Realm(configuration: configuration)
        let frozenRealm = realm.freeze()
        XCTAssert(frozenRealm.isFrozen) // This is failing
    }

@joaocolaco
Copy link

joaocolaco commented Jul 8, 2022

Thanks!

As for the use case freeze is also useful for querying the database in other threads, something that I need to do in my app (due to calls from SwiftUI and Swift async).

This particular database is in the Bundle Resources of the app so it shouldn't never be written. When the first version of the app was made the frozen functionality wasn't available so it was opened as read-only. When .frozen appeared I started to use it, but it didn't cross my mind that it would interfere with the read-only configuration until now.

Can I make a feature suggestion? Why not make all the read-only realms frozen? That would give "free" functionality and remove this sort of confusions.

@dianaafanador3
Copy link
Contributor

Hi @joaocolaco can you open a new issue?, as it seems this is unrelated to the original one and we can follow from there.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Mar 18, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
More-information-needed More information is needed to progress. The issue will close automatically in 2 weeks. O-Community T-Bug
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants