CoreText crashes when run in multiple threads

2020-05-26 10:50发布

问题:

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

回答1:

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!



回答2:

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.



回答3:

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.



回答4:

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?



回答5:

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);