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

Max Seelemann
  • 9,344
  • 4
  • 34
  • 40
  • @Max Seelemann - Yep. Sounds like a race condition or a shared resource competition. – DougW Apr 20 '11 at 06:43
  • 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? – Ole Begemann Apr 20 '11 at 13:43
  • @Ole: yes I am absolutely sure of that – Max Seelemann Apr 20 '11 at 13:54
  • 1
    @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);} – Massimo Cafaro Apr 20 '11 at 18:23
  • @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... – Max Seelemann Apr 22 '11 at 07:24
  • @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. – Massimo Cafaro Apr 22 '11 at 14:22
  • I guess if you have time you could create a sample that explicitly calls `CTFramesetterCreateWithAttributedString()` from multiple threads and see if it crashes. – nielsbot May 11 '11 at 21:44

5 Answers5

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!

Max Seelemann
  • 9,344
  • 4
  • 34
  • 40
  • Thanks for letting us know that! – Hejazi Apr 01 '12 at 14:37
  • 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? – lu yuan Nov 12 '12 at 10:07
  • @lu yuan: simply use `dispatch_sync()` top but the calls on a serial background queue. – Max Seelemann Nov 14 '12 at 22:22
  • 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. – lu yuan Nov 15 '12 at 02:16
  • As of May 9, 2013, this is still an issue: CTFramesetterCreateWithAttributedString cannot be called except sequentially on a single thread/queue. – jaredsinclair May 09 '13 at 20:06
  • As of Feb 25 2014 this is still an issue. Serialize calls to CTFramesetterCreateWithAttributedString. – duncanwilcox Feb 25 '14 at 14:04
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.

Kobski
  • 1,636
  • 15
  • 27
  • 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... – Max Seelemann May 14 '11 at 08:22
  • 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. – duncanwilcox Feb 25 '14 at 14:06
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.

Cocoanetics
  • 8,171
  • 2
  • 30
  • 57
  • 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... – Max Seelemann Jun 20 '11 at 08:59
  • 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. – Cocoanetics Jun 20 '11 at 09:59
  • 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. – Cocoanetics Jun 20 '11 at 10:00
  • 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. – Max Seelemann Jun 22 '11 at 14:05
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?

bastianwegge
  • 2,435
  • 1
  • 24
  • 30
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); 
Zeine
  • 21
  • 1