Using any UIStringDrawing methods on two threads simultaneously causes a crash. My understanding was that all UIStringDrawing methods were thread safe from iOS 4.0.
This code (that does nothing of any use) demonstrates the problem:
dispatch_queue_t queue = dispatch_queue_create("com.queue", NULL);
for (int i = 0; i < 10000; i++) {
dispatch_async(queue, ^{
NSString *string = @"My string";
CGSize size = [string sizeWithFont:[UIFont boldSystemFontOfSize:13]];
});
}
for (int i = 0; i < 10000; i++) {
NSString *string = @"My string";
CGSize size = [string sizeWithFont:[UIFont boldSystemFontOfSize:13]];
}
dispatch_release(queue);
The app crashes after a few iterations of the loops with the following backtrace:
* thread #1: tid = 0x2403, 0x00ad40c8, stop reason = EXC_BAD_ACCESS (code=2, address=0xad40c8)
frame #0: 0x00ad40c8
frame #1: 0x36bc4252 WebCore`WebCore::Font::Font(WebCore::FontPlatformData const&, WTF::PassRefPtr<WebCore::FontSelector>) + 90
frame #2: 0x36bc41f2 WebCore`WebCore::Font::Font(WebCore::FontPlatformData const&, WTF::PassRefPtr<WebCore::FontSelector>) + 10
frame #3: 0x38f0368e WebKit`rendererForFont(__GSFont*) + 246
frame #4: 0x38f03230 WebKit`-[NSString(WebStringDrawing) _web_sizeWithFont:forWidth:ellipsis:letterSpacing:resultRange:] + 200
frame #5: 0x38f03162 WebKit`-[NSString(WebStringDrawing) _web_sizeWithFont:forWidth:ellipsis:letterSpacing:] + 66
frame #6: 0x38f04532 WebKit`-[NSString(WebStringDrawing) _web_sizeWithFont:] + 58
frame #7: 0x361dc5d2 UIKit`-[NSString(UIStringDrawing) sizeWithFont:] + 46
frame #8: 0x00060ca8 myApp`-[TAViewController drawingTest] + 216 at TAViewController.m:157
frame #9: 0x38da1e66 Foundation`__NSFireDelayedPerform + 450
frame #10: 0x3aa47856 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 14
frame #11: 0x3aa47502 CoreFoundation`__CFRunLoopDoTimer + 274
frame #12: 0x3aa46176 CoreFoundation`__CFRunLoopRun + 1230
frame #13: 0x3a9b923c CoreFoundation`CFRunLoopRunSpecific + 356
frame #14: 0x3a9b90c8 CoreFoundation`CFRunLoopRunInMode + 104
frame #15: 0x3a8a433a GraphicsServices`GSEventRunModal + 74
frame #16: 0x3622c288 UIKit`UIApplicationMain + 1120
frame #17: 0x0005f08c myApp`main + 96 at main.m:16
thread #5: tid = 0x2a03, 0x00ad40c8, stop reason = EXC_BAD_ACCESS (code=2, address=0xad40c8)
frame #0: 0x00ad40c8
frame #1: 0x36bc4252 WebCore`WebCore::Font::Font(WebCore::FontPlatformData const&, WTF::PassRefPtr<WebCore::FontSelector>) + 90
frame #2: 0x36bc41f2 WebCore`WebCore::Font::Font(WebCore::FontPlatformData const&, WTF::PassRefPtr<WebCore::FontSelector>) + 10
frame #3: 0x38f0368e WebKit`rendererForFont(__GSFont*) + 246
frame #4: 0x38f03230 WebKit`-[NSString(WebStringDrawing) _web_sizeWithFont:forWidth:ellipsis:letterSpacing:resultRange:] + 200
frame #5: 0x38f03162 WebKit`-[NSString(WebStringDrawing) _web_sizeWithFont:forWidth:ellipsis:letterSpacing:] + 66
frame #6: 0x38f04532 WebKit`-[NSString(WebStringDrawing) _web_sizeWithFont:] + 58
frame #7: 0x361dc5d2 UIKit`-[NSString(UIStringDrawing) sizeWithFont:] + 46
frame #8: 0x00060d5c myApp`__31-[TAViewController drawingTest]_block_invoke_0 + 116 at TAViewController.m:150
frame #9: 0x339f0792 libdispatch.dylib`_dispatch_call_block_and_release + 10
frame #10: 0x339f3b3a libdispatch.dylib`_dispatch_queue_drain + 142
frame #11: 0x339f167c libdispatch.dylib`_dispatch_queue_invoke + 44
frame #12: 0x339f4612 libdispatch.dylib`_dispatch_root_queue_drain + 210
frame #13: 0x339f47d8 libdispatch.dylib`_dispatch_worker_thread2 + 92
frame #14: 0x37f957f0 libsystem_c.dylib`_pthread_wqthread + 360
frame #15: 0x37f95684 libsystem_c.dylib`start_wqthread + 8
My understanding was that UIStringDrawing methods were thread safe from iOS 4. I expect that these loops should complete with no errors.
The crash occurs when running on an iPhone running iOS 6 (tested on iPhone 5) but does NOT occur when running on an iPhone running iOS 5 (tested on iPhone 4) or the simulator (tested with iOS 6).
I have implemented what I thought was a fix by serialising any draw calls using CGD:
- (void)serialiseDrawing:(void (^)())block {
dispatch_sync(self.serialDrawingQueue, block);
}
- (dispatch_queue_t)serialDrawingQueue {
if (_serialDrawingQueue == NULL) _serialDrawingQueue = dispatch_queue_create("com.myApp.SerialDrawQueue", NULL);
return _serialDrawingQueue;
}
...and wrapping every draw call like this:
__block CGSize labelSize = CGSizeZero;
[[TAUtils sharedUtils] serialiseDrawing:^{
labelSize = [label.text sizeWithFont:label.font];
}];
This seems to have improved things a bit (all my UIStringDrawing calls happen on one thread). But it will still crash at times with a backtrace like this:
Exception Type: EXC_CRASH (SIGSEGV)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Crashed Thread: 0
Thread 0 name: Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0 libsystem_kernel.dylib 0x3a28ee80 semaphore_wait_trap + 8
1 libdispatch.dylib 0x32851e90 _dispatch_thread_semaphore_wait + 8
2 libdispatch.dylib 0x32850680 _dispatch_barrier_sync_f_slow + 100
3 myApp 0x000c4330 -[TAUtils serialiseDrawing:] (TAUtils.m:305)
4 myApp 0x000edfd4 -[TAOmniBar updateLabel] (TAOmniBar.m:394)
5 myApp 0x000ee8d6 -[TAOmniBar handleNotification:] (TAOmniBar.m:461)
6 CoreFoundation 0x39820346 _CFXNotificationPost + 1418
7 Foundation 0x37b5838a -[NSNotificationCenter postNotificationName:object:userInfo:] + 66
8 Foundation 0x37b5be9a -[NSNotificationCenter postNotificationName:object:] + 26
9 myApp 0x000f369a -[TAMyViewController update] (TAMyViewController.m:1308)
10 GLKit 0x328383ce -[GLKViewController _updateAndDraw] + 270
11 QuartzCore 0x39ffd77c CA::Display::DisplayLink::dispatch(unsigned long long, unsigned long long) + 156
12 QuartzCore 0x39ffd6d4 CA::Display::IOMFBDisplayLink::callback(__IOMobileFramebuffer*, unsigned long long, unsigned long long, unsigned long long, void*) + 60
13 IOMobileFramebuffer 0x31221fd4 IOMobileFramebufferVsyncNotifyFunc + 152
14 IOKit 0x39f7c5aa IODispatchCalloutFromCFMessage + 190
15 CoreFoundation 0x39899888 __CFMachPortPerform + 116
16 CoreFoundation 0x398a43e4 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 32
17 CoreFoundation 0x398a4386 __CFRunLoopDoSource1 + 134
18 CoreFoundation 0x398a320a __CFRunLoopRun + 1378
19 CoreFoundation 0x39816238 CFRunLoopRunSpecific + 352
20 CoreFoundation 0x398160c4 CFRunLoopRunInMode + 100
21 GraphicsServices 0x39701336 GSEventRunModal + 70
22 UIKit 0x35089284 UIApplicationMain + 1116
23 myApp 0x000b806e main (main.m:16)
24 myApp 0x000b8024 start + 36
Thread 7 name: Dispatch queue: com.myApp.SerialDrawQueue
Thread 7:
0 WebCore 0x35a21410 WebCore::FontFallbackList::invalidate(WTF::PassRefPtr<WebCore::FontSelector>) + 156
1 WebCore 0x35a2124e WebCore::Font::Font(WebCore::FontPlatformData const&, WTF::PassRefPtr<WebCore::FontSelector>) + 86
2 WebCore 0x35a211ee WebCore::Font::Font(WebCore::FontPlatformData const&, WTF::PassRefPtr<WebCore::FontSelector>) + 6
3 WebKit 0x37d6068a rendererForFont(__GSFont*) + 242
4 WebKit 0x37d61796 -[NSString(WebStringDrawing) __web_drawAtPoint:forWidth:withFont:ellipsis:letterSpacing:includeEmoji:measureOnly:renderedStringOut:drawUnderline:] + 198
5 WebKit 0x37d616bc -[NSString(WebStringDrawing) __web_drawAtPoint:forWidth:withFont:ellipsis:letterSpacing:includeEmoji:measureOnly:renderedStringOut:] + 84
6 WebKit 0x37d6165e -[NSString(WebStringDrawing) __web_drawAtPoint:forWidth:withFont:ellipsis:letterSpacing:includeEmoji:measureOnly:] + 82
7 WebKit 0x37d61602 -[NSString(WebStringDrawing) _web_drawAtPoint:forWidth:withFont:ellipsis:letterSpacing:includeEmoji:] + 78
8 UIKit 0x35041960 -[NSString(UIStringDrawing) drawAtPoint:forWidth:withFont:lineBreakMode:letterSpacing:includeEmoji:] + 172
9 UIKit 0x3507de1e -[NSString(UIStringDrawing) drawAtPoint:forWidth:withFont:fontSize:lineBreakMode:baselineAdjustment:includeEmoji:] + 358
10 UIKit 0x3507dca4 -[NSString(UIStringDrawing) drawAtPoint:forWidth:withFont:fontSize:lineBreakMode:baselineAdjustment:] + 68
11 myApp 0x000d3300 -[TALabelManager textureCGImageForString:] (TALabelManager.m:859)
12 myApp 0x000d350a __39-[TALabelManager textureDataForString:]_block_invoke_0 (TALabelManager.m:875)
13 libdispatch.dylib 0x3284d5d8 _dispatch_client_callout + 20
14 libdispatch.dylib 0x3285080a _dispatch_barrier_sync_f_invoke + 22
15 myApp 0x000c4330 -[TAUtils serialiseDrawing:] (TAUtils.m:305)
16 myApp 0x000d3420 -[TALabelManager textureDataForString:] (TALabelManager.m:873)
17 myApp 0x000d0dde __block_global_0 (TALabelManager.m:516)
18 libdispatch.dylib 0x3284d790 _dispatch_call_block_and_release + 8
19 libdispatch.dylib 0x32850b36 _dispatch_queue_drain + 138
20 libdispatch.dylib 0x3284e678 _dispatch_queue_invoke + 40
21 libdispatch.dylib 0x32851610 _dispatch_root_queue_drain + 208
22 libdispatch.dylib 0x328517d4 _dispatch_worker_thread2 + 88
23 libsystem_c.dylib 0x36df27ee _pthread_wqthread + 358
24 libsystem_c.dylib 0x36df2680 start_wqthread + 4
I apologise for the long question but this is a serious problem for me and would really appreciate any help.