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

Enormous usage of Virtual Memory allocations (mmap() failed error) #8746

Open
alxgrm opened this issue Feb 26, 2025 · 1 comment
Open

Enormous usage of Virtual Memory allocations (mmap() failed error) #8746

alxgrm opened this issue Feb 26, 2025 · 1 comment

Comments

@alxgrm
Copy link

alxgrm commented Feb 26, 2025

How frequently does the bug occur?

Always

Description

Our users are experiencing crashes in random places due to the exhaustion of virtual memory address space:

Error Domain=io.realm Code=9 "mmap() failed: Cannot allocate memory (size: 67108864, offset: 2348810240)" UserInfo={Error Name=AddressSpaceExhausted, NSLocalizedDescription=mmap() failed: Cannot allocate memory (size: 67108864, offset: 2348810240), Error Code=9}

I'm investigating this issue, and during profiling with Instruments, I found no heap allocation leaks. However, there is a significant increase in VM allocations. The chart grows exponentially up to 10 GiB and beyond, without ever decreasing.

Upon further investigation, it appears that every time a new Realm instance is created, it allocates 64 MiB in the following call: realm::util::File::MapBase::try_reserve(realm::util::File const&, realm::util::File::AccessMode, unsigned long, long long, realm::util::WriteObserver*)

I suspect this could be the cause of address space exhaustion.

This is how I instantiate and interact with Realm:

class RealmStorage {

    lazy private var realmConfiguration: Realm.Configuration = {
        let path = "default"
        var config = Realm.Configuration.defaultConfiguration
        config.schemaVersion = UInt64(GlobalConstants.dbVersion)
        config.shouldCompactOnLaunch = { totalBytes, usedBytes in
            let size50Mb = 50 * 1024 * 1024
            let shouldCompact: Bool = totalBytes > size50Mb && Double(usedBytes) / Double(totalBytes) < 0.7
            return shouldCompact
        }
        config.objectTypes = objectClasses()
        config.fileURL = config.fileURL!
            .deletingLastPathComponent()
            .appendingPathComponent(path)
            .appendingPathExtension("realm")
        return config
    }()

    lazy private var dbWorkQueue: DispatchQueue = {
        let queue = DispatchQueue(label: "db-queue", qos: .userInitiated, autoreleaseFrequency: .workItem, target: .global(qos: .userInitiated))
        return queue
    }()

    private func realm() throws -> Realm {
        return try Realm(configuration: realmConfiguration)
    }

    func saveSomeObjects(_ objects: [SomeObject], completion: ((Error?) -> Void)?) {
        dbWorkQueue.async { [weak self] in
            guard let self else { return }
            do {
                let realm = try realm()
                try realm.write {
                    realm.add(objects, update: .all)
                }
                DispatchQueue.main.async {
                    completion?(nil)
                }
            } catch {
                DispatchQueue.main.async {
                    completion?(error)
                }
            }
        }
    }
}

The shouldCompactOnLaunch option doesn't mitigate the issue, nor do calling refresh() before using a new Realm instance or invalidate() after finishing.

One potential workaround I’m considering is restricting Realm usage to the main queue, storing the instance in a property, and reusing it instead of creating a new one each time. However, this seems like bad practice since Realm is supposed to return the same instance per thread, even if Realm() is initialized multiple times. Additionally, this approach would prevent me from using advantages of background queues.

Are these VM allocations behaving as expected? Is there a better way to resolve this issue without restricting Realm usage to the main queue?

Stacktrace & log output

Error Domain=io.realm Code=9 "mmap() failed: Cannot allocate memory (size: 67108864, offset: 2348810240)" UserInfo={Error Name=AddressSpaceExhausted, NSLocalizedDescription=mmap() failed: Cannot allocate memory (size: 67108864, offset: 2348810240), Error Code=9}

Can you reproduce the bug?

Sometimes

Reproduction Steps

No response

Version

10.54.2

What Atlas Services are you using?

Local Database only

Are you using encryption?

No

Platform OS and version(s)

iOS 17.5.1

Build environment

Xcode version: 16.2
Dependency manager and version: cocoapods (1.16.2)

@aehlke
Copy link

aehlke commented Feb 28, 2025

I found better stability by always reusing Realm instances (bound to actors)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants