Dispatch

RSS for tag

Execute code concurrently on multicore hardware by submitting work to dispatch queues managed by the system using Dispatch.

Dispatch Documentation

Pinned Posts

Posts under Dispatch tag

28 Posts
Sort by:
Post not yet marked as solved
0 Replies
495 Views
Hi everyone, I have an AR app that allows for collaborative sessions and synchronizes model state (e.g. rotation, can be changed via slider) using Multipeer Connectivity. The receiving peer parses the data and then uses DispatchQueue.main.async to update the UI (SwiftUI) and the model in SceneKit. Lately I have noticed that this synchronization seems to lag periodically. To analyze the issue better I compiled this minimal reproducible example: https://github.com/MrMuetze/MCSyncTest The repository includes a boiled down "Multipeer Connectivity" project that should make this issue reproducible on local devices (maybe even between one device and the simulator). I have also added a readme with a gif that shows the issue. The synchronization between devices worked like a treat for a long time but recently I have noticed that e.g. a rotation is not as smooth as before on the receiving device. A bit of debugging revealed that the messages are received quickly but then the work that needs to happen on the main thread is periodically delayed. In the example project the relevant code bit that should be executed on the main thread looks like this: func session(_: MCSession, didReceive data: Data, fromPeer _: MCPeerID) { print("received data") DispatchQueue.main.async { print("doing stuff") let doubleVal = data.to(type: Double.self) ?? 0.0 self.internalSliderValue = doubleVal self.sliderValue = doubleVal } } It updates a published variable sliderValue that is connected to a Slider and a Text UI element. Regularly (like every 500ms or so) the execution of work on the main thread seems to be delayed. After a short while all outstanding UI updates are executed at once which leads to visual stuttering. This can be observed by looking at the printed messages: ... received data <-- normal behavior doing stuff received data doing stuff received data doing stuff received data <-- hiccup starts received data received data received data doing stuff doing stuff doing stuff doing stuff received data <-- returns to normal behavior doing stuff received data doing stuff ... I have tried to change the "Quality of Service" to .userInteractive as well as limiting the number of messages that are sent in a certain timeframe (I tried one message every 100ms). Both changes have not helped and even with a much lower number of messages the periodic stuttering remains. Using DispatchQueue.main.sync is also not a solution right now. It does bring the sequence back into original order but the periodic "freeze" of the queue is prevalent there as well. This then leads to a "laggy" execution of what happened on the sending peer device. I am not very familiar with Profiling an app and using Instruments, but I have captured some timings in regards to the usage of the main thread and some backtraces. From what I can understand the workload of the written code should not be the issue, but rather an underlying system function or library. I can provide more information in regards to the backtraces if needed. Right now I can't really say what would be useful. Below is an image that shows the main thread usage at the very top. This happens when the slider lags as shown in the gif. I am working with Xcode 15.2 and run the app on iOS 17.3. For devices I use an iPad Pro (2nd gen.) and an iPhone 15 Pro. The issue happens in Debug as well as in Release mode. I can't quite say when the stuttering appeared initially. I wonder if anyone is aware of any changes to iOS or underlying frameworks that could have caused this issue. I am interested in any information about this, if the issue can be resolved or if I have to look for alternative workarounds. Let me know if I can add any additional information. Best regards! Bjoern
Posted
by
Post not yet marked as solved
6 Replies
395 Views
Two different crash patterns -- one an abort, the other complaining about a lock being corrupt or owning thread having exited. The first one is: Thread 1 Crashed:: Dispatch queue: com.apple.root.default-qos.overcommit 0 libsystem_platform.dylib 0x18fc10244 _os_unfair_lock_corruption_abort + 88 1 libsystem_platform.dylib 0x18fc0b788 _os_unfair_lock_lock_slow + 332 2 libobjc.A.dylib 0x18f820c90 objc_sync_enter + 20 3 com.kithrup.TPProvider 0x100d2eee0 closure #3 in TPProvider.startProxy(options:completionHandler:) + 340 4 com.kithrup.TPProvider 0x100d2d980 thunk for @escaping @callee_guaranteed () -> () + 28 5 libdispatch.dylib 0x18fa31910 _dispatch_client_callout + 20 6 libdispatch.dylib 0x18fa34dc8 _dispatch_continuation_pop + 600 7 libdispatch.dylib 0x18fa48be4 _dispatch_source_latch_and_call + 420 8 libdispatch.dylib 0x18fa477b4 _dispatch_source_invoke + 832 9 libdispatch.dylib 0x18fa431f4 _dispatch_root_queue_drain + 392 10 libdispatch.dylib 0x18fa43a04 _dispatch_worker_thread2 + 156 11 libsystem_pthread.dylib 0x18fbdb0d8 _pthread_wqthread + 228 12 libsystem_pthread.dylib 0x18fbd9e30 start_wqthread + 8 while the other one is: Application Specific Information: BUG IN CLIENT OF LIBPLATFORM: os_unfair_lock is corrupt, or owner thread exited without unlocking Abort Cause 198194 Thread 1 Crashed:: Dispatch queue: com.apple.root.default-qos.overcommit 0 libsystem_platform.dylib 0x18fc10220 _os_unfair_lock_corruption_abort + 52 1 libsystem_platform.dylib 0x18fc0b788 _os_unfair_lock_lock_slow + 332 2 libobjc.A.dylib 0x18f820c90 objc_sync_enter + 20 3 com.kithrup.TPProvider 0x104e86ee0 closure #3 in TPProvider.startProxy(options:completionHandler:) +340 4 com.kithrup.TPProvider 0x104e85980 thunk for @escaping @callee_guaranteed () -> () + 28 5 libdispatch.dylib 0x18fa31910 _dispatch_client_callout + 20 6 libdispatch.dylib 0x18fa34dc8 _dispatch_continuation_pop + 600 7 libdispatch.dylib 0x18fa48be4 _dispatch_source_latch_and_call + 420 8 libdispatch.dylib 0x18fa477b4 _dispatch_source_invoke + 832 9 libdispatch.dylib 0x18fa431f4 _dispatch_root_queue_drain + 392 10 libdispatch.dylib 0x18fa43a04 _dispatch_worker_thread2 + 156 11 libsystem_pthread.dylib 0x18fbdb0d8 _pthread_wqthread + 228 12 libsystem_pthread.dylib 0x18fbd9e30 start_wqthread + 8 Our TPProvider, whenever it uses a dispatch queue, uses a custom one, so these are presumably system queues and locks. My best guess would be some XPC command took too long? But that's just WAG. Any ideas about what is actually going on?
Posted
by
Post marked as solved
3 Replies
519 Views
I was wondering if there is a way, while debugging, to observe the 'QoS boosting' behavior that is implemented in various places to provide priority inversion avoidance. The pthread_override_qos_class_start/end_np header comments specifically say that overrides aren't reflected in the qos_class_self() and pthread_get_qos_class_np() return values. As far as I can tell the 'CPU Report' UI in Xcode also does not reflect this information (perhaps for the reason the header comments call out). Is there a direct mechanism to observe this behavior? Presumably a heuristic empirical test could be done to compare throughput of a queue that should have its priority boosted and one that should not, but I would prefer a less opaque means of verification if possible. Thanks in advance!
Posted
by
Post not yet marked as solved
2 Replies
269 Views
Per my understanding of the DispatchQueue docs, and various WWDC videos on the matter, if one creates a queue in the following manner: let q = DisqpatchQueue( label: "my-q", qos: .utility, target: .global(qos: .userInteractive) ) then one should expect work items submitted via async() to effectively run at userInteractive QoS, as the target queue should provide a 'floor' on the effective QoS value (assuming no additional rules are in play, e.g. higher priority items have been enqueued, submitted work items enforce QoS, etc). In practice, however, this particular formulation does not appear to function that way, and the 'resolved' QoS value seems to be utility, contrary to what the potentially relevant documentation suggests. This behavior appears to be inconsistent with other permutations of queue construction, which makes it even more surprising. Here's some sample code I was experimenting with to check the behavior of queues created in various ways that I would expect to function analogously (in regards to the derived QoS value for the threads executing their work items): func test_qos_permutations() { // q1 let utilTargetingGlobalUIQ = DispatchQueue( label: "qos:util tgt:globalUI", qos: .utility, target: .global(qos: .userInitiated) ) let customUITargetQ = DispatchQueue( label: "custom tgt, qos: unspec, tgt:globalUI", target: .global(qos: .userInitiated) ) // q2 let utilTargetingCustomSerialUIQ = DispatchQueue( label: "qos:util tgt:customSerialUI", qos: .utility, target: customUITargetQ ) // q3 let utilDelayedTargetingGlobalUIQ = DispatchQueue( label: "qos:util tgt:globalUI-delayed", qos: .utility, attributes: .initiallyInactive ) utilDelayedTargetingGlobalUIQ.setTarget(queue: .global(qos: .userInitiated)) utilDelayedTargetingGlobalUIQ.activate() let queues = [ utilTargetingGlobalUIQ, utilTargetingCustomSerialUIQ, utilDelayedTargetingGlobalUIQ, ] for q in queues { q.async { Thread.current.name = q.label let threadQos = qos_class_self() print(""" q: \(q.label) orig qosClass: \(q.qos.qosClass) thread qosClass: \(DispatchQoS.QoSClass(rawValue: threadQos)!) """) } } } Running this, I get the following output: q: qos:util tgt:customSerialUI orig qosClass: utility thread qosClass: userInitiated q: qos:util tgt:globalUI-delayed orig qosClass: utility thread qosClass: userInitiated q: qos:util tgt:globalUI orig qosClass: utility thread qosClass: utility This test suggests that constructing a queue with an explicit qos parameter and targeting a global queue of nominally 'higher' QoS does not result in a queue that runs its items at the target's QoS. Perhaps most surprisingly is that if the target queue is set after the queue was initialized, you do get the expected 'QoS floor' behavior. Is this behavior expected, or possibly a bug?
Posted
by
Post not yet marked as solved
5 Replies
327 Views
Hello, I have received 3 almost identical crash reports from the App Store. They all come from the same user, and they are spaced just ± 45 seconds apart. This is the backtrace of the crashed thread: Crashed Thread: 3 Exception Type: EXC_CRASH (SIGABRT) Exception Codes: 0x0000000000000000, 0x0000000000000000 Termination Reason: Namespace SIGNAL, Code 6 Abort trap: 6 Terminating Process: Ssssssss [46033] Thread 3 Crashed: 0 libsystem_kernel.dylib 0x00007ff81b90f196 __pthread_kill + 10 1 libsystem_pthread.dylib 0x00007ff81b946ee6 pthread_kill + 263 (pthread.c:1670) 2 libsystem_c.dylib 0x00007ff81b86dbdf __abort + 139 (abort.c:155) 3 libsystem_c.dylib 0x00007ff81b86db54 abort + 138 (abort.c:126) 4 libc++abi.dylib 0x00007ff81b901282 abort_message + 241 5 libc++abi.dylib 0x00007ff81b8f33fb demangling_terminate_handler() + 267 6 libobjc.A.dylib 0x00007ff81b5c67ca _objc_terminate() + 96 (objc-exception.mm:498) 7 libc++abi.dylib 0x00007ff81b9006db std::__terminate(void (*)()) + 6 8 libc++abi.dylib 0x00007ff81b900696 std::terminate() + 54 9 libdispatch.dylib 0x00007ff81b7a6047 _dispatch_client_callout + 28 (object.m:563) 10 libdispatch.dylib 0x00007ff81b7a87c4 _dispatch_queue_override_invoke + 800 (queue.c:4882) 11 libdispatch.dylib 0x00007ff81b7b5fa2 _dispatch_root_queue_drain + 343 (queue.c:7051) 12 libdispatch.dylib 0x00007ff81b7b6768 _dispatch_worker_thread2 + 170 (queue.c:7119) 13 libsystem_pthread.dylib 0x00007ff81b943c0f _pthread_wqthread + 257 (pthread.c:2631) 14 libsystem_pthread.dylib 0x00007ff81b942bbf start_wqthread + 15 (:-1) In the backtrace of the main thread, I can see that the error is caught by the app delegate which tries to display an alert, but obviously the message has no time to appear. Incidentally (but it's not my main question), I would like to know if it would be possible in such a case to block the background thread for the time the alert is displayed (e.g. using a dispatch queue)? ... (many other related lines) 72 SSUuuuIIIIIIIIIUUuuuu 0x000000010e8089f2 +[NSAlert(Ssssssss) fatalError:] + 32 73 Ssssssss 0x000000010dd5e75b __universalExceptionHandler_block_invoke (in Ssssssss) (SQAppDelegate.m:421) + 333659 74 libdispatch.dylib 0x00007ff81b7a4d91 _dispatch_call_block_and_release + 12 (init.c:1518) 75 libdispatch.dylib 0x00007ff81b7a6033 _dispatch_client_callout + 8 (object.m:560) 76 libdispatch.dylib 0x00007ff81b7b2fcf _dispatch_main_queue_drain + 954 (queue.c:7794) 77 libdispatch.dylib 0x00007ff81b7b2c07 _dispatch_main_queue_callback_4CF + 31 (queue.c:7954) 78 CoreFoundation 0x00007ff81ba62195 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9 (CFRunLoop.c:1780) 79 CoreFoundation 0x00007ff81ba21ebf __CFRunLoopRun + 2452 (CFRunLoop.c:3147) 80 CoreFoundation 0x00007ff81ba20ec1 CFRunLoopRunSpecific + 560 (CFRunLoop.c:3418) 81 HIToolbox 0x00007ff8254a5f3d RunCurrentEventLoopInMode + 292 (EventLoop.c:455) 82 HIToolbox 0x00007ff8254a5d4e ReceiveNextEventCommon + 657 (EventBlocking.c:384) 83 HIToolbox 0x00007ff8254a5aa8 _BlockUntilNextEventMatchingListInModeWithFilter + 64 (EventBlocking.c:171) 84 AppKit 0x00007ff81eabfb18 _DPSNextEvent + 858 (CGDPSReplacement.m:818) 85 AppKit 0x00007ff81eabe9c2 -[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 1214 (appEventRouting.m:407) 86 AppKit 0x00007ff81eab1037 -[NSApplication run] + 586 (NSApplication.m:3432) 87 AppKit 0x00007ff81ea85251 NSApplicationMain + 817 (NSApplication.m:9427) 88 dyld 0x00007ff81b5ec41f start + 1903 (dyldMain.cpp:1165) In all 3 reports, another thread indicates that it is sending or receiving a MIDI data (it's exactly the same backtrace in the 3 reports): Thread 1: 0 libsystem_kernel.dylib 0x00007ff81b908552 mach_msg2_trap + 10 1 libsystem_kernel.dylib 0x00007ff81b9166cd mach_msg2_internal + 78 (mach_msg.c:201) 2 libsystem_kernel.dylib 0x00007ff81b90f584 mach_msg_overwrite + 692 (mach_msg.c:0) 3 libsystem_kernel.dylib 0x00007ff81b90883a mach_msg + 19 (mach_msg.c:323) 4 CoreMIDI 0x00007ff834adfd50 XServerMachPort::ReceiveMessage(int&, void*, int&) + 94 (XMachPort.cpp:62) 5 CoreMIDI 0x00007ff834b118c5 MIDIProcess::MIDIInPortThread::Run() + 105 (MIDIClientLib.cpp:204) 6 CoreMIDI 0x00007ff834af9c44 CADeprecated::XThread::RunHelper(void*) + 10 (XThread.cpp:21) 7 CoreMIDI 0x00007ff834afae9f CADeprecated::CAPThread::Entry(CADeprecated::CAPThread*) + 77 (CAPThread.cpp:324) 8 libsystem_pthread.dylib 0x00007ff81b9471d3 _pthread_start + 125 (pthread.c:893) 9 libsystem_pthread.dylib 0x00007ff81b942bd3 thread_start + 15 (:-1) I wonder if this MIDI activity may be related to the crash, even if it doesn't occur in the crashed thread. I also wonder if the dispatch block starting the backtrace of the thread 3 is the same that leads to the thread 1 (to open the NSAlert), which would mean that it's after the error, or if it's a dispatch block into which the error occurs. Finally, 2024-03-25_13-04-40.6314.crash I wonder if std::terminate() could indicate a problem in C++ codes because I have some part of the application written in C++, and it would be a clue. More generally, is it something in these backtraces that can help me to find what's the problem ? Any help greatly appreciated, thanks! -dp
Posted
by
-dp
Post not yet marked as solved
3 Replies
195 Views
In the 'Discussion' section of the current documentation for Swift's DispatchQueue, it says: If the target queue is a concurrent queue, the blocks run in parallel and must therefore be reentrant-safe. However, unlike dispatch_apply (on which this API is built), this method provides no direct means of specifying a target queue, so this callout is somewhat more confusing than it ought to be. IMO, it's important to highlight the reentrancy considerations that apply in most (all?) cases, but the implicit reference to the implementation details should be removed or clarified. Feedback filed as: FB13708750
Posted
by
Post not yet marked as solved
2 Replies
223 Views
I'm developing iOS framework with Objective C. I create a dispatch_queue_t by using dispatch_queue_create. And call CFRunLoopRun() for run the Runloop in the queue. But, It looks like the dispatch_queue_t has share the RunLoop. Some classes has add an invalid timer, and when I call the CFRunLoopRun(), It crashed on my side. Sample code: - (void)viewDidLoad { [super viewDidLoad]; self.queue1 = dispatch_queue_create("com.queue1", DISPATCH_QUEUE_CONCURRENT); self.queue2 = dispatch_queue_create("org.queue2", DISPATCH_QUEUE_CONCURRENT); } - (IBAction)btnButtonAction:(id)sender { dispatch_async(self.queue1, ^{ NSString *runloop = [NSString stringWithFormat:@"%@", CFRunLoopGetCurrent()]; runloop = [runloop substringWithRange:NSMakeRange(0, 22)]; NSLog(@"Queue1 %p run: %@", self.queue1, runloop); //NSTimer *timer = [[NSTimer alloc] initWithFireDate:[NSDate date] interval:1 target:self selector:@selector(wrongSeletor:) userInfo:nil repeats:NO]; //[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; }); dispatch_async(self.queue2, ^{ NSString *runloop = [NSString stringWithFormat:@"%@", CFRunLoopGetCurrent()]; runloop = [runloop substringWithRange:NSMakeRange(0, 22)]; NSLog(@"Queue2 %p run: %@", self.queue2, runloop); CFRunLoopRun(); }); } Some time they take same RunLoop: https://i.stack.imgur.com/wGcv3.png ===== You can see the crash by uncomment the code of NSTimer. The NSTimer has been added in queue1, but it still running when call CFRunLoopRun() in queue2. I have read some description like: https://stackoverflow.com/questions/38000727/need-some-clarifications-about-dispatch-queue-thread-and-nsrunloop They told that: "system creates a run loop for the thread". But, in my check, they are sharing the RunLoop. This is sad for me, because I facing that crashes happen when calling CFRunLoopRun() on production. Can someone take a look at this.
Posted
by