Thank you Jordan.

I’ve filed a bug at bugs.swift.org <http://bugs.swift.org/>: 
https://bugs.swift.org/browse/SR-6543 <https://bugs.swift.org/browse/SR-6543>.


> On Dec 6, 2017, at 2:47 AM, Jordan Rose <jordan_r...@apple.com> wrote:
> 
> 
> 
>> On Dec 5, 2017, at 16:24, Romain Jacquinot <rjacqui...@me.com 
>> <mailto:rjacqui...@me.com>> wrote:
>> 
>>> Ah, it's a Swift bug in the SynchronizedArray / MyClass cases
>> 
>> If I remove the intermediate copy in the myArray getter, should it — without 
>> Swift bug — also return a unique copy?
>> 
>> public var myArray: Array<Int> {
>>     lock.lock()
>>     defer { lock.unlock() }
>>     _myArray
>> }
> 
> Yes. As soon as you treat it as a value (including returning it), it should 
> be independent. The assignment to a local variable doesn't really add 
> anything interesting, and the optimizer will throw it out.
> 
>> By the way, here is a simpler sample code where TSan also detects a race 
>> condition:
>> 
>> // Code sample A
>> 
>> var array: [Int] = [1, 2, 3]
>> 
>> let iterations = 1_000_000
>> 
>> var copy1 = array
>> q1.async {
>>     for i in 0..<iterations {
>>         copy1.append(i)
>>     }
>> }
>> 
>> var copy2 = array
>> q2.async {
>>     for i in 0..<iterations {
>>         copy2.append(i)
>>     }
>> }
>> 
>> var copy3 = array
>> q3.async {
>>     for i in 0..<iterations {
>>         copy3.append(i)
>>     }
>> }
>> 
>> for i in 0..<iterations {
>>     array.append(i)
>> }
>> 
>> q1.sync {}
>> q2.sync {}
>> q3.sync {}
>> NSLog("done")
> 
> Yes, this would be failing for the same reason (didn't test it). The 
> mutations are operating on different Array variables, but they start off 
> sharing the same storage, and that means we can get into the same situation 
> where one queue thinks it can modify the storage in place while another queue 
> is still copying the initial contents.
> 
> 
>> I can avoid the race condition to occur by using a capture list:
>> 
>> // Code sample B
>> 
>> var array: [Int] = [1, 2, 3]
>> 
>> let iterations = 1_000_000
>> 
>> q1.async { [array] in
>>     for i in 0..<iterations {
>>         var copy = array
>>         copy.append(i)
>>     }
>> }
>> 
>> q2.async { [array] in
>>     for i in 0..<iterations {
>>         var copy = array
>>         copy.append(i)
>>     }
>> }
>> 
>> q3.async { [array] in
>>     for i in 0..<iterations {
>>         var copy = array
>>         copy.append(i)
>>     }
>> }
>> 
>> for i in 0..<iterations {
>>     array.append(i)
>> }
>> 
>> q1.sync {}
>> q2.sync {}
>> q3.sync {}
>> NSLog("done")
>> 
>> or by using a constant copy, which, if I’m not wrong, should behave the same 
>> way than the capture list:
>> 
>> // Code sample C
>> 
>> var array: [Int] = [1, 2, 3]
>> 
>> let iterations = 1_000_000
>> 
>> let copy1 = array
>> q1.async {
>>     var copy1 = copy1
>>     for i in 0..<iterations {
>>         copy1.append(i)
>>     }
>> }
>> 
>> 
>> let copy2 = array
>> q2.async {
>>     var copy2 = copy2
>>     for i in 0..<iterations {
>>         copy2.append(i)
>>     }
>> }
>> 
>> let copy3 = array
>> q3.async {
>>     var copy3 = copy3
>>     for i in 0..<iterations {
>>         copy3.append(i)
>>     }
>> }
>> 
>> for _ in 0..<iterations {
>>     array.append(array.count)
>> }
>> 
>> q1.sync {}
>> q2.sync {}
>> q3.sync {}
>> NSLog("done")
> 
> Yep, these two are equivalent, at least as far as this issue goes. This 
> dodges the issue because the capture or the 'let' keeps an extra reference to 
> the original array at all times, meaning that none of the mutations are ever 
> done in-place.
> 
> Thank you again for catching this! (It's also possible we've already fixed it 
> on master, but it's still worth having a bug report so we can add a 
> regression test.)
> 
> Jordan

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users

Reply via email to