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.
This commit is contained in:
Sönke Ludwig 2020-05-27 12:45:53 +02:00
parent a9344c8490
commit a77f626b97

View file

@ -38,14 +38,38 @@ final class CFRunLoopEventLoop : KqueueEventLoopBase {
override bool doProcessEvents(Duration timeout) override bool doProcessEvents(Duration timeout)
@trusted { @trusted {
import std.algorithm.comparison : min;
// submit changes and process pending events // submit changes and process pending events
auto kres = doProcessEventsBase(0.seconds); auto kres = doProcessEventsBase(0.seconds);
if (kres) timeout = 0.seconds;
CFTimeInterval to = kres ? 0.0 : 1e-7 * timeout.total!"hnsecs"; // 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); auto res = CFRunLoopRunInMode(kCFRunLoopDefaultMode, to, true);
if (res != CFRunLoopRunResult.kCFRunLoopRunTimedOut) {
return kres || res == CFRunLoopRunResult.kCFRunLoopRunHandledSource; return kres || res == CFRunLoopRunResult.kCFRunLoopRunHandledSource;
} }
kres = doProcessEventsBase(0.seconds);
if (kres) break;
}
return kres;
}
override void dispose() override void dispose()
{ {
() @trusted { () @trusted {