Closed ksiwei closed 2 years ago
Hi @ksiwei. @ObservedResults
does not open the Realm asynchronously. How much storage is available on the test device you are using? Could you also post some code showing how you are using Realm?
Sorry about the delay. Just to give you a little more context. My app is a photo app that adopts an image handling strategy that is similar to this: https://www.mongodb.com/developer/how-to/realm-data-architecture-ofish-app/#handling-images. We save image blobs to Realm, and an Atlas trigger actively uploads the blob to s3 and replaces it with an image URL.
The crash happens to people with a lot of images. When I set logLevel = .all
I can see Realm was trying to apply every changeset throughout the sync history. Some of the changesets contain image blob. I wonder whether Realm is keeping references to a lot of those changesets with blobs in memory that eventually grew too large?
Also, does realm always apply all the changesets? is there a way to limit the number of changesets to be applied or squash them?
Sample log before the crash:
InternStrings 0="imageData", 1="P", 2="cards", 3="9CCA9FB6-81CB-49ED-BE64-C7E9812491B3"
Update path=P["9CCA9FB6-81CB-49ED-BE64-C7E9812491B3"].cards[0].imageData, value=Binary(...), default=0
2022-02-01 17:43:29.614621+0000 XXX[739:46540] Sync: Connection[1]: Received: DOWNLOAD CHANGESET(server_version=2090, client_version=0, origin_timestamp=221730758462, origin_file_ident=56, original_changeset_size=70204, changeset_size=70204)
2022-02-01 17:43:29.623764+0000 XXX[739:46540] Sync: Connection[1]: Changeset(comp): 70204
2022-02-01 17:43:29.630874+0000 XXX[739:46540] Sync: Connection[1]: Changeset (parsed):
InternStrings 0="imageData", 1="P", 2="cards", 3="1166D3B3-7F69-4998-8BF6-A921CDB53B4F"
Update path=P["1166D3B3-7F69-4998-8BF6-A921CDB53B4F"].cards[0].imageData, value=Binary(...), default=0
2022-02-01 17:43:29.632512+0000 PicoJar[739:46540] Sync: Connection[1]: Session[1]: Received: DOWNLOAD(download_server_version=2090, download_client_version=0, latest_server_version=4029, latest_server_version_salt=2166913025483924558, upload_client_version=0, upload_server_version=0, downloadable_bytes=114628511, num_changesets=12, ...)
2022-02-01 17:43:29.639893+0000 XXX[739:46540] Sync: Connection[1]: Session[1]: Scanning incoming changeset [1/12] (1 instructions)
2022-02-01 17:43:29.640032+0000 XXX[739:46540] Sync: Connection[1]: Session[1]: Scanning incoming changeset [2/12] (1 instructions)
2022-02-01 17:43:29.640086+0000 XXX[739:46540] Sync: Connection[1]: Session[1]: Scanning incoming changeset [3/12] (1 instructions)
2022-02-01 17:43:29.640133+0000 XXX[739:46540] Sync: Connection[1]: Session[1]: Scanning incoming changeset [4/12] (1 instructions)
2022-02-01 17:43:29.640241+0000 XXX[739:46540] Sync: Connection[1]: Session[1]: Scanning incoming changeset [5/12] (1 instructions)
2022-02-01 17:43:29.640295+0000 XXX[739:46540] Sync: Connection[1]: Session[1]: Scanning incoming changeset [6/12] (1 instructions)
2022-02-01 17:43:29.640350+0000 XXX[739:46540] Sync: Connection[1]: Session[1]: Scanning incoming changeset [7/12] (1 instructions)
2022-02-01 17:43:29.640394+0000 XXX[739:46540] Sync: Connection[1]: Session[1]: Scanning incoming changeset [8/12] (1 instructions)
2022-02-01 17:43:29.641121+0000 XXX[739:46540] Sync: Connection[1]: Session[1]: Scanning incoming changeset [9/12] (1 instructions)
2022-02-01 17:43:29.641493+0000 XXX[739:46540] Sync: Connection[1]: Session[1]: Scanning incoming changeset [10/12] (6 instructions)
2022-02-01 17:43:29.641588+0000 XXX[739:46540] Sync: Connection[1]: Session[1]: Scanning incoming changeset [11/12] (1 instructions)
2022-02-01 17:43:29.642191+0000 XXX[739:46540] Sync: Connection[1]: Session[1]: Scanning incoming changeset [12/12] (1 instructions)
2022-02-01 17:43:29.642265+0000 XXX[739:46540] Sync: Connection[1]: Session[1]: Scanning local changeset [1/3] (0 instructions)
2022-02-01 17:43:29.642322+0000 XXX[739:46540] Sync: Connection[1]: Session[1]: Scanning local changeset [2/3] (4 instructions)
2022-02-01 17:43:29.642377+0000 XXX[739:46540] Sync: Connection[1]: Session[1]: Scanning local changeset [3/3] (8 instructions)
2022-02-01 17:43:29.642445+0000 XXX[739:46540] Sync: Connection[1]: Session[1]: Indexing incoming changeset [1/12] (1 instructions)
2022-02-01 17:43:29.642512+0000 XXX[739:46540] Sync: Connection[1]: Session[1]: Indexing incoming changeset [2/12] (1 instructions)
2022-02-01 17:43:29.642553+0000 XXX[739:46540] Sync: Connection[1]: Session[1]: Indexing incoming changeset [3/12] (1 instructions)
2022-02-01 17:43:29.642594+0000 XXX[739:46540] Sync: Connection[1]: Session[1]: Indexing incoming changeset [4/12] (1 instructions)
2022-02-01 17:43:29.643289+0000 XXX[739:46540] Sync: Connection[1]: Session[1]: Indexing incoming changeset [5/12] (1 instructions)
2022-02-01 17:43:29.643359+0000 XXX[739:46540] Sync: Connection[1]: Session[1]: Indexing incoming changeset [6/12] (1 instructions)
2022-02-01 17:43:29.643404+0000 XXX[739:46540] Sync: Connection[1]: Session[1]: Indexing incoming changeset [7/12] (1 instructions)
2022-02-01 17:43:29.643463+0000 XXX[739:46540] Sync: Connection[1]: Session[1]: Indexing incoming changeset [8/12] (1 instructions)
2022-02-01 17:43:29.643505+0000 XXX[739:46540] Sync: Connection[1]: Session[1]: Indexing incoming changeset [9/12] (1 instructions)
2022-02-01 17:43:29.643553+0000 XXX[739:46540] Sync: Connection[1]: Session[1]: Indexing incoming changeset [10/12] (6 instructions)
2022-02-01 17:43:29.643646+0000 XXX[739:46540] Sync: Connection[1]: Session[1]: Indexing incoming changeset [11/12] (1 instructions)
2022-02-01 17:43:29.644006+0000 XXX[739:46540] Sync: Connection[1]: Session[1]: Indexing incoming changeset [12/12] (1 instructions)
2022-02-01 17:43:29.644611+0000 XXX[739:46540] Sync: Connection[1]: Session[1]: Finished changeset indexing (incoming: 12 changeset(s) / 17 instructions, local: 3 changeset(s) / 12 instructions, conflict group(s): 16)
2022-02-01 17:43:29.644682+0000 XXX[739:46540] Sync: Connection[1]: Session[1]: Transforming local changeset [1/3] through 12 incoming changeset(s) with 16 conflict group(s)
2022-02-01 17:43:29.644720+0000 XXX[739:46540] Sync: Connection[1]: Session[1]: Transforming local changeset [2/3] through 12 incoming changeset(s) with 16 conflict group(s)
2022-02-01 17:43:29.645367+0000 XXX[739:46540] Sync: Connection[1]: Session[1]: Transforming local changeset [3/3] through 12 incoming changeset(s) with 16 conflict group(s)
2022-02-01 17:43:29.645726+0000 XXX[739:46540] Sync: Connection[1]: Session[1]: Finished transforming 3 local changesets through 12 incoming changesets (12 vs 17 instructions, in 16 conflict groups)
libc++abi: terminating with uncaught exception of type realm::AddressSpaceExhausted: mmap() failed: Cannot allocate memory size: 24657920 offset: 134217728
Hi @ksiwei Is it possible to run the app with Instruments utility to see how many memory takes the app for the images? Depending on what and how the app does to image data it could explain the lack of free space. Let's say image file will take only several MBytes as NSData but the same image can take tenths of MBytes as UIImage. The solution can be to sync the images one by one and/or to check the free space before opening next photo. Any way you can get more info from Instruments Allocations tool.
Thanks @pavel-ship-it I will try to play around with the instrument allocation tool - haven't really properly used it.
Weirdly it only runs out of memory during the initial sync - I will double-check if that still happens if I comment out all the UI code.
We do have some chunking logic to make sure every write
is under 15MB.
let fifteenMB = 15 * 1024 * 1024
let imageChunks = images.chunked(into: 5)
for chunk in imageChunks {
let totalBytesForImages = chunk.reduce(0) { acc, p in
return acc + (p.imageData?.count ?? 0)
}
if totalBytesForImages < fifteenMB {
try! realm.write {
realm.add(chunk)
}
} else {
for image in imageChunk {
try! realm.write {
realm.add(image)
}
}
}
}
@ksiwei the memory bloating you are encountering looks related to the use of writes inside the for loops. I would suggest moving the write block outside of the for loop like so:
let fifteenMB = 15 * 1024 * 1024
let imageChunks = images.chunked(into: 5)
try ! realm.write {
for chunk in imageChunks {
let totalBytesForImages = chunk.reduce(0) { acc, p in
return acc + (p.imageData?.count ?? 0)
}
if totalBytesForImages < fifteenMB {
realm.add(chunk)
} else {
for image in imageChunk {
realm.add(image)
}
}
}
}
How frequently does the bug occur?
Sometimes
Description
During the initial sync after user logins, the app sometimes crashes after 10~20 seconds due to
terminating with uncaught exception of type realm::AddressSpaceExhausted: mmap() failed: Cannot allocate memory size: 26279936 offset: 134217728
I removed all the occurrences of realm() that are opened synchronously after seeing this issue: https://github.com/realm/realm-swift/issues/7040 3 but still couldn’t avoid it.
I use @ObservableResults in many places, I wonder if they are causing the crash.
Stacktrace & log output
No response
Can you reproduce the bug?
Yes, sometimes
Reproduction Steps
No response
Version
10.16.0
What SDK flavour are you using?
MongoDB Realm (i.e. Sync, auth, functions)
Are you using encryption?
No, not using encryption
Platform OS and version(s)
iOS14
Build environment
Xcode version: ... Dependency manager and version: ...