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.