// macOS/AppKit F17 demo — threading probes (../../../features/f17-threading.md).
// Deliberately violates (and obeys) AppKit's main-thread rule and records exactly
// what breaks. Five --probe=N run modes, each ending in a verdict line
// (`probe n=… result=ok|error|crash|deadlock|silent detail=…`) that survives any
// outcome: fatal-signal handlers (SIGSEGV/SIGBUS/SIGILL/SIGABRT/SIGTRAP) and an
// NSUncaughtExceptionHandler turn crashes into a flushed verdict + _exit(0) —
// capturing AppKit's exact assert/exception text — and a SIGALRM watchdog converts
// hangs into result=deadlock. The no-argument run (what CI executes) spawns every
// probe TWICE as a fresh fork+exec'd child (fork-without-exec is forbidden to
// Objective-C apps on macOS), so a crashed probe can never poison the next and the
// parent always reports.
//
// 1 NSWindow created on a SECOND thread (pthread), main thread pumps events.
// 2 [NSApp run] on main; AFTER the loop starts, a worker creates a window.
// 3 The sanctioned pattern: worker computes, marshals to main via BOTH
// performSelectorOnMainThread:withObject:waitUntilDone: and
// dispatch_async_f(dispatch_get_main_queue()) — proves both land on main.
// 4 Render from a second thread: (a) worker fills the CPU buffer and marshals
// setNeedsDisplay: to main vs (b) DIRECT cross-thread setNeedsDisplay: and
// lockFocusIfCanDraw + CGContext drawing from the worker.
// 5 NSPasteboard write/read + NSEvent post from a worker thread.
//
// Headless-safe: prints `SKIP:` and exits 0 when no window server is reachable.
module (module) appapp;
import (package) corecore.(module) core.attributeThis module contains UDA's (User Defined Attributes) either used in
the runtime or special UDA's recognized by compiler.
Attribute Name, Linkage, Description
gnuAbiTag, C++,
Declares an ABI tag on a C++ symbol.
mustuse,,
Ensures that values of a struct or union type are not discarded.
optional, Objective-C,
Makes an Objective-C interface method optional.
selector, Objective-C,
Attaches an Objective-C selector to a method.
standalone,,
Marks a shared module constructor as not depending on any
other module constructor being run first.
weak,,
Specifies that a global symbol should be emitted with weak linkage.
Source
core/attribute.d
attribute : selector;
import (package) corecore.(package) core.stdcstdc.(module) core.stdc.stdioD header file for C99 <stdio.h>
pubs.opengroup.org/onlinepubs/009695399/basedefs/stdio.h.html, stdio.h
Source
core/stdc/stdio.d
stdio : (alias) app.printf = int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogcprintf, (alias) app.fprintf = int core.stdc.stdio.fprintf(shared(core.stdc.stdio._IO_FILE)* stream, scope const(char*) format, scope const ...) nothrow @nogcfprintf, (alias) app.snprintf = int core.stdc.stdio.snprintf(scope char* s, ulong n, scope const(char*) format, scope const ...) nothrow @nogcsnprintf, (alias shared global) app.stderr = shared(core.stdc.stdio._IO_FILE*) core.stdc.stdio.stderrstderr, (alias) app.fflush = int core.stdc.stdio.fflush(shared(core.stdc.stdio._IO_FILE)* stream) nothrow @nogc @trustedfflush;
import (package) corecore.(package) core.stdcstdc.(module) core.stdc.stdlibD header file for C99.
pubs.opengroup.org/onlinepubs/009695399/basedefs/stdlib.h.html, stdlib.h
Source
core/stdc/stdlib.d
stdlib : (alias) app.getenv = char* core.stdc.stdlib.getenv(scope const(char*) name) nothrow @nogcgetenv, (alias) app.atoi = int core.stdc.stdlib.atoi(scope const(char*) nptr) nothrow @nogcatoi;
import (package) corecore.(package) core.stdcstdc.(module) core.stdc.stringD header file for C99.
pubs.opengroup.org/onlinepubs/009695399/basedefs/string.h.html, string.h
Source
core/stdc/string.d
string : (alias) app.strncmp = int core.stdc.string.strncmp(scope const(char*) s1, scope const(char*) s2, ulong n) pure nothrow @nogcstrncmp, (alias) app.strlen = ulong core.stdc.string.strlen(scope const(char*) s) pure nothrow @nogcstrlen;
import instrument : instr, instrInit, instrNowUs;
import objc.autorelease : autoreleasepool_push, autoreleasepool_pop; // objective-d
import objc.rt : SEL;
// --- Geometry ----------------------------------------------------------------------
extern (C) struct (struct) app.NSPointNSPoint
{
double (field) double app.NSPoint.xx, (field) double app.NSPoint.yy;
}
extern (C) struct (struct) app.NSSizeNSSize
{
double (field) double app.NSSize.widthwidth, (field) double app.NSSize.heightheight;
}
extern (C) struct (struct) app.NSRectNSRect
{
(struct) app.NSPointNSPoint (field) app.NSPoint app.NSRect.originorigin;
(struct) app.NSSizeNSSize (field) app.NSSize app.NSRect.sizesize;
}
// --- POSIX / CoreGraphics / libdispatch --------------------------------------------
extern (C) nothrow @nogc
{
uint uint app.CGMainDisplayID() nothrow @nogcCGMainDisplayID();
void void app.CFRelease(void* obj) nothrow @nogcCFRelease(void* (parameter) void* objobj);
void* void* app.CGSessionCopyCurrentDictionary() nothrow @nogcCGSessionCopyCurrentDictionary();
void* void* app.CFStringCreateWithCString(void* alloc, const(char)* s, uint encoding) nothrow @nogcCFStringCreateWithCString(void* (parameter) void* allocalloc, const(char)* (parameter) const(char)* ss, uint (parameter) uint encodingencoding);
void* void* app.CFDictionaryGetValue(void* dict, void* key) nothrow @nogcCFDictionaryGetValue(void* (parameter) void* dictdict, void* (parameter) void* keykey);
ubyte ubyte app.CFBooleanGetValue(void* b) nothrow @nogcCFBooleanGetValue(void* (parameter) void* bb);
void void app.CGContextSetRGBFillColor(void* ctx, double r, double g, double b, double a) nothrow @nogcCGContextSetRGBFillColor(void* (parameter) void* ctxctx, double (parameter) double rr, double (parameter) double gg, double (parameter) double bb, double (parameter) double aa);
void void app.CGContextFillRect(void* ctx, app.NSRect rect) nothrow @nogcCGContextFillRect(void* (parameter) void* ctxctx, (struct) app.NSRectNSRect (parameter) app.NSRect rectrect);
int int app.pthread_create(void** thread, const(void)* attr, extern (C) void* function(void*), void* arg) nothrow @nogcpthread_create(void** (parameter) void** threadthread, const(void)* (parameter) const(void)* attrattr,
void* function(void*), void* (parameter) void* argarg);
int int app.pthread_join(void* thread, void** retval) nothrow @nogcpthread_join(void* (parameter) void* threadthread, void** (parameter) void** retvalretval);
int int app.pthread_main_np() nothrow @nogcpthread_main_np();
int int app.usleep(uint usec) nothrow @nogcusleep(uint (parameter) uint usecusec);
uint uint app.alarm(uint seconds) nothrow @nogcalarm(uint (parameter) uint secondsseconds);
void function(int) void function(int) app.signal(int sig, extern (C) void function(int) handler) nothrow @nogcsignal(int (parameter) int sigsig, void function(int) (parameter) extern (C) void function(int) handlerhandler);
void void app._exit(int code) nothrow @nogc_exit(int (parameter) int codecode);
long long app.write(int fd, const(void)* buf, ulong n) nothrow @nogcwrite(int (parameter) int fdfd, const(void)* (parameter) const(void)* bufbuf, (alias) object.size_t = ulongsize_t (parameter) ulong nn);
int int app.fork() nothrow @nogcfork();
int int app.execv(const(char)* path, const(char*)* argv) nothrow @nogcexecv(const(char)* (parameter) const(char)* pathpath, const(char*)* (parameter) const(char*)* argvargv);
int int app.waitpid(int pid, int* status, int options) nothrow @nogcwaitpid(int (parameter) int pidpid, int* (parameter) int* statusstatus, int (parameter) int optionsoptions);
// dispatch_get_main_queue() is a macro for &_dispatch_main_q.
extern __gshared void* (__gshared global) void* app._dispatch_main_q_dispatch_main_q;
void void app.dispatch_async_f(void* queue, void* context, extern (C) void function(void*) work) nothrow @nogcdispatch_async_f(void* (parameter) void* queuequeue, void* (parameter) void* contextcontext, void function(void*) (parameter) extern (C) void function(void*) workwork);
alias (alias) app.NSUncaughtExceptionHandler = extern (C) void function(void* exception) nothrow @nogcNSUncaughtExceptionHandler = void function(void* (parameter) void* exceptionexception);
void void app.NSSetUncaughtExceptionHandler(extern (C) void function(void* exception) nothrow @nogc h) nothrow @nogcNSSetUncaughtExceptionHandler((alias) app.NSUncaughtExceptionHandler = extern (C) void function(void* exception) nothrow @nogcNSUncaughtExceptionHandler (parameter) extern (C) void function(void* exception) nothrow @nogc hh);
}
enum uint (constant) uint app.kCFStringEncodingUTF8 = 134217984ukCFStringEncodingUTF8 = 0x08000100;
enum : int
{
(enum value) app.SIGILL = 4SIGILL = 4,
(enum value) app.SIGABRT = 6SIGABRT = 6,
(enum value) app.SIGBUS = 10SIGBUS = 10,
(enum value) app.SIGSEGV = 11SIGSEGV = 11,
(enum value) app.SIGALRM = 14SIGALRM = 14,
(enum value) app.SIGTRAP = 5SIGTRAP = 5,
}
void void app.logSessionLockState()logSessionLockState()
{
void* (local variable) void* dictdict = void* app.CGSessionCopyCurrentDictionary() nothrow @nogcCGSessionCopyCurrentDictionary();
if ((local variable) void* dictdict is null)
{
instr("session", "lock_state=no_session_dictionary");
return;
}
void* (local variable) void* kLockedkLocked = void* app.CFStringCreateWithCString(void* alloc, const(char)* s, uint encoding) nothrow @nogcCFStringCreateWithCString(null, "CGSSessionScreenIsLocked",
(constant) uint app.kCFStringEncodingUTF8 = 134217984ukCFStringEncodingUTF8);
void* (local variable) void* lockedlocked = void* app.CFDictionaryGetValue(void* dict, void* key) nothrow @nogcCFDictionaryGetValue((local variable) void* dictdict, (local variable) void* kLockedkLocked);
instr("session", "screen_locked=%d",
locked !is null && CFBooleanGetValue(locked) ? 1 : 0);
void app.CFRelease(void* obj) nothrow @nogcCFRelease((local variable) void* kLockedkLocked);
void app.CFRelease(void* obj) nothrow @nogcCFRelease((local variable) void* dictdict);
}
// --- AppKit class declarations -----------------------------------------------------
extern (Objective-C):
extern class (class) app.NSObjectNSObject
{
void app.NSObject.performSelectorOnMainThreadperformSelectorOnMainThread(SEL sel, (class) app.NSObjectNSObject (parameter) NSObject argarg, bool wait)
@selector("performSelectorOnMainThread:withObject:waitUntilDone:");
}
extern class (class) app.NSStringNSString : (class) app.NSObjectNSObject
{
static (class) app.NSStringNSString app.NSString app.NSString.alloc()alloc() @selector("alloc");
(class) app.NSStringNSString app.NSString app.NSString.initWithUTF8String(const(char)* s)initWithUTF8String(const(char)* (parameter) const(char)* ss) @selector("initWithUTF8String:");
const(char)* const(char)* app.NSString.UTF8String()UTF8String() @selector("UTF8String");
}
extern class (class) app.NSExceptionNSException : (class) app.NSObjectNSObject
{
(class) app.NSStringNSString app.NSString app.NSException.name()name() @selector("name");
(class) app.NSStringNSString app.NSString app.NSException.reason()reason() @selector("reason");
}
extern class (class) app.NSPasteboardNSPasteboard : (class) app.NSObjectNSObject
{
static (class) app.NSPasteboardNSPasteboard app.NSPasteboard app.NSPasteboard.generalPasteboard()generalPasteboard() @selector("generalPasteboard");
long long app.NSPasteboard.changeCount()changeCount() @selector("changeCount");
long long app.NSPasteboard.clearContents()clearContents() @selector("clearContents");
bool bool app.NSPasteboard.setString(app.NSString s, app.NSString type)setString((class) app.NSStringNSString (parameter) app.NSString ss, (class) app.NSStringNSString (parameter) app.NSString typetype) @selector("setString:forType:");
(class) app.NSStringNSString app.NSString app.NSPasteboard.stringForType(app.NSString type)stringForType((class) app.NSStringNSString (parameter) app.NSString typetype) @selector("stringForType:");
}
extern class (class) app.NSDateNSDate : (class) app.NSObjectNSObject
{
static (class) app.NSDateNSDate app.NSDate app.NSDate.distantPast()distantPast() @selector("distantPast");
}
extern class (class) app.NSEventNSEvent : (class) app.NSObjectNSObject
{
static (class) app.NSEventNSEvent app.NSEvent app.NSEvent.otherEventWithType(long type, app.NSPoint location, ulong modifierFlags, double timestamp, long windowNumber, app.NSObject context, short subtype, long data1, long data2)otherEventWithType(long (parameter) long typetype, (struct) app.NSPointNSPoint (parameter) app.NSPoint locationlocation, ulong (parameter) ulong modifierFlagsmodifierFlags,
double (parameter) double timestamptimestamp, long (parameter) long windowNumberwindowNumber, (class) app.NSObjectNSObject (parameter) app.NSObject contextcontext, short (parameter) short subtypesubtype,
long (parameter) long data1data1, long (parameter) long data2data2)
@selector("otherEventWithType:location:modifierFlags:timestamp:windowNumber:context:subtype:data1:data2:");
long long app.NSEvent.type()type() @selector("type");
short short app.NSEvent.subtype()subtype() @selector("subtype");
long long app.NSEvent.data1()data1() @selector("data1");
}
extern class (class) app.NSTimerNSTimer : (class) app.NSObjectNSObject
{
static (class) app.NSTimerNSTimer app.NSTimer.scheduledTimerWithTimeIntervalscheduledTimerWithTimeInterval(double interval, (class) app.NSObjectNSObject target,
SEL sel, (class) app.NSObjectNSObject userInfo, bool repeats)
@selector("scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:");
}
extern class (class) app.NSApplicationNSApplication : (class) app.NSObjectNSObject
{
static (class) app.NSApplicationNSApplication app.NSApplication app.NSApplication.sharedApplication()sharedApplication() @selector("sharedApplication");
void void app.NSApplication.setActivationPolicy(long policy)setActivationPolicy(long (parameter) long policypolicy) @selector("setActivationPolicy:");
void void app.NSApplication.run()run() @selector("run");
void void app.NSApplication.stop(app.NSObject sender)stop((class) app.NSObjectNSObject (parameter) app.NSObject sendersender) @selector("stop:");
void void app.NSApplication.postEvent(app.NSEvent event, bool atStart)postEvent((class) app.NSEventNSEvent (parameter) app.NSEvent eventevent, bool (parameter) bool atStartatStart) @selector("postEvent:atStart:");
void void app.NSApplication.sendEvent(app.NSEvent event)sendEvent((class) app.NSEventNSEvent (parameter) app.NSEvent eventevent) @selector("sendEvent:");
(class) app.NSEventNSEvent app.NSEvent app.NSApplication.nextEventMatchingMask(ulong mask, app.NSDate untilDate, app.NSString inMode, bool dequeue)nextEventMatchingMask(ulong (parameter) ulong maskmask, (class) app.NSDateNSDate (parameter) app.NSDate untilDateuntilDate, (class) app.NSStringNSString (parameter) app.NSString inModeinMode,
bool (parameter) bool dequeuedequeue)
@selector("nextEventMatchingMask:untilDate:inMode:dequeue:");
}
extern class (class) app.NSGraphicsContextNSGraphicsContext : (class) app.NSObjectNSObject
{
static (class) app.NSGraphicsContextNSGraphicsContext app.NSGraphicsContext app.NSGraphicsContext.currentContext()currentContext() @selector("currentContext");
void* void* app.NSGraphicsContext.CGContext()CGContext() @selector("CGContext");
}
extern class (class) app.NSViewNSView : (class) app.NSObjectNSObject
{
(struct) app.NSRectNSRect app.NSRect app.NSView.bounds()bounds() @selector("bounds");
void void app.NSView.setNeedsDisplay(bool flag)setNeedsDisplay(bool (parameter) bool flagflag) @selector("setNeedsDisplay:");
bool bool app.NSView.lockFocusIfCanDraw()lockFocusIfCanDraw() @selector("lockFocusIfCanDraw");
void void app.NSView.unlockFocus()unlockFocus() @selector("unlockFocus");
}
extern class (class) app.NSWindowNSWindow : (class) app.NSObjectNSObject
{
(class) app.NSWindowNSWindow app.NSWindow app.NSWindow.initWithContentRect(app.NSRect contentRect, ulong styleMask, ulong backing, bool defer)initWithContentRect((struct) app.NSRectNSRect (parameter) app.NSRect contentRectcontentRect, ulong (parameter) ulong styleMaskstyleMask,
ulong (parameter) ulong backingbacking, bool (parameter) bool deferdefer) @selector("initWithContentRect:styleMask:backing:defer:");
void void app.NSWindow.setTitle(app.NSString title)setTitle((class) app.NSStringNSString (parameter) app.NSString titletitle) @selector("setTitle:");
void void app.NSWindow.setContentView(app.NSObject view)setContentView((class) app.NSObjectNSObject (parameter) app.NSObject viewview) @selector("setContentView:");
void void app.NSWindow.setReleasedWhenClosed(bool b)setReleasedWhenClosed(bool (parameter) bool bb) @selector("setReleasedWhenClosed:");
void void app.NSWindow.makeKeyAndOrderFront(app.NSObject sender)makeKeyAndOrderFront((class) app.NSObjectNSObject (parameter) app.NSObject sendersender) @selector("makeKeyAndOrderFront:");
void void app.NSWindow.orderOut(app.NSObject sender)orderOut((class) app.NSObjectNSObject (parameter) app.NSObject sendersender) @selector("orderOut:");
long long app.NSWindow.windowNumber()windowNumber() @selector("windowNumber");
}
class (class) app.PlainWindowPlainWindow : (class) app.NSWindowNSWindow
{
static (class) app.PlainWindowPlainWindow app.PlainWindow app.PlainWindow.alloc()alloc() @selector("alloc");
}
// Probe 4's view: drawRect logs which thread it runs on and blits g_fill.
class (class) app.ProbeViewProbeView : (class) app.NSViewNSView
{
static (class) app.ProbeViewProbeView app.ProbeView app.ProbeView.alloc()alloc() @selector("alloc");
(class) app.ProbeViewProbeView app.ProbeView app.ProbeView.initWithFrame(app.NSRect frame)initWithFrame((struct) app.NSRectNSRect (parameter) app.NSRect frameframe) @selector("initWithFrame:");
void void app.ProbeView.drawRect(app.NSRect dirtyRect)drawRect((struct) app.NSRectNSRect (parameter) app.NSRect dirtyRectdirtyRect) @selector("drawRect:")
{
++g_draws;
instr("draw_rect", "n=%d main_thread=%d fill=%.2f", g_draws,
pthread_main_np(), g_fill);
void* _error_ ctxctx = NSGraphicsContext.NSGraphicsContext.currentContextcurrentContext().NSGraphicsContext.currentContext().CGContextCGContext();
CGContextSetRGBFillColor(ctx, g_fill, 0.3, 0.5, 1);
CGContextFillRect(ctx, this.this.boundsbounds());
}
}
// Main-thread marshaling target for probe 3 (performSelectorOnMainThread:).
class (class) app.MarshalMarshal : (class) app.NSObjectNSObject
{
static (class) app.MarshalMarshal app.Marshal app.Marshal.alloc()alloc() @selector("alloc");
(class) app.MarshalMarshal app.Marshal app.Marshal.init()init() @selector("init");
void void app.Marshal.report(app.NSObject arg)report((class) app.NSObjectNSObject (parameter) app.NSObject argarg) @selector("report:")
{
g_perfSelOnMain = pthread_main_np();
instr("marshal", "route=performSelectorOnMainThread main_thread=%d", g_perfSelOnMain);
maybeFinishProbe3();
}
void void app.Marshal.tick(app.NSTimer t)tick((class) app.NSTimerNSTimer (parameter) app.NSTimer tt) @selector("tick:")
{
onTick();
}
}
extern (D): // back to D linkage
enum (constant) int app.NSApplicationActivationPolicyRegular = 0NSApplicationActivationPolicyRegular = 0;
enum ulong (constant) ulong app.NSWindowStyleMaskTitled = 1LUNSWindowStyleMaskTitled = 1;
enum ulong (constant) ulong app.NSBackingStoreBuffered = 2LUNSBackingStoreBuffered = 2;
enum long (constant) long app.NSEventTypeApplicationDefined = 15LNSEventTypeApplicationDefined = 15;
__gshared (class) app.NSApplicationNSApplication (__gshared global) app.NSApplication app.g_appg_app;
__gshared (class) app.NSWindowNSWindow (__gshared global) app.NSWindow app.g_wing_win;
__gshared (class) app.ProbeViewProbeView (__gshared global) app.ProbeView app.g_viewg_view;
__gshared (class) app.MarshalMarshal (__gshared global) app.Marshal app.g_marshalg_marshal;
__gshared int (__gshared global) int app.g_probeg_probe;
__gshared int (__gshared global) int app.g_ticksg_ticks;
__gshared int (__gshared global) int app.g_drawsg_draws;
__gshared int (__gshared global) int app.g_perfSelOnMaing_perfSelOnMain = -1;
__gshared int (__gshared global) int app.g_dispatchOnMaing_dispatchOnMain = -1;
__gshared bool (__gshared global) bool app.g_workerDoneg_workerDone;
__gshared bool (__gshared global) bool app.g_verdictWritteng_verdictWritten;
__gshared double (__gshared global) double app.g_fillg_fill = 0.2;
__gshared char[256] (__gshared global) char[256] app.g_detailg_detail;
(class) app.NSStringNSString app.NSString app.nsstr(const(char)* s)nsstr(const(char)* (parameter) const(char)* ss)
{
return NSString.NSString.allocalloc().NSString.alloc().initWithUTF8StringinitWithUTF8String(s);
}
// --- Verdicts that survive crashes ---------------------------------------------------
// Async-signal-safe: snprintf into a static buffer + write(2).
void void app.verdict(const(char)* result, const(char)* detail) nothrow @nogcverdict(const(char)* (parameter) const(char)* resultresult, const(char)* (parameter) const(char)* detaildetail) nothrow @nogc
{
if (g_verdictWritten)
return;
g_verdictWritten = true;
__gshared char[512] _error_ bufbuf;
immutable _error_ nn = snprintf(buf.ptr, buf.length,
"%llu APPKIT_F17 probe n=%d result=%s detail=%s\n",
instrNowUs(), g_probe, result, detail);
write(2, buf.ptr, n > 0 ? n : 0);
}
extern (C) void void app.onFatalSignal(int sig) nothrow @nogconFatalSignal(int (parameter) int sigsig) nothrow @nogc
{
__gshared char[128] _error_ dd;
snprintf(d.ptr, d.length, "fatal_signal_%d%s", sig,
sig == SIGABRT ? "_abort(see_exception_log_above)".ptr : "".ptr);
verdict("crash", d.ptr);
_exit(0); // crashing is this probe's job; the verdict got out
}
extern (C) void void app.onAlarm(int sig) nothrow @nogconAlarm(int (parameter) int sigsig) nothrow @nogc
{
verdict("deadlock", "watchdog_12s_expired");
_exit(0);
}
// AppKit's main-thread violations usually surface as NSExceptions → capture the
// exact assert text before abort() turns it into SIGABRT.
extern (C) void void app.onUncaught(void* exception)onUncaught(void* (parameter) void* exceptionexception)
{
(unresolved type) NSExceptionNSException _error_ ee = cast((unresolved type) NSExceptionNSException) exception;
instr("uncaught_exception", "name=%s reason=%s",
e !is null ? e.e.namename().e.name().UTF8StringUTF8String() : "null",
e !is null ? e.e.reasonreason().e.reason().UTF8StringUTF8String() : "null");
__gshared char[256] _error_ dd;
snprintf(d.ptr, d.length, "nsexception:%s",
e !is null ? e.e.namename().e.name().UTF8StringUTF8String() : "unknown");
verdict("crash", d.ptr);
_exit(0);
}
void void app.installHandlers()installHandlers()
{
signal(SIGSEGV, &onFatalSignal);
signal(SIGBUS, &onFatalSignal);
signal(SIGILL, &onFatalSignal);
signal(SIGABRT, &onFatalSignal);
signal(SIGTRAP, &onFatalSignal);
signal(SIGALRM, &onAlarm);
NSSetUncaughtExceptionHandler(cast((unresolved type) NSUncaughtExceptionHandlerNSUncaughtExceptionHandler) &onUncaught);
alarm(12);
}
// --- Run-loop helpers ----------------------------------------------------------------
void void app.stopApp()stopApp()
{
g_app.g_app.stopstop(null);
(unresolved type) NSEventNSEvent _error_ evev = NSEvent.NSEvent.otherEventWithTypeotherEventWithType(NSEventTypeApplicationDefined,
NSPoint(0, 0), 0, 0, 0, null, 0, 0, 0);
g_app.g_app.postEventpostEvent(ev, true);
}
// Pump the main-thread event queue manually for ~ms milliseconds.
void void app.pumpFor(int ms)pumpFor(int (parameter) int msms)
{
(unresolved type) NSStringNSString _error_ modemode = nsstr("kCFRunLoopDefaultMode");
foreach ((parameter) ii; 0 .. ms / 10)
{
for (;;)
{
(unresolved type) NSEventNSEvent _error_ ee = g_app.g_app.nextEventMatchingMasknextEventMatchingMask(ulong.max, NSDate.NSDate.distantPastdistantPast(),
mode, true);
if (e is null)
break;
if (e.e.typetype() == NSEventTypeApplicationDefined && e.e.data1data1() == 42)
instr("event", "kind=worker_posted_event_received main_thread=%d data1=%ld",
pthread_main_np(), e.e.data1data1());
g_app.g_app.sendEventsendEvent(e);
}
usleep(10_000);
}
}
void void app.createWindow(const(char)* who)createWindow(const(char)* (parameter) const(char)* whowho)
{
instr("step", "name=NSWindow_init thread=%s main_thread=%d", who, pthread_main_np());
g_win = PlainWindow.PlainWindow.allocalloc().PlainWindow.alloc().initWithContentRectinitWithContentRect(
NSRect(NSPoint(120, 120), NSSize(320, 240)),
NSWindowStyleMaskTitled, NSBackingStoreBuffered, false);
g_win.g_win.setTitlesetTitle(nsstr("wsi-f17"));
g_win.g_win.setReleasedWhenClosedsetReleasedWhenClosed(false);
g_view = ProbeView.ProbeView.allocalloc().ProbeView.alloc().initWithFrameinitWithFrame(NSRect(NSPoint(0, 0), NSSize(320, 240)));
g_win.g_win.setContentViewsetContentView(g_view);
g_win.g_win.makeKeyAndOrderFrontmakeKeyAndOrderFront(null);
instr("step", "name=window_created thread=%s win_num=%ld", who, g_win.g_win.windowNumberwindowNumber());
}
// --- Probe 1: NSWindow on a second thread; main pumps --------------------------------
extern (C) void* void* app.probe1Worker(void* arg)probe1Worker(void* (parameter) void* argarg)
{
auto _error_ poolpool = autoreleasepool_push();
createWindow("worker");
autoreleasepool_pop(pool);
g_workerDone = true;
return null;
}
void void app.probe1()probe1()
{
void* _error_ thth;
pthread_create(&th, null, &probe1Worker, null);
pumpFor(2500); // events pumped on main while the worker violates the rule
pthread_join(th, null);
if (g_win !is null)
g_win.g_win.orderOutorderOut(null);
char* _error_ dd = g_detail.ptr;
snprintf(d, g_detail.length, "window_created_off_main=%d win=%ld no_assert_no_crash",
g_workerDone ? 1 : 0, g_win !is null ? g_win.g_win.windowNumberwindowNumber() : -1);
verdict(g_workerDone ? "silent" : "error", d);
}
// --- Probe 2: [NSApp run] on main; worker creates the window after run starts --------
extern (C) void* void* app.probe2Worker(void* arg)probe2Worker(void* (parameter) void* argarg)
{
auto _error_ poolpool = autoreleasepool_push();
createWindow("worker_after_run");
autoreleasepool_pop(pool);
g_workerDone = true;
return null;
}
void void app.probe2()probe2()
{
g_marshal = Marshal.Marshal.allocalloc().Marshal.alloc().initinit();
NSTimer.NSTimer.scheduledTimerWithTimeIntervalscheduledTimerWithTimeInterval(0.3, g_marshal, SEL.register("tick:"), null, true);
instr("step", "name=NSApp_run");
g_app.g_app.runrun(); // ticks drive the script (onTick)
if (g_win !is null)
g_win.g_win.orderOutorderOut(null);
char* _error_ dd = g_detail.ptr;
snprintf(d, g_detail.length,
"run_loop_alive=1 window_created_on_worker_during_run=%d draws=%d",
g_workerDone ? 1 : 0, g_draws);
verdict(g_workerDone ? "silent" : "error", d);
}
// --- Probe 3: the sanctioned pattern --------------------------------------------------
extern (C) void void app.dispatchedToMain(void* ctx)dispatchedToMain(void* (parameter) void* ctxctx) // runs on the main queue
{
g_dispatchOnMain = pthread_main_np();
instr("marshal", "route=dispatch_async_main_queue main_thread=%d", g_dispatchOnMain);
maybeFinishProbe3();
}
void void app.maybeFinishProbe3()maybeFinishProbe3()
{
if (g_probe != 3 || g_perfSelOnMain < 0 || g_dispatchOnMain < 0)
return;
char* _error_ dd = g_detail.ptr;
snprintf(d, g_detail.length,
"performSelectorOnMainThread_on_main=%d dispatch_async_on_main=%d",
g_perfSelOnMain, g_dispatchOnMain);
verdict(g_perfSelOnMain == 1 && g_dispatchOnMain == 1 ? "ok" : "error", d);
stopApp();
}
extern (C) void* void* app.probe3Worker(void* arg)probe3Worker(void* (parameter) void* argarg)
{
// "Work" off-main, then marshal the UI-facing part to the main thread.
ulong _error_ sumsum;
foreach ((parameter) ii; 0 .. 1_000_000)
sum += i;
instr("step", "name=worker_work_done sum=%llu main_thread=%d", sum, pthread_main_np());
g_marshal.g_marshal.performSelectorOnMainThreadperformSelectorOnMainThread(SEL.register("report:"), null, false);
dispatch_async_f(cast(void*) &_dispatch_main_q, null, &dispatchedToMain);
return null;
}
void void app.probe3()probe3()
{
g_marshal = Marshal.Marshal.allocalloc().Marshal.alloc().initinit();
void* _error_ thth;
pthread_create(&th, null, &probe3Worker, null);
instr("step", "name=NSApp_run");
g_app.g_app.runrun(); // maybeFinishProbe3 stops it
pthread_join(th, null);
}
// --- Probe 4: render from a second thread ---------------------------------------------
extern (C) void void app.marshaledSetNeedsDisplay(void* ctx)marshaledSetNeedsDisplay(void* (parameter) void* ctxctx)
{
instr("render", "route=marshaled_setNeedsDisplay main_thread=%d", pthread_main_np());
g_view.g_view.setNeedsDisplaysetNeedsDisplay(true);
}
extern (C) void* void* app.probe4Worker(void* arg)probe4Worker(void* (parameter) void* argarg)
{
auto _error_ poolpool = autoreleasepool_push();
// (a) The sanctioned render loop: CPU work on the worker, dirty-mark on main.
g_fill = 0.9;
instr("render", "route=worker_buffer_fill fill=0.9 main_thread=%d", pthread_main_np());
dispatch_async_f(cast(void*) &_dispatch_main_q, null, &marshaledSetNeedsDisplay);
usleep(600_000);
immutable _error_ drawsAfterMarshaldrawsAfterMarshal = g_draws;
// (b1) DIRECT cross-thread setNeedsDisplay: (docs: not thread-safe).
g_fill = 0.5;
instr("render", "route=direct_setNeedsDisplay_from_worker main_thread=%d",
pthread_main_np());
g_view.g_view.setNeedsDisplaysetNeedsDisplay(true);
usleep(600_000);
// (b2) DIRECT lockFocus + CGContext drawing from the worker.
instr("render", "route=direct_lockFocusIfCanDraw_from_worker main_thread=%d",
pthread_main_np());
immutable _error_ lockedlocked = g_view.g_view.lockFocusIfCanDrawlockFocusIfCanDraw();
instr("render", "lockFocusIfCanDraw=%d", locked ? 1 : 0);
if (locked)
{
void* _error_ ctxctx = NSGraphicsContext.NSGraphicsContext.currentContextcurrentContext().NSGraphicsContext.currentContext().CGContextCGContext();
CGContextSetRGBFillColor(ctx, 1, 0, 0, 1);
CGContextFillRect(ctx, NSRect(NSPoint(0, 0), NSSize(64, 64)));
g_view.g_view.unlockFocusunlockFocus();
instr("render", "direct_cgcontext_draw=done from_worker=1");
}
autoreleasepool_pop(pool);
char* _error_ dd = g_detail.ptr;
snprintf(d, g_detail.length,
"marshaled_draws=%d direct_setNeedsDisplay_survived=1 lockFocusIfCanDraw=%d total_draws=%d",
drawsAfterMarshal, locked ? 1 : 0, g_draws);
g_workerDone = true;
return null;
}
void void app.probe4()probe4()
{
createWindow("main");
g_marshal = Marshal.Marshal.allocalloc().Marshal.alloc().initinit();
NSTimer.NSTimer.scheduledTimerWithTimeIntervalscheduledTimerWithTimeInterval(0.3, g_marshal, SEL.register("tick:"), null, true);
void* _error_ thth;
pthread_create(&th, null, &probe4Worker, null);
instr("step", "name=NSApp_run");
g_app.g_app.runrun(); // onTick stops once the worker is done
pthread_join(th, null);
g_win.g_win.orderOutorderOut(null);
verdict("silent", g_detail.ptr);
}
// --- Probe 5: NSPasteboard + NSEvent APIs from a worker -------------------------------
extern (C) void* void* app.probe5Worker(void* arg)probe5Worker(void* (parameter) void* argarg)
{
auto _error_ poolpool = autoreleasepool_push();
auto _error_ pbpb = NSPasteboard.NSPasteboard.generalPasteboardgeneralPasteboard();
immutable _error_ cccc = pb.pb.clearContentsclearContents();
immutable _error_ okok = pb.pb.setStringsetString(nsstr("f17-worker-thread"), nsstr("public.utf8-plain-text"));
(unresolved type) NSStringNSString _error_ backback = pb.pb.stringForTypestringForType(nsstr("public.utf8-plain-text"));
instr("step", "name=pasteboard_from_worker main_thread=%d change_count=%ld set_ok=%d readback=%s",
pthread_main_np(), cc, ok ? 1 : 0, back !is null ? back.back.UTF8StringUTF8String() : "nil");
(unresolved type) NSEventNSEvent _error_ evev = NSEvent.NSEvent.otherEventWithTypeotherEventWithType(NSEventTypeApplicationDefined,
NSPoint(0, 0), 0, 0, 0, null, 7, 42, 0);
g_app.g_app.postEventpostEvent(ev, false); // postEvent: from a non-main thread
instr("step", "name=postEvent_from_worker main_thread=%d", pthread_main_np());
char* _error_ dd = g_detail.ptr;
snprintf(d, g_detail.length,
"pasteboard_set_ok=%d readback_ok=%d postEvent_from_worker=accepted",
ok ? 1 : 0,
back !is null && strncmp(back.back.UTF8StringUTF8String(), "f17-worker-thread", 17) == 0 ? 1 : 0);
autoreleasepool_pop(pool);
g_workerDone = true;
return null;
}
void void app.probe5()probe5()
{
void* _error_ thth;
pthread_create(&th, null, &probe5Worker, null);
pumpFor(1500); // main pumps; does the worker's posted event arrive here?
pthread_join(th, null);
verdict(g_workerDone ? "ok" : "error", g_detail.ptr);
}
// --- Tick script (probes 2 and 4) ------------------------------------------------------
void void app.onTick()onTick()
{
++g_ticks;
if (g_probe == 2)
{
if (g_ticks == 1)
{
instr("step", "name=spawn_worker_inside_run tick=%d", g_ticks);
void* _error_ thth;
pthread_create(&th, null, &probe2Worker, null);
}
if (g_ticks >= 6)
stopApp();
}
else if (g_probe == 4)
{
if (g_workerDone || g_ticks >= 20)
stopApp();
}
}
// --- Probe driver / parent -----------------------------------------------------------
int int app.runProbe(int n)runProbe(int (parameter) int nn)
{
g_probe = n;
installHandlers();
instr("probe_start", "n=%d main_thread=%d", n, pthread_main_np());
if (CGMainDisplayID() == 0)
{
printf("SKIP: no macOS window server (CGMainDisplayID == 0)\n");
return 0;
}
logSessionLockState();
auto _error_ poolpool = autoreleasepool_push();
g_app = NSApplication.NSApplication.sharedApplicationsharedApplication();
g_app.g_app.setActivationPolicysetActivationPolicy(NSApplicationActivationPolicyRegular);
switch (n)
{
case 1:
probe1();
break;
case 2:
probe2();
break;
case 3:
probe3();
break;
case 4:
probe4();
break;
case 5:
probe5();
break;
default:
verdict("error", "unknown_probe");
break;
}
autoreleasepool_pop(pool);
return 0;
}
// Parent: fork+exec each probe twice (the spec's nondeterminism rule); a crashed
// child can never poison the next, and the parent reports every exit status.
int int app.runAll(const(char)* self)runAll(const(char)* (parameter) const(char)* selfself)
{
foreach ((parameter) nn; 1 .. 6)
{
foreach ((parameter) runrun; 1 .. 3)
{
char[32] _error_ argbufargbuf;
snprintf(argbuf.ptr, argbuf.length, "--probe=%d", n);
const(char)*[3] _error_ argvargv = [self, argbuf.ptr, null];
immutable _error_ pidpid = fork();
if (pid == 0)
{
execv(self, argv.ptr);
_exit(127);
}
int _error_ statusstatus;
waitpid(pid, &status, 0);
immutable _error_ exitedexited = (status & 0x7f) == 0;
instr("probe_run", "n=%d run=%d exit_normal=%d code=%d term_signal=%d",
n, run, exited ? 1 : 0, exited ? (status >> 8) & 0xff : -1,
exited ? 0 : status & 0x7f);
}
}
instr("all_probes_done", "runs=10");
return 0;
}
int int D main(string[] args)main((alias) object.string = stringstring[] (parameter) string[] argsargs)
{
instrInit("APPKIT_F17");
if (args.length > 1 && args[1].length > 8 && args[1][0 .. 8] == "--probe=")
return runProbe(atoi(args[1].ptr + 8));
instr("init_start", "mode=all_probes_forked_twice");
if (CGMainDisplayID() == 0)
{
printf("SKIP: no macOS window server (CGMainDisplayID == 0)\n");
return 0;
}
char[1024] _error_ selfself;
snprintf(self.ptr, self.length, "%s", args[0].ptr);
return runAll(self.ptr);
}