My app is experiencing a crash during the sort on line 2 below. This happens inside a method on a ViewController
when a NSNotification
comes in to trigger the sort and refresh the display.
My understanding is that the devices
array on line 1 is a new array created from the dictionary, so accessing the elements is tread safe, even though the dictionary might change.
Even if the serialID
(a String?
) changes during the sort, I would not think this could cause a crash.
What am I missing? What could possibly cause a bad access inside the sort?
1: var devices = deviceDict.values()
2: let sortedDevices = devices.sorted(by: { ($0.serialID ?? "") < ($1.serialID ?? "") })
I have no known way to reproduce this. I have only seen it in crash reports like below.
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000f9a70f7c0
VM Region Info: 0xf9a70f7c0 is not in any region. Bytes after previous region: 55741315009 Bytes before following region: 630130752
REGION TYPE START - END [ VSIZE] PRT/MAX SHRMOD REGION DETAIL
MALLOC_NANO 280000000-2a0000000 [512.0M] rw-/rwx SM=COW
---> GAP OF 0xd20000000 BYTES
commpage (reserved) fc0000000-1000000000 [ 1.0G] ---/--- SM=NUL ...(unallocated)
Termination Signal: Segmentation fault: 11
Termination Reason: Namespace SIGNAL, Code 0xb
Terminating Process: exc handler [4510]
Triggered by Thread: 9
...
Thread 9 Crashed:
0 libobjc.A.dylib 0x00000001a6ae2dd0 objc_release 16 (objc-runtime-new.h:1589)
1 OAPP 0x00000001044c35e4 specialized UnsafeMutableBufferPointer._stableSortImpl(by:) 364 (DeviceViewController.swift:0)
2 OAPP 0x00000001044c17f0 specialized MutableCollection<>.sort(by:) 100
2 OAPP 0x00000001044baf60 DeviceViewController.generateDeviceList() 1644
4 OAPP 0x00000001044b67ac DeviceViewController.refreshDeviceTableView() 524 (DeviceViewController.swift:655)
5 OAPP 0x00000001044bb7a4 DeviceViewController.handleSyncStateNotification(_:) 680 (DeviceViewController.swift:821)
...
```
CodePudding user response:
devices
is not an Array. It's a Dictionary.Values
. It's a lazy view into deviceDict.
But none of that matters. Mutable access to deviceDict on multiple threads is always undefined behavior. If it's possible for serialID
to change on another thread without some kind of synchronization, you've already made an error. The deviceDict.values()
line would be invalid; having nothing to do with later accesses to it.
But of course the sorted
line is more likely to be where that undefined behavior bites you, since it's going to walk a lot of values, which might be moved in memory if deviceDict
has to resize or otherwise modify its backing storage.
CodePudding user response:
deviceDict.values()
is not an Array, it's another kind of collection that's a projection over the dictionary storage. And if that dictionary is a mutable one, that gets changes by another thread during the sorting, then, well, you can get crashes.
If you want to make sure you own a local copy of the dictionary values, then you can create an array out of them:
var devices = Array(deviceDict.values())
However that code is also not race free, as the dictionary mutation can happen while the array is constructed.
So best if you synchronize the accesses over the dictionary, in order to avoid any potential data races.