From a77f626b97c5af97e823239a974351e8da28b5ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6nke=20Ludwig?= Date: Wed, 27 May 2020 12:45:53 +0200 Subject: [PATCH] Implement an alternative workaround for CFRunLoop hangs. Limits the timeout for the CFRunLoop call to one second, after which kqueue will be re-checked again manually, guaranteeing that if a hang occurs, it will be resolved after at most one second. --- source/eventcore/drivers/posix/cfrunloop.d | 30 +++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/source/eventcore/drivers/posix/cfrunloop.d b/source/eventcore/drivers/posix/cfrunloop.d index 53007ce..6fbcbe7 100644 --- a/source/eventcore/drivers/posix/cfrunloop.d +++ b/source/eventcore/drivers/posix/cfrunloop.d @@ -38,12 +38,36 @@ final class CFRunLoopEventLoop : KqueueEventLoopBase { override bool doProcessEvents(Duration timeout) @trusted { + import std.algorithm.comparison : min; + // submit changes and process pending events auto kres = doProcessEventsBase(0.seconds); + if (kres) timeout = 0.seconds; - CFTimeInterval to = kres ? 0.0 : 1e-7 * timeout.total!"hnsecs"; - auto res = CFRunLoopRunInMode(kCFRunLoopDefaultMode, to, true); - return kres || res == CFRunLoopRunResult.kCFRunLoopRunHandledSource; + // NOTE: the timeout per CFRunLoopRunInMode call is limited to one + // second to work around the issue that the kqueue CFFileDescriptor + // sometimes does not fire. There seems to be some kind of race- + // condition, between the edge-triggered kqueue events and + // CFFileDescriptorEnableCallBacks/CFRunLoopRunInMode. + // + // Even changing the order of calls in processKqueue to first + // re-enable the callbacks and *then* process the already pending + // events does not help (and is also eplicitly discouraged in + // Apple's documentation). + while (timeout > 0.seconds) { + auto tol = min(timeout, 1.seconds); + timeout -= tol; + CFTimeInterval to = 1e-7 * tol.total!"hnsecs"; + auto res = CFRunLoopRunInMode(kCFRunLoopDefaultMode, to, true); + if (res != CFRunLoopRunResult.kCFRunLoopRunTimedOut) { + return kres || res == CFRunLoopRunResult.kCFRunLoopRunHandledSource; + } + + kres = doProcessEventsBase(0.seconds); + if (kres) break; + } + + return kres; } override void dispose()