Skip to content

Commit 42309df

Browse files
committed
[Basic] Introduce a Condition wrapper.
- This is just so it is clear what types we want to use for synchronization at the layers above Basic...
1 parent 2164da1 commit 42309df

File tree

8 files changed

+136
-26
lines changed

8 files changed

+136
-26
lines changed

Sources/Basic/Condition.swift

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
This source file is part of the Swift.org open source project
3+
4+
Copyright 2016 Apple Inc. and the Swift project authors
5+
Licensed under Apache License v2.0 with Runtime Library Exception
6+
7+
See http://swift.org/LICENSE.txt for license information
8+
See http://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
*/
10+
11+
import Foundation
12+
13+
// FIXME: Temporary compatibility shims.
14+
#if !os(macOS)
15+
private typealias NSCondition = Foundation.Condition
16+
#endif
17+
18+
/// A simple condition wrapper.
19+
public struct Condition {
20+
private let _condition = NSCondition()
21+
22+
/// Create a new condition.
23+
public init() {}
24+
25+
/// Wait for the condition to become available.
26+
public func wait() {
27+
_condition.wait()
28+
}
29+
30+
/// Signal the availability of the condition (awake one thread waiting on
31+
/// the condition).
32+
public func signal() {
33+
_condition.signal()
34+
}
35+
36+
/// Broadcast the availability of the condition (awake all threads waiting
37+
/// on the condition).
38+
public func broadcast() {
39+
_condition.broadcast()
40+
}
41+
42+
/// A helper method to execute the given body while condition is locked.
43+
public func whileLocked<T>(_ body: @noescape () throws -> T) rethrows -> T {
44+
_condition.lock()
45+
defer { _condition.unlock() }
46+
return try body()
47+
}
48+
}

Sources/Basic/Lock.swift

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ import Foundation
1212

1313
// FIXME: Temporary compatibility shims.
1414
#if !os(macOS)
15-
public typealias NSLock = Foundation.Lock
16-
public typealias NSCondition = Foundation.Condition
15+
private typealias NSLock = Foundation.Lock
1716
#endif
1817

1918
/// A simple lock wrapper.
@@ -31,12 +30,3 @@ public struct Lock {
3130
return try body()
3231
}
3332
}
34-
35-
public extension NSCondition {
36-
/// A helper method to execute the given body while condition is locked.
37-
public func whileLocked<T>(_ body: @noescape () throws -> T) rethrows -> T {
38-
lock()
39-
defer { unlock() }
40-
return try body()
41-
}
42-
}

Sources/Basic/SynchronizedQueue.swift

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,19 @@
88
See http://swift.org/CONTRIBUTORS.txt for Swift project authors
99
*/
1010

11-
import Foundation
12-
1311
/// This class can be used as a shared queue between multiple threads providing
1412
/// thread safe APIs.
1513
public final class SynchronizedQueue<Element> {
16-
1714
/// Storage for queued elements.
1815
private var storage: [Element]
1916

2017
/// Condition variable to block the thread trying dequeue and queue is empty.
21-
private var notEmptyCondition: NSCondition
18+
private var notEmptyCondition: Condition
2219

2320
/// Create a default instance of queue.
2421
public init() {
2522
storage = []
26-
notEmptyCondition = NSCondition()
23+
notEmptyCondition = Condition()
2724
}
2825

2926
/// Safely enqueue an element to end of the queue and signals a thread blocked on dequeue.

Sources/Basic/Thread.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,15 @@ final public class Thread {
2020
private var thread: ThreadImpl!
2121

2222
/// Condition variable to support blocking other threads using join when this thread has not finished executing.
23-
private var finishedCondition: NSCondition
23+
private var finishedCondition: Condition
2424

2525
/// A boolean variable to track if this thread has finished executing its task.
2626
private var finished: Bool
2727

2828
/// Creates an instance of thread class with closure to be executed when start() is called.
2929
public init(task: @escaping () -> Void) {
3030
finished = false
31-
finishedCondition = NSCondition()
31+
finishedCondition = Condition()
3232

3333
// Wrap the task with condition notifying any other threads blocked due to this thread.
3434
// Capture self weakly to avoid reference cycle. In case Thread is deinited before the task

Tests/BasicTests/ConditionTests.swift

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
This source file is part of the Swift.org open source project
3+
4+
Copyright 2016 Apple Inc. and the Swift project authors
5+
Licensed under Apache License v2.0 with Runtime Library Exception
6+
7+
See http://swift.org/LICENSE.txt for license information
8+
See http://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
*/
10+
11+
import XCTest
12+
13+
import Basic
14+
15+
class ConditionTests: XCTestCase {
16+
func testSignal() {
17+
let condition = Condition()
18+
var waiting = false
19+
var doneWaiting = false
20+
let thread = Thread {
21+
condition.whileLocked{
22+
waiting = true
23+
condition.wait()
24+
doneWaiting = true
25+
}
26+
}
27+
thread.start()
28+
29+
// Wait for the thread to start waiting
30+
while !condition.whileLocked({ waiting }) {}
31+
32+
// Signal and wait for the thread to complete.
33+
condition.whileLocked{
34+
condition.signal()
35+
}
36+
thread.join()
37+
38+
// Wait for the thread to complete.
39+
XCTAssert(doneWaiting)
40+
}
41+
42+
func testBroadcast() {
43+
let condition = Condition()
44+
var waiting = [false, false]
45+
var doneWaiting = [false, false]
46+
let threads = [0, 1].map { i -> Thread in
47+
let thread = Thread {
48+
condition.whileLocked{
49+
waiting[i] = true
50+
condition.wait()
51+
doneWaiting[i] = true
52+
}
53+
}
54+
thread.start()
55+
return thread
56+
}
57+
58+
// Wait for the thread to start waiting
59+
while !(condition.whileLocked({ waiting[0] }) || condition.whileLocked({ waiting[1] })) {}
60+
61+
// Signal and wait for the thread to complete.
62+
condition.whileLocked{
63+
condition.broadcast()
64+
}
65+
threads.forEach{ $0.join() }
66+
67+
// Wait for the thread to complete.
68+
XCTAssert(doneWaiting[0])
69+
XCTAssert(doneWaiting[1])
70+
}
71+
72+
static var allTests = [
73+
("testSignal", testSignal),
74+
("testBroadcast", testBroadcast),
75+
]
76+
}

Tests/BasicTests/SynchronizedQueueTests.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,11 @@
88
See http://swift.org/CONTRIBUTORS.txt for Swift project authors
99
*/
1010

11-
import Foundation
1211
import XCTest
1312

1413
import Basic
1514

1615
class SyncronizedQueueTests: XCTestCase {
17-
1816
func testSingleProducerConsumer() {
1917
let queue = SynchronizedQueue<Int?>()
2018
let queueElements = Set(0..<10)
@@ -92,7 +90,7 @@ class SyncronizedQueueTests: XCTestCase {
9290

9391
var consumed = Set<Int>()
9492

95-
let canProduceCondition = NSCondition()
93+
let canProduceCondition = Condition()
9694
// Initially we should be able to produce.
9795
var canProduce = true
9896

Tests/BasicTests/ThreadTests.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,19 +51,19 @@ class ThreadTests: XCTestCase {
5151
}
5252

5353
func testNotDeinitBeforeExecutingTask() {
54-
let finishedCondition = NSCondition()
54+
let finishedCondition = Condition()
5555
var finished = false
5656

5757
Thread {
5858
finished = true
5959
finishedCondition.signal()
6060
}.start()
6161

62-
finishedCondition.lock()
63-
while !finished {
64-
finishedCondition.wait()
62+
finishedCondition.whileLocked{
63+
while !finished {
64+
finishedCondition.wait()
65+
}
6566
}
66-
finishedCondition.unlock()
6767

6868
XCTAssertTrue(finished)
6969
}

Tests/BasicTests/XCTestManifests.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public func allTests() -> [XCTestCaseEntry] {
1515
return [
1616
testCase(ByteStringTests.allTests),
1717
testCase(CollectionAlgorithmsTests.allTests),
18+
testCase(ConditionTests.allTests),
1819
testCase(FileAccessTests.allTests),
1920
testCase(FileSystemTests.allTests),
2021
testCase(GraphAlgorithmsTests.allTests),

0 commit comments

Comments
 (0)