Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Realm: mmap() failed: Cannot allocate memory size

I'm using Realm 2.4.3 in a large iPad Application for one of our customer.
Most of the time Realm performed very well, but now the number of Records is increasing and a get some memory issues.

App

  • Deployment Target: 10.0
  • BaseSDK: 10.2
  • iPad only
  • Realm 2.4.3
  • The App was designed to work offline, thats why we use Realm for local storage
  • There are some methods which syncs Data from a Backend via HTTP to the Realm Database and vice versa.
  • Number of Records in Realm Database Number of Records

(unexpected) behaviour

  • the realm file size growth above 2,5GB (with compact fix from below ~500KB)
  • realm tries to load the whole file into memory
  • iPad Air 2 (2GB) crashes, on iPad Pro (4GB) everything works well

Issues i already read

  • mmap() failed: Cannot allocate memory
    https://github.com/realm/realm-cocoa/issues/1159
  • mmap() failed: Cannot allocate memory size
    https://github.com/realm/realm-cocoa/issues/3226
  • 'RLMException', reason: 'mmap() failed: Cannot allocate memory size: 1207959552'
    https://github.com/realm/realm-cocoa/issues/3920

The fix I tried

the writeCopyToURL:encryptionKey:error: compact hack

I have a Singleton Object which handles all storage actions to realm, which has a writeTransaction: method which handles beginWriteTransaction and commitWriteTransaction handling. All storage actions are coming through this method.

- (void)writeTransaction:(void (^)(void))block
{
    [self _ensureRealmThread:^{
        NSDate *startDate = [NSDate date];
        [[self _defaultRealm] beginWriteTransaction];
        block();

        NSError *commitWriteTransactionError = nil;
        [[self _defaultRealm] commitWriteTransaction:&commitWriteTransactionError];

        if (commitWriteTransactionError) {
            NSLog(@"commit error: %@", commitWriteTransactionError);
        }

        NSTimeInterval time = [[NSDate date] timeIntervalSinceDate:startDate];
        if (time > 0.5) {
            NSLog(@"WARNING: Transaction duration > 0.5");
        }

        // i added these 5 lines to compact the database every 2000 requests 
        _writeTransactionIndex++;
        if (_writeTransactionIndex > 2000) {
            _writeTransactionIndex = 0;
            [self compactDatabase];
        }
    }];
}


- (void)compactDatabase
{
    NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
                                                               NSUserDomainMask,
                                                               YES);
    NSString *documentsPath = searchPaths[0];

    NSString *defaultCompactPath = [documentsPath stringByAppendingPathComponent:@"defaultCompact.realm"];
    NSURL *defaultCompactURL = [NSURL fileURLWithPath:defaultCompactPath];

    // remove old
    if ([[NSFileManager defaultManager] fileExistsAtPath:[defaultCompactURL path]]) {
        [[NSFileManager defaultManager] removeItemAtURL:defaultCompactURL
                                                  error:nil];
    }

    NSError *writeError = nil;
    [[self _defaultRealm] writeCopyToURL:defaultCompactURL
                           encryptionKey:nil
                                   error:&writeError];
    if (!writeError) {
        [[NSFileManager defaultManager] replaceItemAtURL:[self _defaultRealm].configuration.fileURL
                                           withItemAtURL:defaultCompactURL
                                          backupItemName:nil
                                                 options:NSFileManagerItemReplacementUsingNewMetadataOnly
                                        resultingItemURL:nil
                                                   error:nil];
    }
}

The fix works in the storage!! The file shrinks from 2,5GB to 500KB. But i still got the issue that realm wants to allocate too much memory:

commit error: Error Domain=io.realm Code=9 "mmap() failed: Cannot allocate memory size: 268435456 offset: 2952790016" UserInfo={NSLocalizedDescription=mmap() failed: Cannot allocate memory size: 268435456 offset: 2952790016, Error Code=9}

Is there somebody with an idea to fix this ? :-)
If i missed some necessary information please leave a comment.. i'm in this issue for several days and my brain is like 💥

like image 531
Christoph P. Avatar asked Mar 01 '17 09:03

Christoph P.


Video Answer


1 Answers

We had this error. Below is our workaround until Realm fixes the issue:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_SERIAL, 0), ^{

    RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
    config.schemaVersion = [schema intValue];
    config.shouldCompactOnLaunch = ^BOOL(NSUInteger totalBytes, NSUInteger usedBytes){
        // totalBytes refers to the size of the file on disk in bytes (data + free space)
        // usedBytes refers to the number of bytes used by data in the file

        // Compact if the file is over 50MB in size and less than 50% 'used'
        NSUInteger oneHundredMB = 50 * 1024 * 1024;
        return (totalBytes > oneHundredMB) && (usedBytes / totalBytes) < 0.5;
    };

    __block BOOL deleteRealm = false;

    //schema has changed. Set delete flag.
    config.migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) {
        if (oldSchemaVersion < [schema intValue]) {
            deleteRealm = TRUE;
        }
    };

    [RLMRealmConfiguration setDefaultConfiguration:config];


    //*** Memory Allocation bug 'fix/hack' ***
    // Put realm init in try block. If it fails, blow it away in catch block. Then the following realm init will work.
    // This fix eliminated the error for us, with no effect on perfomance.
    @try
    {
        [RLMRealm defaultRealm];
    }
    @catch(...) {
        NSURL *rurl = [RLMRealmConfiguration defaultConfiguration].fileURL;
        // blow the database clean
        NSError *error = nil;
        [[NSFileManager defaultManager] removeItemAtURL:rurl error:&error];
        if(error) {
            NSLog(@"error %@ removing realm db", error);
        } else {
            NSLog(@"removed realm db successfully!");
        }
    }

    [RLMRealm defaultRealm];

    if (deleteRealm){
        [[RLMRealm defaultRealm] beginWriteTransaction];
        [[RLMRealm defaultRealm] deleteAllObjects];
        [[RLMRealm defaultRealm] commitWriteTransaction];
        deleteRealm = FALSE;
    }
});
like image 163
edhnb Avatar answered Oct 14 '22 05:10

edhnb