Nothing prints out in the console in command line tool Xcode when I run the following code:
import Foundation
class A {
var someValue = 0
let concurrentQueue = dispatch_queue_create("queue_for_property", DISPATCH_QUEUE_CONCURRENT)
func increaseValueBy1000() {
dispatch_barrier_async(concurrentQueue) {
for _ in 0 ..< 1000 {
let v = self.someValue + 1
print(v)
self.someValue = v
}
}
}
}
let instance1 = A()
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0)) {
instance1.increaseValueBy1000()
}
instance1.increaseValueBy1000()
I don't see any print statement in the console.
If I remove barrier line works pretty fine.
What I do wrong in this case why my barriers don't allow to print?
Applications – such has command-line programs – which do not already have a "run loop" have to call
dispatch_main() // Swift 2
dispatchMain() // Swift 3
in order to use GCD. From the documentation:
This function "parks" the main thread and waits for blocks to be
submitted to the main queue. Applications that call UIApplicationMain
(iOS), NSApplicationMain (Mac OS X), or CFRunLoopRun on the main
thread must not call dispatch_main.
Related
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"))")
}
}
I've been trying to reproduce example from on NSCondition here using my Ubuntu 18.04 machine and it seems that threads won't start even though thread_object.start() is called.
Code of the example
import Foundation
let cond = NSCondition()
var available = false
var SharedString = ""
class WriterThread : Thread {
override func main(){
for _ in 0..<5 {
cond.lock()
SharedString = "😅"
available = true
cond.signal() // Notify and wake up the waiting thread/s
cond.unlock()
}
}
}
class PrinterThread : Thread {
override func main(){
for _ in 0..<5 { //Just do it 5 times
cond.lock()
while(!available){ //Protect from spurious signals
cond.wait()
}
Thread.sleep(forTimeInterval:100)
print(SharedString)
SharedString = ""
available = false
cond.unlock()
}
}
}
let writet = WriterThread()
let printt = PrinterThread()
printt.start()
writet.start()
What have I done:
inited project with swift init --type=executable
amended code in Sources/Project/main.swift with the code above
used swift build without any errors
run the compiled object using .build/x86_64-unknown-linux-gnu/debug/Project
And the output is empty, which seems like thread won't start, even if I put print statement as first command inside the threads main. Is there any problem with running threading with Swift on Ubuntu, or do I do something wrong?
The Swift version is 5.5.3
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.
So, I have a Swift command-line program:
import Foundation
print("start")
startAsyncNetworkingStuff()
RunLoop.current.run()
print("end")
The code compiles without error. The async networking code runs just fine, fetches all its data, prints the result, and eventually calls its completion function.
How do I get that completion function to break out of above current runloop so that the last "end" gets printed?
Added:
Replacing RunLoop.current.run() with the following:
print("start")
var shouldKeepRunning = true
startAsyncNetworkingStuff()
let runLoop = RunLoop.current
while ( shouldKeepRunning
&& runLoop.run(mode: .defaultRunLoopMode,
before: .distantFuture ) ) {
}
print("end")
Setting
shouldKeepRunning = false
in the async network completion function still does not result in "end" getting printed. (This was checked by bracketing the shouldKeepRunning = false statement with print statements which actually do print to console). What is missing?
For a command line interface use this pattern and add a completion handler to your AsyncNetworkingStuff (thanks to Rob for code improvement):
print("start")
let runLoop = CFRunLoopGetCurrent()
startAsyncNetworkingStuff() { result in
CFRunLoopStop(runLoop)
}
CFRunLoopRun()
print("end")
exit(EXIT_SUCCESS)
Please don't use ugly while loops.
Update:
In Swift 5.5+ with async/await it has become much more comfortable. There's no need anymore to maintain the run loop.
Rename the file main.swift as something else and use the #main attribute like in a normal application.
#main
struct CLI {
static func main() async throws {
let result = await startAsyncNetworkingStuff()
// do something with result
}
}
The name of the struct is arbitrary, the static function main is mandatory and is the entry point.
Here's how to use URLSession in a macOS Command Line Tool using Swift 4.2
// Mac Command Line Tool with Async Wait and Exit
import Cocoa
// Store a reference to the current run loop
let runLoop = CFRunLoopGetCurrent()
// Create a background task to load a webpage
let url = URL(string: "http://SuperEasyApps.com")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let data = data {
print("Loaded: \(data) bytes")
}
// Stop the saved run loop
CFRunLoopStop(runLoop)
}
task.resume()
// Start run loop after work has been started
print("start")
CFRunLoopRun()
print("end") // End will print after the run loop is stopped
// If your command line tool finished return success,
// otherwise return EXIT_FAILURE
exit(EXIT_SUCCESS)
You'll have to call the stop function using a reference to the run loop before you started (as shown above), or using GCD in order to exit as you'd expect.
func stopRunLoop() {
DispatchQueue.main.async {
CFRunLoopStop(CFRunLoopGetCurrent())
}
}
References
https://developer.apple.com/documentation/corefoundation/1542011-cfrunlooprun
Run loops can be run recursively. You can call CFRunLoopRun() from within any run loop callout and create nested run loop activations on the current thread’s call stack.
https://developer.apple.com/documentation/corefoundation/1541796-cfrunloopstop
If the run loop is nested with a callout from one activation starting another activation running, only the innermost activation is exited.
(Answering my own question)
Adding the following snippet to my async network completion code allows "end" to be printed :
DispatchQueue.main.async {
shouldKeepRunning = false
}
When writing a Command Line Tool (CLT) in Swift, I want to process a lot of data. I've determined that my code is CPU bound and performance could benefit from using multiple cores. Thus I want to parallelize parts of the code. Say I want to achieve the following pseudo-code:
Fetch items from database
Divide items in X chunks
Process chunks in parallel
Wait for chunks to finish
Do some other processing (single-thread)
Now I've been using GCD, and a naive approach would look like this:
let group = dispatch_group_create()
let queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT)
for chunk in chunks {
dispatch_group_async(group, queue) {
worker(chunk)
}
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
However GCD requires a run loop, so the code will hang as the group is never executed. The runloop can be started with dispatch_main(), but it never exits. It is also possible to run the NSRunLoop just a few seconds, however that doesn't feel like a solid solution. Regardless of GCD, how can this be achieved using Swift?
I mistakenly interpreted the locking thread for a hanging program. The work will execute just fine without a run loop. The code in the question will run fine, and blocking the main thread until the whole group has finished.
So say chunks contains 4 items of workload, the following code spins up 4 concurrent workers, and then waits for all of the workers to finish:
let group = DispatchGroup()
let queue = DispatchQueue(label: "", attributes: .concurrent)
for chunk in chunk {
queue.async(group: group, execute: DispatchWorkItem() {
do_work(chunk)
})
}
_ = group.wait(timeout: .distantFuture)
Just like with an Objective-C CLI, you can make your own run loop using NSRunLoop.
Here's one possible implementation, modeled from this gist:
class MainProcess {
var shouldExit = false
func start () {
// do your stuff here
// set shouldExit to true when you're done
}
}
println("Hello, World!")
var runLoop : NSRunLoop
var process : MainProcess
autoreleasepool {
runLoop = NSRunLoop.currentRunLoop()
process = MainProcess()
process.start()
while (!process.shouldExit && (runLoop.runMode(NSDefaultRunLoopMode, beforeDate: NSDate(timeIntervalSinceNow: 2)))) {
// do nothing
}
}
As Martin points out, you can use NSDate.distantFuture() as NSDate instead of NSDate(timeIntervalSinceNow: 2). (The cast is necessary because the distantFuture() method signature indicates it returns AnyObject.)
If you need to access CLI arguments see this answer. You can also return exit codes using exit().
Swift 3 minimal implementation of Aaron Brager solution, which simply combines autoreleasepool and RunLoop.current.run(...) until you break the loop:
var shouldExit = false
doSomethingAsync() { _ in
defer {
shouldExit = true
}
}
autoreleasepool {
var runLoop = RunLoop.current
while (!shouldExit && (runLoop.run(mode: .defaultRunLoopMode, before: Date.distantFuture))) {}
}
I think CFRunLoop is much easier than NSRunLoop in this case
func main() {
/**** YOUR CODE START **/
let group = dispatch_group_create()
let queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT)
for chunk in chunks {
dispatch_group_async(group, queue) {
worker(chunk)
}
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
/**** END **/
}
let runloop = CFRunLoopGetCurrent()
CFRunLoopPerformBlock(runloop, kCFRunLoopDefaultMode) { () -> Void in
dispatch_async(dispatch_queue_create("main", nil)) {
main()
CFRunLoopStop(runloop)
}
}
CFRunLoopRun()