CoreText crashes when run in multiple threads
Asked Answered
W

5

13

I have a very weird problem with core text, which sometimes randomly and sometimes reproducibly crashes my application. I use it to lay out and render a couple of pages. I do this asynchronously in the background to not block the user interface.

While this works fine in general, it sometimes crashes. All these crashes happen on the very same line:

framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)myText);

In fact, they also also seem to come from a similar point in the framework. I know you don't like it, but here's the head of a crash log:

Thread 8 Crashed:
0   ???                             0x0764f446 typeinfo for FT::data_stream + 6
1   libCGFreetype.A.dylib           0x076048b8 FT::font::copy_table(unsigned int) const + 94
2   libCGFreetype.A.dylib           0x0760b085 (anonymous namespace)::copy_table(void*, unsigned int) + 53
3   CoreText                        0x00f9592e TBaseFont::CopyTable(unsigned int) const + 334
4   CoreText                        0x00f670f6 TAATMorphTable::TAATMorphTable(TLine&, long, unsigned int) + 110
5   CoreText                        0x00f6744c TAATMorphTableMorx::TAATMorphTableMorx(TLine&, long, TGlyphList<TDeletedGlyphIndex>&) + 54
6   CoreText                        0x00f53eb5 TShapingEngine::ShapeGlyphs(TLine&, TCharStream const&, CFRange&, TGlyphList<TDeletedGlyphIndex>*) + 215
7   CoreText                        0x00f579ce TTypesetter::FinishEncoding(TLine&, signed char, TGlyphList<TDeletedGlyphIndex>*) const + 260
8   CoreText                        0x00f6664b TTypesetterAttrString::Initialize(__CFAttributedString const*) + 543
9   CoreText                        0x00f6683e TTypesetterAttrString::TTypesetterAttrString(__CFAttributedString const*) + 158
10  CoreText                        0x00f6102e TFramesetterAttrString::TFramesetterAttrString(__CFAttributedString const*) + 86
11  CoreText                        0x00f6099e CTFramesetterCreateWithAttributedString + 78
...

All crashes I can remember have been in the FT::font::copy_table function. Interestingly, the more complicated the font-requirements, the more frequent the crashes. Chinese text nearly always crash -- those fonts seem to be quite complicated.

Workaround: The workaround I found is to sequentialize the calls to CTFramesetterCreateWithAttributedString in either the main queue or a separate one. The problem is that this single call makes up 79% of the total layout and rendering running time. So I would love to have it in multiple threads.

Question: Any Pros around that could help? To me this sounds like a race condition somewhere deep down. I didn't find anything stating that CoreText may not be used threaded. And I will file a bug tomorrow. However, I might also just have missed something. Any advice?

Thanks, Max

Wamble answered 12/4, 2011 at 23:17 Comment(7)
@Max Seelemann - Yep. Sounds like a race condition or a shared resource competition.Deadlight
Can you rule out that it has to do with the argument myText to the function? E.g. if myText is being changed while the framesetter is being created?Pernell
@Ole: yes I am absolutely sure of thatWamble
@Max Seelemann: try the following and report back if you still experience the crash. Simply substitute the statement with the block: @synchronized(myText){framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)myText);}Grams
@unforgiven: Won't help because i do not reuse the text storage. I use a separate one per typesetter. Also, synchronizing would sequentialize the process as well...Wamble
@Max Seelemann: this was just to make sure the problem was not actually related to multiple threads accessing the same shared resource simultaneously. If this was the case, then serializing the accesses through a mutex, a lock or a synchronized block is the usual way to go unless you change your code in order to support truly concurrent accesses.Grams
I guess if you have time you could create a sample that explicitly calls CTFramesetterCreateWithAttributedString() from multiple threads and see if it crashes.Cytogenesis
W
8

I've asked some of the engineers during WWDC whether they know the issue. The answer: YES. And there are in fact some problems in the type subsystem. They might be doing a fix some day, but for now all that is left to do is to sequentialize all text layout. :(

Everyone: PLEASE FILE BUGS!

Wamble answered 17/6, 2011 at 13:30 Comment(5)
I had the same issue when i am implementing the EGOTextView, it crashed on CTFramesetterCreateWithAttributedString when i am setting an attributedString to the EGOTextView. Do you know how to sequentialize the text layout?Ladyship
@lu yuan: simply use dispatch_sync() top but the calls on a serial background queue.Wamble
Thx for your reply :). I found my problem it crashed when im using UIFont. After changing to CTFont, it's ok now. Maybe it's a bug of iOS5, as UIFont works on iOS6.Ladyship
As of May 9, 2013, this is still an issue: CTFramesetterCreateWithAttributedString cannot be called except sequentially on a single thread/queue.Maharanee
As of Feb 25 2014 this is still an issue. Serialize calls to CTFramesetterCreateWithAttributedString.Gesualdo
M
4

Here is what the documentation says:

Multicore Considerations: All individual functions in Core Text are thread safe. Font objects (CTFont, CTFontDescriptor, and associated objects) can be used by simultaneously by multiple operations, work queues, or threads. However, the layout objects (CTTypesetter, CTFramesetter, CTRun, CTLine, CTFrame, and associated objects) should be used in a single operation, work queue, or thread.

So I guess there is no way around serialising calls to CTFramesetterCreateWithAttributedString.

Macroclimate answered 13/5, 2011 at 18:18 Comment(2)
Interesting, I didn't find that yet. But if the individual layout objects are not thread-safe does that imply that one cannot create two simultaneously? E.g. it is in fact no problem to draw multiple lines simultaneously...Wamble
I don't think you're reading it correctly. I believe the documentation is saying that CTFramesetterCreateWithAttributedString, a function, is thread safe, but the CTFramesetter it creates is not. However it's pretty easy to reproduce a crash by concurrently calling CTFramesetterCreateWithAttributedString, likely a bug.Gesualdo
R
1

CoreText takes a while to initialize the font lookup table when you use it for the first time. I imagine you might be able to get rid of your problem by first triggering a loading of this table before going to multiple threads.

See http://www.cocoanetics.com/2011/04/coretext-loading-performance/ for a method how.

Realm answered 19/6, 2011 at 3:7 Comment(4)
Great thought, thanks. Unfortunately this problem occurs even after the application has run for a long time, I can basically trigger it any moment. So I don't think it's a problem with the setup...Wamble
The other problem with CTFramesetter is that it references the attributed string, but it does not retain it. Make sure that you retain it appropriately yourself.Realm
Another thing that might speed it up quite a bit is if you cache the fonts you create and reuse one font instead of creating exactly the same typeface over and over.Realm
Thanks :) But as I said, the font creation is not the problem. It is the fact that the CFTypesetter uses the font subsystem in a non-thread-sfae way.Wamble
W
0

Please make sure you're retaining the framesetter before reopening it. This is REALLY not meant to be used asynchronous before 4.0!

CFRelease(framesetter);

Could you also provide the Version of Xcode & iOS you're working with?

Wardle answered 21/4, 2011 at 12:50 Comment(3)
I'm using the iOS SDK 4.3 and Xcode 4. I don't see where I'm reopening the framesetter, it's a local variable that I release afterwards...Wamble
Do you use release or CFRelease?Wardle
If it's a CFTypeRef, either -[NSObject release] or if ( obj ) { CFRelease( obj ) ; } will workCytogenesis
I
0

fix from me :-) no crash anymore

old code

NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:aText];
[attributedString addAttribute:(id)kCTFontAttributeName value:(id)aFontRef range:NSMakeRange(0, [aText length])];

CTFramesetterRef framesetterRef = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributedString);

new code

CFMutableAttributedStringRef attributedStringRef = CFAttributedStringCreateMutable(nil, 0);
CFAttributedStringBeginEditing(attributedStringRef);
CFAttributedStringReplaceString(attributedStringRef, CFRangeMake(0, 0), (CFStringRef)aText);
CFAttributedStringSetAttribute(attributedStringRef, CFRangeMake(0, aText.length), kCTFontAttributeName, aFontRef);
CFAttributedStringEndEditing(attributedStringRef);

CTFramesetterRef framesetterRef = CTFramesetterCreateWithAttributedString(attributedStringRef); 
Ileneileo answered 29/8, 2011 at 17:22 Comment(1)
I don't see how this should solve the issue in the font system. It is still the same font being used parallel, right?Wamble

© 2022 - 2024 — McMap. All rights reserved.