Skip to content

Commit fd30b72

Browse files
author
Dave Abrahams
committed
[stdlib] Fix a race
Lock-free programming is almost always a bug. Fixes <rdar://25398370> Data Race in StringBuffer.append (found by TSan)
1 parent 684bddf commit fd30b72

File tree

3 files changed

+15
-19
lines changed

3 files changed

+15
-19
lines changed

stdlib/public/core/StringCore.swift

+4-1
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,10 @@ public struct _StringCore {
418418
/// the existing buffer's storage.
419419
mutating func _claimCapacity(
420420
_ newSize: Int, minElementWidth: Int) -> (Int, UnsafeMutableRawPointer?) {
421-
if _fastPath((nativeBuffer != nil) && elementWidth >= minElementWidth) {
421+
if _fastPath(
422+
(nativeBuffer != nil) && elementWidth >= minElementWidth
423+
&& isKnownUniquelyReferenced(&_owner)
424+
) {
422425
var buffer = nativeBuffer!
423426

424427
// In order to grow the substring in place, this _StringCore should point

test/stdlib/NewStringAppending.swift

+1-7
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ func repr(_ x: NSString) -> String {
3838
func repr(_ x: _StringCore) -> String {
3939
if x.hasContiguousStorage {
4040
if let b = x.nativeBuffer {
41-
var offset = x.elementWidth == 2
41+
let offset = x.elementWidth == 2
4242
? b.start - UnsafeMutableRawPointer(x.startUTF16)
4343
: b.start - UnsafeMutableRawPointer(x.startASCII)
4444
return "Contiguous(owner: "
@@ -110,14 +110,8 @@ print("\(repr(s))")
110110
s += "C"
111111
print("\(repr(s))")
112112

113-
/// An additional reference to the same buffer doesn't, by itself,
114-
/// impede the use of available capacity
115113
var s1 = s
116114

117-
// CHECK-NEXT: String(Contiguous(owner: .native@[[buffer4]][0...50], capacity = 96))
118-
s += "F"
119-
print("\(repr(s))")
120-
121115
// CHECK-NEXT: String(Contiguous(owner: .native@[[buffer4]][0...49], capacity = 96))
122116
print("\(repr(s1))")
123117

validation-test/stdlib/StringSlicesConcurrentAppend.swift

+10-11
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,21 @@ import Glibc
1313
var StringTestSuite = TestSuite("String")
1414

1515
extension String {
16-
var bufferID: UInt {
17-
return unsafeBitCast(_core._owner, to: UInt.self)
18-
}
1916
var capacityInBytes: Int {
2017
return _core.nativeBuffer!.capacity
2118
}
2219
}
2320

24-
// Swift.String has an optimization that allows us to append to a shared string
25-
// buffer. Make sure that it works correctly when two threads try to append to
26-
// different non-shared strings that point to the same shared buffer.
21+
// Swift.String used to hsve an optimization that allowed us to append to a
22+
// shared string buffer. However, as lock-free programming invariably does, it
23+
// introduced a race condition [rdar://25398370 Data Race in StringBuffer.append
24+
// (found by TSan)].
25+
//
26+
// These tests verify that it works correctly when two threads try to append to
27+
// different non-shared strings that point to the same shared buffer. They used
28+
// to verify that the first append could succeed without reallocation even if
29+
// the string was held by another thread, but that has been removed. This could
30+
// still be an effective thread-safety test, though.
2731

2832
enum ThreadID {
2933
case Primary
@@ -79,11 +83,6 @@ func sliceConcurrentAppendThread(_ tid: ThreadID) {
7983
secondaryString = privateString
8084
}
8185
barrier()
82-
if tid == .Primary {
83-
expectTrue(
84-
(privateString.bufferID == sharedString.bufferID) !=
85-
(secondaryString.bufferID == sharedString.bufferID))
86-
}
8786
}
8887
}
8988

0 commit comments

Comments
 (0)