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

Infinite loop in db query [Realm Swift 2.6.2] #4895

Closed
shuhaodo opened this issue Apr 24, 2017 · 12 comments · Fixed by #4922
Closed

Infinite loop in db query [Realm Swift 2.6.2] #4895

shuhaodo opened this issue Apr 24, 2017 · 12 comments · Fixed by #4922
Assignees
Labels
Blocked This issue is blocked by another issue T-Bug

Comments

@shuhaodo
Copy link

shuhaodo commented Apr 24, 2017

In the latest Realm update, when running a filter query involves case-insensitive string compare, it went into an infinite loop causing the phone heats up.

Steps to Reproduce

This is 100% reproducible with this query: ("displayName = [c]%@", id)

It worked fine till the 2.6.1 update, and still happens in 2.6.2.

Code Sample

Here is the code snippet:

static func getContactByDisplayName(_ displayName:String?) -> EdoContact? {
    if let id = displayName {
        return filter("displayName = [c]%@", id).first //case insensitive
    } else {
        return nil
    }
}

Version of Realm and Tooling

  • Realm framework version: Realm Swift 2.6.1 & 2.6.2
  • Realm Object Server version: N/A
  • Xcode version: 8.3
  • iOS/OSX version: 10.3
  • Dependency manager + version: ?
@pigeondotdev
Copy link

Hey @shuhaodo. Thanks for reaching out. Someone will follow-up soon.

@jpsim jpsim assigned bdash and unassigned tgoyne Apr 24, 2017
@jpsim
Copy link
Contributor

jpsim commented Apr 24, 2017

@shuhaodo please share the following:

  1. Full class definition of the EdoContact model.
  2. The value for id/displayName that triggers the infinite loop.
  3. All the displayName values stored in the EdoContact models.

@youbing
Copy link

youbing commented Apr 25, 2017

EdoContact definition:

open class EdoContact: Object{
    dynamic var displayName = ""
    dynamic var firstName = ""
    dynamic var lastName = ""
    dynamic var lastUpdatedTime = Date.distantPast
    dynamic var contactedCount = 0
    dynamic var lastContactTime = Date.distantPast
    dynamic var note = ""
    
    fileprivate var _items = List<EdoContactItem>()
    
    override open static func primaryKey() -> String {
        return "displayName"
    }
    
    override open static func indexedProperties() -> [String] {
        return ["firstName", "lastName"]
    }
    
    open func items()->Results<EdoContactItem>{
        return _items.filter(NSPredicate(value: true))
    }
    
    open func addItem(_ item:EdoContactItem){
        self._items.append(item)
    }
    
    open func deleteItem(_ index:Int){
        if index < 0 {
            self._items.removeAll()
        }else{
            self._items.remove(at: index)
        }
    }
}

The value of "displayName" is leave-fd8115791a3c402029-fe6116757c6406757114-ff2b11797461-fe871372726d067a73-fef715737c670c.

@jpsim
Copy link
Contributor

jpsim commented Apr 25, 2017

@youbing @shuhaodo here's my best attempt at piecing together a full app based on the code fragments you provided that attempts to reproduce the issue.

Attempted Repro Case AppDelegate.swift
import UIKit
import RealmSwift

open class EdoContact: Object{
    dynamic var displayName = ""
    dynamic var firstName = ""
    dynamic var lastName = ""
    dynamic var lastUpdatedTime = Date.distantPast
    dynamic var contactedCount = 0
    dynamic var lastContactTime = Date.distantPast
    dynamic var note = ""

    // Commented out because no definition for 'EdoContactItem' was provided
//    fileprivate var _items = List<EdoContactItem>()

    override open static func primaryKey() -> String {
        return "displayName"
    }
    
    override open static func indexedProperties() -> [String] {
        return ["firstName", "lastName"]
    }

    // Commented out because no definition for 'EdoContactItem' was provided
//    open func items()->Results<EdoContactItem>{
//        return _items.filter(NSPredicate(value: true))
//    }
//    
//    open func addItem(_ item:EdoContactItem){
//        self._items.append(item)
//    }
//    
//    open func deleteItem(_ index:Int){
//        if index < 0 {
//            self._items.removeAll()
//        }else{
//            self._items.remove(at: index)
//        }
//    }
}

// Best guess as to how the 'getContactByDisplayName' function provided was implemented
extension Results where Element == EdoContact {
    func getContactByDisplayName(_ displayName:String?) -> EdoContact? {
        if let id = displayName {
            return filter("displayName = [c]%@", id).first //case insensitive
        } else {
            return nil
        }
    }
}

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // Potentially clean up from previous runs
        _ = try? FileManager.default.removeItem(at: Realm.Configuration.defaultConfiguration.fileURL!)

        let value = "leave-fd8115791a3c402029-fe6116757c6406757114-ff2b11797461-fe871372726d067a73-fef715737c670c"
        let realm = try! Realm()
        try! realm.write {
            let contact = EdoContact()
            contact.displayName = value
            realm.add(contact)
        }
        if let contact = realm.objects(EdoContact.self).getContactByDisplayName(value) {
            // Successfully prints the 'EdoContact' object that was added
            print(contact)
        }
        return true
    }
}

As you can see, I can't reproduce the issue you're seeing with Realm Swift 2.6.2.

@youbing
Copy link

youbing commented Apr 26, 2017

Please test it on device. This issue doesn't happen on simulator. When the app is running, break the app, the call stack can be:

#0	0x00000001028cf27c in realm::IndexArray::index_string_all_ins(realm::StringData, realm::Column<long long>&, realm::ColumnBase*) const ()
#1	0x000000010293e9c0 in realm::StringNode<realm::EqualIns>::_search_index_init() ()
#2	0x000000010293defc in realm::StringNodeEqualBase::init() ()
#3	0x00000001028e9544 in realm::Query::init() const ()
#4	0x00000001028e9668 in realm::Query::find_all(realm::TableViewBase&, unsigned long, unsigned long, unsigned long) const ()
#5	0x00000001028e89a8 in realm::Query::find_all(unsigned long, unsigned long, unsigned long) ()
#6	0x00000001026df698 in realm::Results::update_tableview(bool) ()
#7	0x00000001026dfd54 in realm::Results::first() ()
#8	0x000000010276e50c in -[RLMResults firstObject] ()
#9	0x0000000102cb80d4 in Results.first.getter ()
#10	0x00000001008f33cc in static EmailDAL.getContactByDisplayName(String?) -> EdoContact? at /Mail/EmailContactsDB.swift:15
#11	0x00000001008f4274 in static EmailDAL.getOrAddOrUpdateContactItem(inDB : EmailDB, displayName : String?, email : String?, weight : Int) -> EdoContactItem? at /Mail/EmailContactsDB.swift:52



#0	0x0000000102812ea0 in realm::SlabAlloc::do_translate(unsigned long) const ()
#1	0x00000001028cf688 in realm::IndexArray::index_string_all_ins(realm::StringData, realm::Column<long long>&, realm::ColumnBase*) const ()
#2	0x000000010293e9c0 in realm::StringNode<realm::EqualIns>::_search_index_init() ()
#3	0x000000010293defc in realm::StringNodeEqualBase::init() ()
#4	0x00000001028e9544 in realm::Query::init() const ()
#5	0x00000001028e9668 in realm::Query::find_all(realm::TableViewBase&, unsigned long, unsigned long, unsigned long) const ()
#6	0x00000001028e89a8 in realm::Query::find_all(unsigned long, unsigned long, unsigned long) ()
#7	0x00000001026df698 in realm::Results::update_tableview(bool) ()
#8	0x00000001026dfd54 in realm::Results::first() ()
#9	0x000000010276e50c in -[RLMResults firstObject] ()
#10	0x0000000102cb80d4 in Results.first.getter ()
#11	0x00000001008f33cc in static EmailDAL.getContactByDisplayName(String?) -> EdoContact? at /Users/youbing/WORK/EasilyDo/Veyron_Mas/Mail/Mail/EmailContactsDB.swift:15

@kishikawakatsumi
Copy link
Contributor

kishikawakatsumi commented Apr 26, 2017

@shuhaodo @youbing I also tried to run JP's code with the device, but the crash didn't reproduce. It is very hard to reproduce the issue with the information you shared. EdoContact class cannot be compiled because there is no definition of EdoContactItem. We don't know what kind of data is stored in Realm, what is search word, and so on.

Could you please send us a reproducible sample code or an entire project? If you cannot publish your code, please send it to help@realm.io.

@youbing
Copy link

youbing commented Apr 27, 2017

Can you please insert some items into EdoContact? At least, there should be an item whose displayName value is "leave-fd8115791a3c402029-fe6116757c6406757114-ff2b11797461-fe871372726d067a73-fef715737c670c". Then you can search the item which has the displayName value - "leave-fd8115791a3c402029-fe6116757c6406757114-ff2b11797461-fe871372726d067a73-fef715737c670c."

kishikawakatsumi added a commit that referenced this issue Apr 27, 2017
…operty has long string that contains number

Original issue is here #4895

If objects has primary key property, then the property has "1111111111111111111111111111111" and "1111111111111111111111111111112",
then case-insensitive search fails (or taking very long time?) if search query is "name == [c]%@", "1111111111111111111111111111111"

If the property isn't primary key, the query succeeds.
If the property doesn't have number like "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", the query succeeds.
If the property is a little bit shorter, the query succeeds after a long time.
@kishikawakatsumi
Copy link
Contributor

@youbing Thank you very much. I can reproduce the issue.
The small reproducible sample code is the following. We're going to investigate and fix the issue soon.

let value = "1111111111111111111111111111111"
let realm = try! Realm()
try! realm.write {
    let obj1 = TestObj()
    obj1.name = value
    realm.add(obj1)

    let obj2 = TestObj()
    obj2.name = "1111111111111111111111111111112"
    realm.add(obj2)
}
if let obj = realm.objects(TestObj.self).filter("name = [c]%@", "1111111111111111111111111111111").first {
    print(obj)
}

class TestObj: Object {
    dynamic var name = ""

    override static func primaryKey() -> String? {
        return "name"
    }
}

@bdash
Copy link
Contributor

bdash commented Apr 27, 2017

Presumably a core issue due to @danielpovlsen's recent work?

@kishikawakatsumi
Copy link
Contributor

@jpsim @bdash @tgoyne

A small reproducible smaple code is above. Also failing test case is here 68bb564

If objects has primary key property, then the property has "1111111111111111111111111111111" and "1111111111111111111111111111112",
then case-insensitive search fails (or taking very long time?) if search query is "name == [c]%@", "1111111111111111111111111111111"

If the property isn't primary key, the query succeeds.
If the property doesn't have number like "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", the query succeeds.
If the property is a little bit shorter, the query succeeds after a long time.

Maybe the query takes so long time. It depends on what the property value is. The backtrace while querying is the following.

Traceback (most recent call last):
  File "/Users/katsumi/Library/Application Support/Realm/rlm_lldb.py", line 210, in RLMResults_SummaryProvider
    return 'Unevaluated query on ' + class_name
TypeError: cannot concatenate 'str' and 'NoneType' objects
* thread #1: tid = 0x3fbc2e, 0x00000001069586a9 Realm`realm::BpTree<long long>::LeafValueInserter::leaf_insert(leaf_mem=(m_addr = "", m_ref = 106102881445760), parent=0x00007fff59477200, ndx_in_parent=868, alloc=0x0000000106fb4720, ndx_in_leaf=18446744073709551615, state=0x00007fff59477420) + 121 at bptree.hpp:864, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
  * frame #0: 0x00000001069586a9 Realm`realm::BpTree<long long>::LeafValueInserter::leaf_insert(leaf_mem=(m_addr = "", m_ref = 106102881445760), parent=0x00007fff59477200, ndx_in_parent=868, alloc=0x0000000106fb4720, ndx_in_leaf=18446744073709551615, state=0x00007fff59477420) + 121 at bptree.hpp:864
    frame #1: 0x0000000106957e50 Realm`unsigned long realm::BpTreeNode::bptree_append<realm::BpTree<long long>::LeafValueInserter>(this=0x00007fff59477200, state=0x00007fff59477420) + 208 at bptree.hpp:559
    frame #2: 0x0000000106957f16 Realm`unsigned long realm::BpTreeNode::bptree_append<realm::BpTree<long long>::LeafValueInserter>(this=0x00006100000d19c0, state=0x00007fff59477420) + 406 at bptree.hpp:566
    frame #3: 0x0000000106e0fc7e Realm`realm::IndexArray::index_string_all_ins(realm::StringData, realm::Column<long long>&, realm::ColumnBase*) const + 3518
    frame #4: 0x0000000106eaab5d Realm`realm::StringNode<realm::EqualIns>::_search_index_init() + 381
    frame #5: 0x0000000106ea9e49 Realm`realm::StringNodeEqualBase::init() + 425
    frame #6: 0x0000000106e2ec2f Realm`realm::Query::init() const + 47
    frame #7: 0x0000000106e2ed51 Realm`realm::Query::find_all(realm::TableViewBase&, unsigned long, unsigned long, unsigned long) const + 81
    frame #8: 0x0000000106e2e015 Realm`realm::Query::find_all(unsigned long, unsigned long, unsigned long) + 85
    frame #9: 0x00000001069e0d5e Realm`realm::Results::update_tableview(this=0x00007ff9240063b8, wants_notifications=true) + 318 at results.cpp:279
    frame #10: 0x00000001069e1ca7 Realm`realm::Results::first(this=0x00007ff9240063b8) + 423 at results.cpp:217
    frame #11: 0x0000000106b96e5c Realm`-[RLMResults firstObject]::$_4::operator(this=0x00007fff59477b58)() const + 44 at RLMResults.mm:231
    frame #12: 0x0000000106b92feb Realm`auto translateErrors<-[RLMResults firstObject]::$_4>(f=0x00007fff59477b58, aggregateMethod=0x0000000000000000) + 59 at RLMResults.mm:108
    frame #13: 0x0000000106b92edd Realm`::-[RLMResults firstObject](self=0x00007ff9240063b0, _cmd="firstObject") + 45 at RLMResults.mm:231
    frame #14: 0x00000001074016db RealmSwift`Results.first.getter(self=0x000060000003b780) + 59 at Results.swift:163
    frame #15: 0x000000010678ecbb SampleApp`AppDelegate.application(application=0x00007ff921e00490, launchOptions=nil, self=0x00006180000b9e00) -> Bool + 571 at AppDelegate.swift:132
    frame #16: 0x000000010678f404 SampleApp`@objc AppDelegate.application(UIApplication, didFinishLaunchingWithOptions : [UIApplicationLaunchOptionsKey : Any]?) -> Bool + 180 at AppDelegate.swift:0
    frame #17: 0x0000000107f0a3c2 UIKit`-[UIApplication _handleDelegateCallbacksWithOptions:isSuspended:restoreState:] + 290
    frame #18: 0x0000000107f0bd47 UIKit`-[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 4236
    frame #19: 0x0000000107f120ed UIKit`-[UIApplication _runWithMainScene:transitionContext:completion:] + 1731
    frame #20: 0x0000000107f0f26d UIKit`-[UIApplication workspaceDidEndTransaction:] + 188
    frame #21: 0x00000001106b36cb FrontBoardServices`__FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 24
    frame #22: 0x00000001106b3544 FrontBoardServices`-[FBSSerialQueue _performNext] + 189
    frame #23: 0x00000001106b38cd FrontBoardServices`-[FBSSerialQueue _performNextFromRunLoopSource] + 45
    frame #24: 0x000000010a092761 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
    frame #25: 0x000000010a07798c CoreFoundation`__CFRunLoopDoSources0 + 556
    frame #26: 0x000000010a076e76 CoreFoundation`__CFRunLoopRun + 918
    frame #27: 0x000000010a076884 CoreFoundation`CFRunLoopRunSpecific + 420
    frame #28: 0x0000000107f0daea UIKit`-[UIApplication _run] + 434
    frame #29: 0x0000000107f13c68 UIKit`UIApplicationMain + 159
    frame #30: 0x000000010678fe0f SampleApp`main + 111 at AppDelegate.swift:111
    frame #31: 0x000000010b6e168d libdyld.dylib`start + 1

@bdash
Copy link
Contributor

bdash commented Apr 27, 2017

I filed an issue against core with a core test case and some further analysis.

@youbing
Copy link

youbing commented Apr 28, 2017

@kishikawakatsumi Ok. Thanks. Looking forward to the new build.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Blocked This issue is blocked by another issue T-Bug
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants