Copy-on-write and data races

For those interested, I've moved the subject to the swift-users list: https://lists.swift.org/pipermail/swift-users/Week-of-Mon-20171204/006682.html


- besides capture lists, what are the correct ways to pass an array between threads?

- for thread-safe classes that expose an array as a property, should I always copy the private array variable before returning it from the public getter? If so, is there any recommended way to force-copy a value-type variable in Swift ?


Thanks.

Accepted Reply

>> is there any recommended way to force-copy a value-type variable in Swift ?


A value is always logically copied by assignment to a new variable. That's what a value type means, and it's as true of arrays as it is of Int values.


However, mutation of a value is not automatically thread-safe, and in general is extremely thread-unsafe. In consequence, copying a value by assigning it (or an equivalent operation, such as passing it as a parameter or returning it as a function's return value) at the same time as mutating the value is dangerous in the extreme. This is as true of Ints as it is of arrays.


The fact that Swift arrays happen to be implemented with an underlying copy-on-write referencing mechanism is completely irrelevant to how you reason about this situation. Thus, for example:


>> should I always copy the private array variable before returning it from the public getter?


there's no point in "manually" copying an array in order to return it as a function value. The return is itself a copy, and it's just as safe (or unsafe) as the manual copy you're considering doing instead.


AFAICT, the discussion in your swift.org thread doesn't contradict this, except by acknowledging a bug in the Swift compiler. In a scenario where your code has apparently ensured that mutation and your copying can't happen at the same time, the bug causes the mutation to trigger a different copy at the same time.


In general, and pretending that the bug is no longer an issue, you should be able to treat arrays in the same way you treat Ints: as long as you do not mutate the value while you are copying it, you don't have to worry about the underlying implementation.

Replies

Which version Swift?


>why does the thread sanitizer never detect a race condition here? However, TSan always detects a race condition


Are you sure both those tools are tuned to the Swift you are using?

In this code:


func mutateArray(_ array: [Int]) {
     var elements = array
     elements.append(1)
}


under the assumption that the input parameter is passed as an array reference of some kind, I can see no circumstance where the array copy within the function can ever be avoided. It's not clear that the compiler needs to call "isKnownUniquelyReferenced" here, since a copy needs to be made regardless of what that method would return.


More generally, I think part of the difficulty is that you're not distinguishing between a race condition in getting the result of "isKnownUniquelyReferenced" (which depends on mutations to the internal state that keeps track of copies) and a race condition in changing the array contents (which depends on mutations to the structure of the array data storage).


This said, I admit I don't have answers to all of your questions. In part, that's because it's unclear how the compiler optimizes the array references. For example, in the "mutateArray" function implementation I just quoted, is the "elements" variable in line 2 a copy of the input (and in that case, is it an immediate copy or a lazy copy?), or is it optimized to be a kind of synonym for the input parameter (which defers the issue of copying to line 3)? I can imagine similar kinds of optimizations choices affecting what happens at lines 30-32 of the final fragment in your original post.


You might get a more informative response on the "swift-users" list over at swift.org.

"More generally, I think part of the difficulty is that you're not distinguishing between a race condition in getting the result of "isKnownUniquelyReferenced" (which depends on mutations to the internal state that keeps track of copies) and a race condition in changing the array contents (which depends on mutations to the structure of the array data storage)."


Indeed, but both race conditions result from the fact the array implements copy-on-write.


I've moved the subject to the swift-users list: https://lists.swift.org/pipermail/swift-users/Week-of-Mon-20171204/006682.html


Thank you.

I would suggest to use GCD, in your class that you would like to make thread-safe: create a serial-queue, and perform all array modifications on that queue.

>> is there any recommended way to force-copy a value-type variable in Swift ?


A value is always logically copied by assignment to a new variable. That's what a value type means, and it's as true of arrays as it is of Int values.


However, mutation of a value is not automatically thread-safe, and in general is extremely thread-unsafe. In consequence, copying a value by assigning it (or an equivalent operation, such as passing it as a parameter or returning it as a function's return value) at the same time as mutating the value is dangerous in the extreme. This is as true of Ints as it is of arrays.


The fact that Swift arrays happen to be implemented with an underlying copy-on-write referencing mechanism is completely irrelevant to how you reason about this situation. Thus, for example:


>> should I always copy the private array variable before returning it from the public getter?


there's no point in "manually" copying an array in order to return it as a function value. The return is itself a copy, and it's just as safe (or unsafe) as the manual copy you're considering doing instead.


AFAICT, the discussion in your swift.org thread doesn't contradict this, except by acknowledging a bug in the Swift compiler. In a scenario where your code has apparently ensured that mutation and your copying can't happen at the same time, the bug causes the mutation to trigger a different copy at the same time.


In general, and pretending that the bug is no longer an issue, you should be able to treat arrays in the same way you treat Ints: as long as you do not mutate the value while you are copying it, you don't have to worry about the underlying implementation.

Thank you. The bug in the Swift compiler confused me regarding how copy-on-write worked in Swift.