Home > Software engineering >  Is the Swift Array sorted method thread safe for a local copy of an array?
Is the Swift Array sorted method thread safe for a local copy of an array?

Time:10-02

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.

  • Related