Repository navigation
Concurrent NSFont creation crashes inside libfontconfig via the backend #239
Description
Summary
Creating NSFont instances concurrently from several threads crashes inside libfontconfig, reached from libgnustep-back. A short program whose worker threads do nothing but call +[NSFont fontWithName:size:] crashes on every run.
On macOS, NSFont instances are immutable and font lookup is safe from any thread, so code written against Cocoa crashes under GNUstep.
Environment
- GNUstep Base
1.31, GUI0.32, Back0.32(X11 backend) libs-backat45eeba4,libs-guiatf03c34410- clang /
libobjc2/libdispatch - Debian 13, kernel 6.12.101,
fontconfig2.15.0-2.3, x86_64
Reproducer
// clang `gnustep-config --objc-flags` fontrace.m -o fontrace \
// `gnustep-config --gui-libs` -ldispatch
#import <AppKit/AppKit.h>
#import <dispatch/dispatch.h>
#include <stdio.h>
int main(void)
{
[NSAutoreleasePool new];
[NSApplication sharedApplication]; // bring the backend up
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
int task = 0;
for (; task < 200; task++) {
dispatch_group_async(group, queue, ^{
@autoreleasepool {
int i = 0;
for (; i < 60; i++) {
NSFont *font = [NSFont fontWithName: @"Helvetica"
size: 8.0 + (CGFloat)(i % 24)];
(void)[font pointSize];
}
}
});
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
printf("completed without crashing\n");
return 0;
}10 crashes in 10 runs, split between SIGSEGV and SIGABRT with glibc reporting malloc_consolidate(): unaligned fastbin chunk detected.
Backtrace
Thread 18 "oot.default-qos" received signal SIGSEGV, Segmentation fault.
#0 libfontconfig.so.1
#1 libfontconfig.so.1
#2 libgnustep-back-032.bundle
#3 libgnustep-back-032.bundle
#4 libgnustep-back-032.bundle
#5 libgnustep-back-032.bundle
Narrowing
Two control variants isolate which path is unsafe:
| Variant | Result |
|---|---|
| As above (concurrent create and release) | 10 / 10 crashed |
Same, with @synchronized ([NSFont class]) around the font work |
0 / 10 crashed |
| All 24 fonts created and retained on the main thread first, so workers only take the cache-hit path | 0 / 10 crashed |
The read path is fine. Concurrent font construction is what reaches the backend and crashes.
Impact
Any GNUstep application that measures or renders text off the main thread can crash, and because the failure mode is heap corruption it often surfaces far from its cause — in our case as unrelated crashes elsewhere in a test suite. Rendering a document off the main thread is a natural design: it is CPU-bound, per-document, and touches no UI.
Note
Companion issue: gnustep/libs-gui#932, for the unsynchronized globalFontMap cache in NSFont.m, which shows up as NSMapRemove crashes in the same workload. The two look related but are separately fixable, and locking on the gui side alone might only serialise enough to hide this one rather than fix it.
Activity