realm-core icon indicating copy to clipboard operation
realm-core copied to clipboard

Crash: `REALM_ASSERT_DEBUG(ndx < m_size);` assertion failed

Open rserentill opened this issue 2 years ago • 3 comments

SDK and version

SDK : Cocoa - RealmSwift (iOS) Version: 10.28.0

Observations

  • How frequent do the crash occur? We are seeing this on production with several users (~2%). Hard to reproduce on our side.
  • Does it happen in production or during dev/test? In production
  • Can the crash be reproduced by you? Very hardly, we managed to reproduce it once. Seems to happen when different objects are being saved to the database
  • Can you provide instructions for how we can reproduce it? Not really
#### Crash log / stacktrace
Thread 1 Queue : com.apple.main-thread (serial)
#0	0x00000001b7714b38 in __pthread_kill ()
#1	0x00000001f08973bc in pthread_kill ()
#2	0x000000018b9b8524 in abort ()
#3	0x0000000101a817bc in ::please_report_this_issue_in_github_realm_realm_core() at /Users/<myuser>/Library/Developer/Xcode/DerivedData/App-djnigzvptbddtsdqrwnmknmvznyx/SourcePackages/checkouts/realm-core/src/realm/util/terminate.cpp:50
#4	0x0000000101a81b40 in realm::util::terminate_internal(std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) at /Users/<myuser>/Library/Developer/Xcode/DerivedData/App-djnigzvptbddtsdqrwnmknmvznyx/SourcePackages/checkouts/realm-core/src/realm/util/terminate.cpp:123
#5	0x0000000101a818a0 in realm::util::terminate(char const*, char const*, long, std::initializer_list<realm::util::Printable>&&) at /Users/<myuser>/Library/Developer/Xcode/DerivedData/App-djnigzvptbddtsdqrwnmknmvznyx/SourcePackages/checkouts/realm-core/src/realm/util/terminate.cpp:133
#6	0x0000000101225fa4 in realm::Array::get(unsigned long) const at /Users/<myuser>/Library/Developer/Xcode/DerivedData/App-djnigzvptbddtsdqrwnmknmvznyx/SourcePackages/checkouts/realm-core/src/realm/array.hpp:714
#7	0x000000010127d148 in realm::ArrayBoolNull::get(unsigned long) const at /Users/<myuser>/Library/Developer/Xcode/DerivedData/App-djnigzvptbddtsdqrwnmknmvznyx/SourcePackages/checkouts/realm-core/src/realm/array_bool.hpp:150
#8	0x0000000101720d74 in realm::BoolNode<realm::Equal>::find_first_local(unsigned long, unsigned long) at /Users/<myuser>/Library/Developer/Xcode/DerivedData/App-djnigzvptbddtsdqrwnmknmvznyx/SourcePackages/checkouts/realm-core/src/realm/query_engine.hpp:948
#9	0x000000010177d304 in realm::ParentNode::aggregate_local(realm::QueryStateBase*, unsigned long, unsigned long, unsigned long, realm::ArrayPayload*) at /Users/<myuser>/Library/Developer/Xcode/DerivedData/App-djnigzvptbddtsdqrwnmknmvznyx/SourcePackages/checkouts/realm-core/src/realm/query_engine.cpp:130
#10	0x00000001016f12e4 in realm::Query::aggregate_internal(realm::ParentNode*, realm::QueryStateBase*, unsigned long, unsigned long, realm::ArrayPayload*) const at /Users/<myuser>/Library/Developer/Xcode/DerivedData/App-djnigzvptbddtsdqrwnmknmvznyx/SourcePackages/checkouts/realm-core/src/realm/query.cpp:1048
#11	0x00000001016fe958 in realm::Query::do_count(unsigned long) const::$_4::operator()(realm::Cluster const*) const at /Users/<myuser>/Library/Developer/Xcode/DerivedData/App-djnigzvptbddtsdqrwnmknmvznyx/SourcePackages/checkouts/realm-core/src/realm/query.cpp:1601
#12	0x00000001016fe8b0 in realm::util::FunctionRef<bool (realm::Cluster const*)>::FunctionRef<realm::Query::do_count(unsigned long) const::$_4&>(realm::Query::do_count(unsigned long) const::$_4&)::'lambda'(void*, realm::Cluster const*)::operator()(void*, realm::Cluster const*) const at /Users/<myuser>/Library/Developer/Xcode/DerivedData/App-djnigzvptbddtsdqrwnmknmvznyx/SourcePackages/checkouts/realm-core/src/realm/util/function_ref.hpp:106
#13	0x00000001016fe868 in realm::util::FunctionRef<bool (realm::Cluster const*)>::FunctionRef<realm::Query::do_count(unsigned long) const::$_4&>(realm::Query::do_count(unsigned long) const::$_4&)::'lambda'(void*, realm::Cluster const*)::__invoke(void*, realm::Cluster const*) at /Users/<myuser>/Library/Developer/Xcode/DerivedData/App-djnigzvptbddtsdqrwnmknmvznyx/SourcePackages/checkouts/realm-core/src/realm/util/function_ref.hpp:105
#14	0x000000010129d128 in realm::util::FunctionRef<bool (realm::Cluster const*)>::operator()(realm::Cluster const*) const at /Users/<myuser>/Library/Developer/Xcode/DerivedData/App-djnigzvptbddtsdqrwnmknmvznyx/SourcePackages/checkouts/realm-core/src/realm/util/function_ref.hpp:119
#15	0x000000010129cf18 in realm::ClusterNodeInner::traverse(realm::util::FunctionRef<bool (realm::Cluster const*)>, long long) const at /Users/<myuser>/Library/Developer/Xcode/DerivedData/App-djnigzvptbddtsdqrwnmknmvznyx/SourcePackages/checkouts/realm-core/src/realm/cluster_tree.cpp:695
#16	0x000000010129f23c in realm::ClusterTree::traverse(realm::util::FunctionRef<bool (realm::Cluster const*)>) const at /Users/<myuser>/Library/Developer/Xcode/DerivedData/App-djnigzvptbddtsdqrwnmknmvznyx/SourcePackages/checkouts/realm-core/src/realm/cluster_tree.cpp:1000
#17	0x00000001016f59c8 in realm::Table::traverse_clusters(realm::util::FunctionRef<bool (realm::Cluster const*)>) const at /Users/<myuser>/Library/Developer/Xcode/DerivedData/App-djnigzvptbddtsdqrwnmknmvznyx/SourcePackages/checkouts/realm-core/src/realm/table.hpp:311
#18	0x00000001016f65d4 in realm::Query::do_count(unsigned long) const at /Users/<myuser>/Library/Developer/Xcode/DerivedData/App-djnigzvptbddtsdqrwnmknmvznyx/SourcePackages/checkouts/realm-core/src/realm/query.cpp:1606
#19	0x00000001016f6b48 in realm::Query::count(realm::DescriptorOrdering const&) at /Users/<myuser>/Library/Developer/Xcode/DerivedData/App-djnigzvptbddtsdqrwnmknmvznyx/SourcePackages/checkouts/realm-core/src/realm/query.cpp:1671
#20	0x00000001015183e8 in realm::Results::do_size() at /Users/<myuser>/Library/Developer/Xcode/DerivedData/App-djnigzvptbddtsdqrwnmknmvznyx/SourcePackages/checkouts/realm-core/src/realm/object-store/results.cpp:151
#21	0x0000000101522154 in realm::Results::size() at /Users/<myuser>/Library/Developer/Xcode/DerivedData/App-djnigzvptbddtsdqrwnmknmvznyx/SourcePackages/checkouts/realm-core/src/realm/object-store/results.cpp:136
#22	0x000000010105ff80 in -[RLMResults count]::$_1::operator()() const at /Users/<myuser>/Library/Developer/Xcode/DerivedData/App-djnigzvptbddtsdqrwnmknmvznyx/SourcePackages/checkouts/realm-swift/Realm/RLMResults.mm:148
#23	0x000000010105b92c in auto translateRLMResultsErrors<-[RLMResults count]::$_1>(-[RLMResults count]::$_1&&, NSString*) at /Users/<myuser>/Library/Developer/Xcode/DerivedData/App-djnigzvptbddtsdqrwnmknmvznyx/SourcePackages/checkouts/realm-swift/Realm/RLMResults_Private.hpp:60
#24	0x000000010105b8fc in -[RLMResults count] at /Users/<myuser>/Library/Developer/Xcode/DerivedData/App-djnigzvptbddtsdqrwnmknmvznyx/SourcePackages/checkouts/realm-swift/Realm/RLMResults.mm:148
#25	0x0000000101122e28 in RealmCollectionImpl.count.getter at /Users/<myuser>/Library/Developer/Xcode/DerivedData/App-djnigzvptbddtsdqrwnmknmvznyx/SourcePackages/checkouts/realm-swift/RealmSwift/Impl/RealmCollectionImpl.swift:37
#26	0x0000000100a43610 in RealmRepositoryA.countPendingDownloads(_:) at /Users/<myuser>/<myprojectpath>/.../**.swift:114

RealmRepositoryA.countPendingDownloads:

let predicate = NSPredicate(format: "objectId = %d AND completed = false", objectId)
guard let results = database?.objects(RealmObject.self).filter(predicate) else { return 0 }
return results.count

We can see it's failing on this assertion (realm-core/src/realm/array.hpp:714):

inline int64_t Array::get(size_t ndx) const noexcept
{
    REALM_ASSERT_DEBUG(is_attached());
    REALM_ASSERT_DEBUG(ndx < m_size); // Assertion fails (ndx = 0 and m_size = 0)
    return (this->*m_getter)(ndx);
    ...
}

We think the problem is when applying that predicate. Specifically the AND completed = false bit. We've checked that we had items on the database with completed = false, but for some reason m_size is 0, so the assertion fails.

rserentill avatar Sep 15 '22 11:09 rserentill

Hello, thanks for reporting this. From the stack trace, it seems like you said that the Node that was inspected (called Array because of how we store data internally) was basically empty, thus that assertion triggered.

I'll try to reproduce the issue myself, but without a clear list of steps, it is going to be hard. You said that you were able to reproduce the issue:

  1. How did you save 2 objects in the database? Did you have multiple threads writing to the database?
  2. Is the query run on the same thread that wrote the data?
  3. Do you happen to have the database files when you managed to reproduce the crash? In case, you can share it privately with us using [email protected]

nicola-cab avatar Sep 20 '22 14:09 nicola-cab

Hello @nicola-cab, thank you for your answer!

  1. Yes, we are accessing the database from different threads and concurrent threads might be writing to the database at the same time. Let me try to summarise what are we doing:

We have a class Repository which has a variable database:

class Repository<Model, RealmModel> {
  private var mainDatabaseThread: Thread!
  var mainDataBase : Realm?
  
  var database: Realm? {
    return Thread.current == mainDatabaseThread
      ? mainDataBase
      : try? Realm(configuration: configuration ?? .defaultConfiguration)
  }
  
  var configuration: RealmSwift.Realm.Configuration?
  
  init(database: Realm?, configuration: RealmSwift.Realm.Configuration?) {
    self.mainDatabaseThread = Thread.current
    self.mainDataBase = database
    self.configuration = configuration
  }

  ...

}

NOTE: configuration should never be nil, that would mean try Realm() threw an exception while creating Repository

This class has some functions to create/retrieve/update/delete objects. Functions needing to write on the database do so like this:

try database?.safeWrite {
  database?.add(dbEntities, update: .all) // or any other operation
}

safeWrite is a custom extension we implemented, trying to mitigate crashes related to Realm already being in a write transaction:

extension Realm {
  func safeWrite<Result>(_ block: (() throws -> Result)) throws -> Result {
    guard !isInWriteTransaction else { return try block() }
    return try write(block)
  }
}

But we then also have other classes inheriting from Repository, let's say RepositoryA which also access database from its super class:

final class RepositoryA: Repository<Download, DownloadRealm> {
  func markAsCompleted(_ download: Download) throws {
    let predicate = NSPredicate(format: "id = %@", download.id)
    
    try database?.safeWrite {
      if let download = database?.objects(DownloadRealm.self).filter(predicate).first {
        download.completed = true
      }
    }
  }
}
  1. Yes, it should be, we're not switching threads inside the function, line markAsCompleted above.
  2. Unfortunately we don't have the files, we weren't able to reproduce the crash again, but we'll send an email to the email account you provided if we do reproduce it again.

rserentill avatar Sep 20 '22 16:09 rserentill

Hello @rserentill thanks for sharing the code with us, I am not an expert with Swift. But it seems to me that your code should not cause any harm or in general be responsible for corrupting the file. If you find a way to reproduce the issue and or you get a realm file, please share with us. We are trying to find the root cause and fix this bug.

nicola-cab avatar Sep 22 '22 15:09 nicola-cab

Hi @nicola-cab, thank you for answering! sure, we will send you guys the file if we are able to reproduce the issue again on our end.

rserentill avatar Sep 27 '22 09:09 rserentill