Skip to content

Commit 1cc5a6d

Browse files
committed
[Windows] Fix main dispatch queue processing in CFRunLoop
Dispatch uses SetEvent to notify Foundation about pending work in main queue. CFRunLoop handles event, but not resets it, leaving event in "pending" state forever. This makes all consequent calls to __CFRunLoopRun exit immediately without waiting for real events.
1 parent cfac32b commit 1cc5a6d

File tree

2 files changed

+46
-0
lines changed

2 files changed

+46
-0
lines changed

CoreFoundation/RunLoop.subproj/CFRunLoop.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2874,6 +2874,16 @@ static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInter
28742874
#if TARGET_OS_WIN32 || TARGET_OS_LINUX
28752875
void *msg = 0;
28762876
#endif
2877+
2878+
#if TARGET_OS_WIN32
2879+
// Dispatch sets this event to notify us about
2880+
// pending work on main queue.
2881+
// We should consume it properly, or CFRunLoop will
2882+
// immediately poke empty main queue on next run(s)
2883+
// and exit without any actual work done.
2884+
ResetEvent(dispatchPort);
2885+
#endif
2886+
28772887
cf_trace(KDEBUG_EVENT_CFRL_IS_CALLING_DISPATCH | DBG_FUNC_START, rl, rlm, msg, livePort);
28782888
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
28792889
cf_trace(KDEBUG_EVENT_CFRL_IS_CALLING_DISPATCH | DBG_FUNC_END, rl, rlm, msg, livePort);

Tests/Foundation/Tests/TestRunLoop.swift

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,41 @@ class TestRunLoop : XCTestCase {
125125
XCTAssertTrue(didDeallocate)
126126
}
127127

128+
func test_mainDispatchQueueCallout() {
129+
let runLoop = RunLoop.current
130+
131+
var asyncExecuted = false
132+
DispatchQueue.main.async {
133+
asyncExecuted = true
134+
}
135+
136+
// RunLoop should service main queue
137+
_ = runLoop.run(mode: .default, before: Date(timeIntervalSinceNow: 2))
138+
XCTAssertTrue(asyncExecuted, "Main queue async code should be executed")
139+
140+
asyncExecuted = false
141+
DispatchQueue.main.async {
142+
asyncExecuted = true
143+
}
144+
145+
// Second run to be sure RunLoop will not stuck
146+
_ = runLoop.run(mode: .default, before: Date(timeIntervalSinceNow: 2))
147+
XCTAssertTrue(asyncExecuted, "Main queue async code should be executed")
148+
149+
var timerFired = false
150+
let dummyTimer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { _ in
151+
timerFired = true
152+
}
153+
runLoop.add(dummyTimer, forMode: .default)
154+
155+
// At this moment RunLoop has no work to do except waiting for timer.
156+
// But RunLoop will exit prematurely if event from previous async calls
157+
// got stuck in wrong state.
158+
_ = runLoop.run(mode: .default, before: Date(timeIntervalSinceNow: 2))
159+
160+
XCTAssertTrue(timerFired, "Time should fire already")
161+
}
162+
128163
static var allTests : [(String, (TestRunLoop) -> () throws -> Void)] {
129164
return [
130165
("test_constants", test_constants),
@@ -134,6 +169,7 @@ class TestRunLoop : XCTestCase {
134169
("test_runLoopLimitDate", test_runLoopLimitDate),
135170
("test_runLoopPoll", test_runLoopPoll),
136171
("test_addingRemovingPorts", test_addingRemovingPorts),
172+
("test_mainDispatchQueueCallout", test_mainDispatchQueueCallout)
137173
]
138174
}
139175
}

0 commit comments

Comments
 (0)