Thread-Safe-Dictionary icon indicating copy to clipboard operation
Thread-Safe-Dictionary copied to clipboard

Fatal error: Can't compare indices belonging to different collections

Open mikon63 opened this issue 4 years ago • 9 comments

I am getting a strange runtime error after changing the aisShips Dictionary to the ThreadSafeDictionary:

Can't compare indices belonging to different collections

Following function is called by timer every 120 seconds. Every 20...30 minutes the app will crash with the above error. Error is caused by for (mmsi, aisShip) in aisShips Given the time interval between crashes I suspect that the aisShips.removeValue(...) statement causes the trouble by removing a dictionary element from the dictionary. This was not an issue with the standard Dictionary.

`@objc func onAisPrune(_ timer: Timer) { let currentdate = Date()

    for (mmsi, aisShip) in aisShips
    {
        if currentdate.timeIntervalSince(aisShip.aisdate) > 20 * 60
        {
            nmea.dbLock.wait()
            aisShips.removeValue(forKey: mmsi)
            aisTracks.removeValue(forKey: mmsi)
            nmea.dbLock.signal()
            
            DispatchQueue.main.async {
                MapFactory.shared.removeAisTarget(mmsi: mmsi)
            }
            
        }
    }
}`

mikon63 avatar Aug 26 '21 07:08 mikon63

Same happens for aisShips.forEach { (mmsi: String, aisShip: Aisdata) ... }

mikon63 avatar Aug 26 '21 08:08 mikon63

yep we have a very similar implementation for our thread safe dictionary, same error. Was looking to see if anyone ran into that and solved it.

chrisvoronin avatar Sep 29 '21 21:09 chrisvoronin

I'm also getting this. Has there been any fixes for this?

macaaw avatar Jan 11 '23 09:01 macaaw

@mikon63 @macaaw apologies for the long delay in replying, are you sure removeValue is the culprit? if so then ill try to make time to add a thread safe implementation for that as well.

iThink32 avatar Jan 13 '23 13:01 iThink32

I believe the error results from traversing the dictionary and within this loop removing items, though I can't be absolutely sure. I found a workaround by writing the elements I need to remove in an array first and then traversing the new array to remove elements in the dictionary, so I am actually working with two independent collections now.

@objc func onAisPrune(_ timer: Timer) { let currentdate = Date() var mmsiToRemove : [String] = []

    mmsiToRemove = aisShips.filter({ (mmsi: String, aisShip: Aisdata) in
        return currentdate.timeIntervalSince(aisShip.aisdate) > 20 * 60
    }).map({ (mmsi: String, aisShip: Aisdata) -> String in
        return mmsi
    })

    for mmsi in mmsiToRemove
    {
        nmea.dbLock.wait()
        aisShips.removeValue(forKey: mmsi)
        aisTracks.removeValue(forKey: mmsi)
        nmea.dbLock.signal()
        
        DispatchQueue.main.async {
            MapFactory.shared.removeAisTarget(mmsi: mmsi)
        }
    }
}

mikon63 avatar Jan 13 '23 13:01 mikon63

Unfortunately guys I've tried creating an iterator and making that thread safe but that too doesn't work. As suggested the issue is in modifying a dictionary while iterating the same. Thinking about fixes will update you on the same.

iThink32 avatar Feb 15 '23 12:02 iThink32

For all those who are on iOS 13+ do check out actors, it will help in removing the dependency on our good old DispatchQueue.

iThink32 avatar Feb 15 '23 12:02 iThink32

I got this too. Try solution by adding code:

    public func forEach(_ body: ((key: V, value: T)) throws -> Void) rethrows {
        try concurrentQueue.sync {
            try dictionary.forEach(body)
        }
    }

brandFromNSK avatar Oct 02 '23 14:10 brandFromNSK

The removeValue and removeAll funds do an async dispatch, rather than sync. Could that be an issue? It's easy to switch to code to sync and try reproducing it.

nikbhatt-cu avatar Oct 09 '23 16:10 nikbhatt-cu