Fatal error: Can't compare indices belonging to different collections
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)
}
}
}
}`
Same happens for aisShips.forEach { (mmsi: String, aisShip: Aisdata) ... }
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.
I'm also getting this. Has there been any fixes for this?
@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.
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)
}
}
}
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.
For all those who are on iOS 13+ do check out actors, it will help in removing the dependency on our good old DispatchQueue.
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)
}
}
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.