Is this crash in a Swift script caused by a race condition? - swift

Running the code below usually prints:
hello
Occasionally, it prints:
hello BlockExperiment world
But sometimes I get the following crash:
hello BlockExperiment/usr/bin/swift[0x51f95a4]
/usr/bin/swift[0x51f719e]
/usr/bin/swift[0x51f987c]
/lib/x86_64-linux-gnu/libpthread.so.0(+0x12980)[0x7fc7c0650980]
/usr/lib/swift/linux/libswiftCore.so(+0x40d2c4)[0x7fc7bc9642c4]
/usr/lib/swift/linux/libswiftCore.so(+0x3fecc6)[0x7fc7bc955cc6]
/usr/lib/swift/linux/libswiftCore.so(+0x3fe20b)[0x7fc7bc95520b]
/usr/lib/swift/linux/libswiftCore.so(+0x3e456d)[0x7fc7bc93b56d]
This doesn't seem to break any of the weak/strong/unowned practices that I know of.
Is the problem a basic race condition, where self is being accessed in the print statement after it has already been deallocated?
I can't be sure because in that case, I would expect the usual error message saying the access was to a deallocated instance. If it isn't this, how can you tell and in general debug this type of issue?
And if it is a race condition, why does the print statement get cut off sometimes after "hello"?
Code:
import Foundation
final class BlockRunner {
var block: () -> ()
init(_ b: #escaping () -> ()) { block = b }
func run() { block() }
deinit { print("Deinit: \(self)") }
}
final class BlockExperiment {
func f() {
let queue = DispatchQueue(label: "Queue")
let runner = BlockRunner { print("hello", self, "world") }
queue.asyncAfter(deadline: .now() + 1) { runner.run() }
}
deinit { print("Deinit: \(self)") }
}
do {
let e = BlockExperiment()
e.f()
RunLoop.current.run(until: Date() + 1 + 0.000000000000001)
}

That is definitely a race condition. If you need to print out anything, you need to do it on the main thread. Therefore, always wrap your print statements with something like this:
DispatchQueue.main.async {
print("Whatever")
}
I was bitten by this just recently, when I didn't realize that Process() was running my task in a background thread and I was seeing the exactly same type of log text corruption.

Related

is it possible to swizzle the keyword `throw`?

I was thinking about error handling and I learned about swizzling recently. Swizzling is certainly a tool which shouldn't be used too often, and I think I understand that, but it made me wonder. If whenever an error is thrown, if I wanted to capture the thrown error. Is there a way I could use swizzling or some such in order to intercept the error and log it somewhere without interrupting the flow of the app? I was thinking about possibly swizzling the throws keyword, but that might not work. What tools would be used for this kind of thing?
No you cannot. Many compiler checks depend on the fact that throw "interrupts the flow of the app". For example, if some path of the code throws, then that path doesn't need to return:
func foo(x: Bool) throws -> Int {
if x {
throw someError
} else {
return 1
}
}
Now if throw someError did not "interrupt the flow of the app", what would bar print in the following code?
func bar() throws {
let x = try foo(x: true)
print(x)
}
Another example is guard:
guard let value = somethingThatMayBeNil else {
throw someError
}
print(value.nowICanSafelyAccessThis)
If throw someError above didn't actually "interrupt the flow of the app" and stop running the rest of the method, something really bad is going to happen at print(value.nowICanSafelyAccessThis), because somethingThatMayBeNil is nil, and I'm not even sure value even exists.
The whole point is that throw would unwind the call stack to a point where the error can be caught, and that the code that depends on there not being an error is not executed.
If you want to "capture" the error in some way, you can use a Result. The first example can be turned into:
func foo(x: Bool) -> Result<Int, Error> {
if x {
return Result.failure(someError)
} else {
return Result.success(1)
}
}
func bar() {
let x = foo(x: true)
// now this will print "failure(...)"
print(x)
// and x is of type Result<Int, Error>, rather than Int
switch x {
case .failure(let e):
// log the error e here...
case .success(let theInt):
// do something with theInt...
}
}
You can also use init(catching:) to wrap any throwing call into a Result. Suppose if you can't change foo, then you can do this in bar instead:
func bar() {
let x = Result { try foo(x: true) }
...
The second example can be turned into:
guard let value = somethingThatMayBeNil else {
// assuming the return type is changed appropriately
return Result.failure(someError)
}
print(value.nowICanSafelyAccessThis)
Note that this will still "interrupt the flow of the app", as in the print is still not executed if somethingThatMayBeNil is nil. There is nothing you can do about that.
You could also add a convenient factory method for logging:
extension Result {
static func failureAndLog(_ error: Failure) -> Result {
// do your logging here...
return .failure(error)
}
}
No, you can't swizzle throw. But the Swift runtime has a hook, _swift_WillThrow, that lets you examine an Error at the moment it's about to be thrown. This hook is not a stable API and could be changed or removed in future versions of Swift.
If you're using Swift 5.8, which is included in Xcode 14.3 (in beta release now), you can use the _swift_setWillThrowHandler function to set the _swift_willThrow function:
#_silgen_name("_swift_setWillThrowHandler")
func setWillThrowHandler(
_ handler: (#convention(c) (UnsafeRawPointer) -> Void)?
)
var errors: [String] = []
func tryItOut() {
setWillThrowHandler {
let error = unsafeBitCast($0, to: Error.self)
let callStack = Thread.callStackSymbols.joined(separator: "\n")
errors.append("""
\(error)
\(callStack)
""")
}
do {
throw MyError()
} catch {
print("caught \(error)")
print("errors = \(errors.joined(separator: "\n\n"))")
}
}
Output:
caught MyError()
errors = MyError()
0 iPhoneStudy 0x0000000102a97d9c $s11iPhoneStudy8tryItOutyyFySVcfU_ + 252
1 iPhoneStudy 0x0000000102a97ff0 $s11iPhoneStudy8tryItOutyyFySVcfU_To + 12
2 libswiftCore.dylib 0x000000018c2f4ee0 swift_willThrow + 56
3 iPhoneStudy 0x0000000102a978f8 $s11iPhoneStudy8tryItOutyyF + 160
4 iPhoneStudy 0x0000000102a99740 $s11iPhoneStudy6MyMainV4mainyyFZ + 28
5 iPhoneStudy 0x0000000102a997d0 $s11iPhoneStudy6MyMainV5$mainyyFZ + 12
6 iPhoneStudy 0x0000000102a99f48 main + 12
7 dyld 0x0000000102d15514 start_sim + 20
8 ??? 0x0000000102e11e50 0x0 + 4343275088
9 ??? 0x9f43000000000000 0x0 + 11476016275470155776
If you're using an older Swift (but at least Swift 5.2 I think, which was in Xcode 11.4), you have to access the _swift_willThrow hook directly:
var swift_willThrow: UnsafeMutablePointer<(#convention(c) (UnsafeRawPointer) -> Void)?> {
get {
dlsym(UnsafeMutableRawPointer(bitPattern: -2), "_swift_willThrow")!
.assumingMemoryBound(to: (#convention(c) (UnsafeRawPointer) -> Void)?.self)
}
}
var errors: [String] = []
func tryItOut() {
swift_willThrow.pointee = {
let error = unsafeBitCast($0, to: Error.self)
let callStack = Thread.callStackSymbols.joined(separator: "\n")
errors.append("""
\(error)
\(callStack)
""")
}
do {
throw MyError()
} catch {
print("caught \(error)")
print("errors = \(errors.joined(separator: "\n\n"))")
}
}

Modify an new array then crash

var list: [Int] = []
public func printListValues() {
DispatchQueue.global().async {
while true {
if self.list.count < 10 {
self.list.append(self.list.count)
} else {
self.list.removeAll()
}
}
}
DispatchQueue.global().async {
while true {
let newList = self.list
newList.forEach { debugPrint($0) }
}
}
}
I know array is not thread-safe sometimes. But I have made let value = self.list. And it also crash with the information:
Thread 3: Fatal error: Index out of range
on the line newList.forEach { debugPrint($0) }.
Why is newList not safe. What's the problem?
Array operations are not atomic. When you access the array in your second thread you have to be sure that it isn't in the middle of either the append() or removeAll() operation on the first thread otherwise you could be copying an array that is in an indeterminate state. Similarly one of these two operations could have occurred during the middle of the array copy operation which causes issues where the array changes state while it is being copied. You can fix your code by adding some thread synchronization.
var list: [Int] = []
var mutex = pthread_mutex_t()
public func printListValues() {
pthread_mutex_init(&mutex, nil)
DispatchQueue.global().async {
while true {
if self.list.count < 10 {
pthread_mutex_lock(&self.mutex)
self.list.append(self.list.count)
pthread_mutex_unlock(&self.mutex)
} else {
pthread_mutex_lock(&self.mutex)
self.list.removeAll()
pthread_mutex_unlock(&self.mutex)
}
}
}
DispatchQueue.global().async {
while true {
pthread_mutex_lock(&self.mutex)
let newList = self.list
pthread_mutex_unlock(&self.mutex)
newList.forEach { debugPrint($0) }
}
}
}
#Spads has great information about the problem (though I'd use GCD rather than pthreads to solve it), but it raises the question of how you would discover this if you didn't see the problem right away. The answer is the Thread Sanitizer, which will point you directly to where your problem is. It's a setting on the scheme (or you can pass -sanitizer=thread to swiftc directly).
With that set, you will get the following in Xcode, pointing you directly to the lines that conflict.

How to exit a function scope from inner function using Swift?

With return it is possible to exit the scope of the current function but is it also possible to exit the scope of the outer function that is calling the inner function?
func innerFunction() {
guard 1 == 2 else {
// exit scope of innerFunction and outerFunction
}
}
func outerFunction() {
innerFunction()
print("should be unreachable")
}
There could be one approach using a return value of the inner function that we can check for:
func innerFunction() -> Bool {
guard 1 == 2 else {
return false
}
return true
}
func outerFunction() {
guard innerFunction() else {
return
}
print("should be unreachable")
}
The problem with this approach is that it can clutter your code pretty quickly if the functions become more complicated and you have to use them over and over again.
Consider applying this approach with XCTest. It would look like this:
func testFoobar() {
guard let unwrappedObject = helperFunction() else {
XCTFail("A failure message that can change with each helperFunction call")
return
}
print("should be unreachable when helperFunction already failed")
}
I'd like to have something similar to this:
func testFoobar() {
let unwrappedObject = helperFunction()
print("should be unreachable when helperFunction already failed")
}
This is basically what Swift's error handling does:
func outer() throws
{
try inner()
print("Unreachable")
}
struct DoNotContinue : Error {}
func inner() throws
{
throw DoNotContinue()
}
do { try outer() }
catch _ { print("Skipped the unreachable") }
Note, of course, that the caller still has control over this: it could catch the thrown error itself instead of just exiting.
problem with this approach is that it can clutter your code
There's a much more serious problem with allowing callees to directly exit their callers, and that is that the flow of control very quickly becomes incomprehensible. Imagine that you have a couple of layers of this. Reading the top-level function, you no longer have any clear idea what can happen. You must yourself recurse into every callee's callee to make sure that the original code will continue on its course.

How to check when multiple concurrent threads have finished?

I have code that goes like this:
myArray.forEach { item in
concurentOperation(item)
}
Every item in the array goes through a concurrent operation function, which runs in different threads, I'm not sure exactly which thread or how many threads because the function is from a third party library and out of my control. I need a way to find out once all operations are finished.
How can I do this?
without modifying concurentOperation() this is NOT available, sorry ...
UPDATE for user #Scriptable
next snippet demonstrates, why his solution doesn't work ...
import PlaygroundSupport
import Dispatch
PlaygroundPage.current.needsIndefiniteExecution = true
let pq = DispatchQueue(label: "print", qos: .background)
func dprint(_ items: Any...) {
pq.async {
let r = items.map{ String(describing: $0) }.joined(separator: " ")
print(r)
}
}
func concurrentOperation<T>(item: T) { // dummy items
DispatchQueue.global().async {
// long time operation
for i in 0..<10000 {
_ = sin(Double(i))
}
dprint(item, "done")
}
}
let myArray = [1,2,3,4,5,6,7,8,9,0]
let g = DispatchGroup()
myArray.forEach { (item) in
DispatchQueue.global().async(group: g) {
concurrentOperation(item: item)
}
}
g.notify(queue: DispatchQueue.main) {
dprint("all jobs done???")
}
UPDATE 2
without modifying the code of ConcurrentOperation()
DispatchQueue.global().async(group: g) {
concurrentOperation(item: item)
}
the dispatch group is entered and immediately left because concurrentOperation is the asynchronous function. If it is synchronous then the question has no sense.

Swift closure async order of execution

In my model have function to fetch data which expects completion handler as parameter:
func fetchMostRecent(completion: (sortedSections: [TableItem]) -> ()) {
self.addressBook.loadContacts({
(contacts: [APContact]?, error: NSError?) in
// 1
if let unwrappedContacts = contacts {
for contact in unwrappedContacts {
// handle constacts
...
self.mostRecent.append(...)
}
}
// 2
completion(sortedSections: self.mostRecent)
})
}
It's calling another function which does asynchronous loading of contacts, to which I'm forwarding my completion
The call of fetchMostRecent with completion looks like this:
model.fetchMostRecent({(sortedSections: [TableItem]) in
dispatch_async(dispatch_get_main_queue()) {
// update some UI
self.state = State.Loaded(sortedSections)
self.tableView.reloadData()
}
})
This sometimes it works, but very often the order of execution is not the way as I would expect. Problem is, that sometimes completion() under // 2 is executed before scope of if under // 1 was finished.
Why is that? How can I ensure that execution of // 2 is started after // 1?
A couple of observations:
It will always execute what's at 1 before 2. The only way you'd get the behavior you describe is if you're doing something else inside that for loop that is, itself, asynchronous. And if that were the case, you'd use a dispatch group to solve that (or refactor the code to handle the asynchronous pattern). But without seeing what's in that for loop, it's hard to comment further. The code in the question, alone, should not manifest the problem you describe. It's got to be something else.
Unrelated, you should note that it's a little dangerous to be updating model objects inside your asynchronously executing for loop (assuming it is running on a background thread). It's much safer to update a local variable, and then pass that back via the completion handler, and let the caller take care of dispatching both the model update and the UI updates to the main queue.
In comments, you mention that in the for loop you're doing something asynchronous, and something that must be completed before the completionHandler is called. So you'd use a dispatch group to do ensure this happens only after all the asynchronous tasks are done.
Note, since you're doing something asynchronous inside the for loop, not only do you need to use a dispatch group to trigger the completion of these asynchronous tasks, but you probably also need to create your own synchronization queue (you shouldn't be mutating an array from multiple threads). So, you might create a queue for this.
Pulling this all together, you end up with something like:
func fetchMostRecent(completionHandler: ([TableItem]?) -> ()) {
addressBook.loadContacts { contacts, error in
var sections = [TableItem]()
let group = dispatch_group_create()
let syncQueue = dispatch_queue_create("com.domain.app.sections", nil)
if let unwrappedContacts = contacts {
for contact in unwrappedContacts {
dispatch_group_enter(group)
self.someAsynchronousMethod {
// handle contacts
dispatch_async(syncQueue) {
let something = ...
sections.append(something)
dispatch_group_leave(group)
}
}
}
dispatch_group_notify(group, dispatch_get_main_queue()) {
self.mostRecent = sections
completionHandler(sections)
}
} else {
completionHandler(nil)
}
}
}
And
model.fetchMostRecent { sortedSections in
guard let sortedSections = sortedSections else {
// handle failure however appropriate for your app
return
}
// update some UI
self.state = State.Loaded(sortedSections)
self.tableView.reloadData()
}
Or, in Swift 3:
func fetchMostRecent(completionHandler: #escaping ([TableItem]?) -> ()) {
addressBook.loadContacts { contacts, error in
var sections = [TableItem]()
let group = DispatchGroup()
let syncQueue = DispatchQueue(label: "com.domain.app.sections")
if let unwrappedContacts = contacts {
for contact in unwrappedContacts {
group.enter()
self.someAsynchronousMethod {
// handle contacts
syncQueue.async {
let something = ...
sections.append(something)
group.leave()
}
}
}
group.notify(queue: .main) {
self.mostRecent = sections
completionHandler(sections)
}
} else {
completionHandler(nil)
}
}
}