Prevention of race conditions in the system
import Foundation
class SharedResource {
var data: String = ""
}
class WorkerThread: Thread {
let sharedResource: SharedResource
init(sharedResource: SharedResource) {
self.sharedResource = sharedResource
}
override func main() {
if !sharedResource.data.isEmpty {
print("Data: \\(sharedResource.data)")
} else {
sharedResource.data = "New Data"
}
}
}
let sharedResource = SharedResource()
let thread1 = WorkerThread(sharedResource: sharedResource)
let thread2 = WorkerThread(sharedResource: sharedResource)
thread1.start()
thread2.start()
In the above code, we have a
SharedResource class that contains a
data property. We have a
WorkerThread class that inherits from
Thread. Each
WorkerThread instance shares the same
SharedResource instance.
When the
main method of
WorkerThread is called, it checks if
data property of the shared resource is empty. If it's not, it prints the data, otherwise, it sets the data to "New Data".
The vulnerability lies in the fact that there is no mechanism in place to ensure that the operations (checking if
data is empty and setting
data to "New Data") on the shared resource are atomic. This can lead to a race condition.
For example, consider the scenario where
thread1 checks if
data is empty and finds it to be true. Before
thread1 can set
data to "New Data",
thread2 may also check if
data is empty and find it to be true. As a result, both threads may end up setting
data to "New Data", which could lead to unexpected behavior.
import Foundation
class SharedResource {
private var internalData: String = ""
private let accessQueue = DispatchQueue(label: "com.example.SharedResource")
var data: String {
get {
return accessQueue.sync { internalData }
}
set {
accessQueue.sync { internalData = newValue }
}
}
}
class WorkerThread: Thread {
let sharedResource: SharedResource
init(sharedResource: SharedResource) {
self.sharedResource = sharedResource
}
override func main() {
if !sharedResource.data.isEmpty {
print("Data: \\(sharedResource.data)")
} else {
sharedResource.data = "New Data"
}
}
}
let sharedResource = SharedResource()
let thread1 = WorkerThread(sharedResource: sharedResource)
let thread2 = WorkerThread(sharedResource: sharedResource)
thread1.start()
thread2.start()
The original code had a race condition vulnerability where two threads could access and modify the shared resource at the same time. This could lead to unexpected behavior if the threads do not execute in the expected order.
The fixed code uses a DispatchQueue to synchronize access to the shared resource. This ensures that only one thread can access the shared resource at a time, eliminating the race condition. The DispatchQueue is a first-in, first-out queue to which work can be submitted. Work submitted to dispatch queues are executed on a pool of threads fully managed by the system. No guarantee is made as to the thread on which a task executes.
In the
SharedResource class, a private DispatchQueue
accessQueue is created. The
data property is modified to use this queue when getting or setting the
internalData value. This ensures that these operations are always performed in the order they were received, and only one at a time.
In the
WorkerThread class, the
main method is unchanged. It will either print the current data if it exists, or set new data if it does not. However, because of the changes to the
SharedResource class, these operations are now thread-safe.
Finally, two
WorkerThread instances are created and started. These threads will now safely share the
SharedResource instance without risk of a race condition.