Skip to content

Commit 3b535ec

Browse files
committed
Use NIOThreadPoolTaskExecutor for NIOAsyncWriter test that hangs on Android emulator
The testSuspendingBufferedYield_whenWriterFinished test requires at least two threads in the concurrency thread pool because it blocks one task, which waits for another task to set a condition. In environments where the global concurrency thread pool doesn’t have at least two threads available, the test will fail, as observed on the Android emulator running with a single virtual core (see discussion in apple#3044). Using a custom task executor guarantees that at least two threads are available for the test, regardless of the width of the global concurrency thread pool.
1 parent c5de24b commit 3b535ec

File tree

1 file changed

+39
-34
lines changed

1 file changed

+39
-34
lines changed

Tests/NIOCoreTests/AsyncSequences/NIOAsyncWriterTests.swift

Lines changed: 39 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import DequeModule
1616
import NIOConcurrencyHelpers
17+
import NIOTestUtils
1718
import XCTest
1819

1920
@testable import NIOCore
@@ -607,50 +608,54 @@ final class NIOAsyncWriterTests: XCTestCase {
607608
}
608609

609610
func testSuspendingBufferedYield_whenWriterFinished() async throws {
610-
self.sink.setWritability(to: false)
611-
612-
let bothSuspended = expectation(description: "suspended on both yields")
613-
let suspendedAgain = ConditionLock(value: false)
614-
self.delegate.didSuspendHandler = {
615-
if self.delegate.didSuspendCallCount == 2 {
616-
bothSuspended.fulfill()
617-
} else if self.delegate.didSuspendCallCount > 2 {
618-
suspendedAgain.lock()
619-
suspendedAgain.unlock(withValue: true)
611+
#if compiler(>=6)
612+
try await withNIOThreadPoolTaskExecutor(numberOfThreads: 2) { taskExecutor in
613+
self.sink.setWritability(to: false)
614+
615+
let bothSuspended = expectation(description: "suspended on both yields")
616+
let suspendedAgain = ConditionLock(value: false)
617+
self.delegate.didSuspendHandler = {
618+
if self.delegate.didSuspendCallCount == 2 {
619+
bothSuspended.fulfill()
620+
} else if self.delegate.didSuspendCallCount > 2 {
621+
suspendedAgain.lock()
622+
suspendedAgain.unlock(withValue: true)
623+
}
620624
}
621-
}
622625

623-
self.delegate.didYieldHandler = { _ in
624-
if self.delegate.didYieldCallCount == 1 {
625-
// Delay this yield until the other yield is suspended again.
626-
if suspendedAgain.lock(whenValue: true, timeoutSeconds: 5) {
627-
suspendedAgain.unlock()
628-
} else {
629-
XCTFail("Timeout while waiting for other yield to suspend again.")
626+
self.delegate.didYieldHandler = { _ in
627+
if self.delegate.didYieldCallCount == 1 {
628+
// Delay this yield until the other yield is suspended again.
629+
if suspendedAgain.lock(whenValue: true, timeoutSeconds: 5) {
630+
suspendedAgain.unlock()
631+
} else {
632+
XCTFail("Timeout while waiting for other yield to suspend again.")
633+
}
630634
}
631635
}
632-
}
633636

634-
let task1 = Task { [writer] in
635-
try await writer!.yield("message1")
636-
}
637-
let task2 = Task { [writer] in
638-
try await writer!.yield("message2")
639-
}
637+
let task1 = Task(executorPreference: taskExecutor) { [writer] in
638+
try await writer!.yield("message1")
639+
}
640+
let task2 = Task(executorPreference: taskExecutor) { [writer] in
641+
try await writer!.yield("message2")
642+
}
640643

641-
await fulfillment(of: [bothSuspended], timeout: 5)
642-
self.writer.finish()
644+
await fulfillment(of: [bothSuspended], timeout: 5)
645+
self.writer.finish()
643646

644-
self.assert(suspendCallCount: 2, yieldCallCount: 0, terminateCallCount: 0)
647+
self.assert(suspendCallCount: 2, yieldCallCount: 0, terminateCallCount: 0)
645648

646-
// We have to become writable again to unbuffer the yields
647-
// The first call to didYield will pause, so that the other yield will be suspended again.
648-
self.sink.setWritability(to: true)
649+
// We have to become writable again to unbuffer the yields
650+
// The first call to didYield will pause, so that the other yield will be suspended again.
651+
self.sink.setWritability(to: true)
649652

650-
await XCTAssertNoThrow(try await task1.value)
651-
await XCTAssertNoThrow(try await task2.value)
653+
await XCTAssertNoThrow(try await task1.value)
654+
await XCTAssertNoThrow(try await task2.value)
652655

653-
self.assert(suspendCallCount: 3, yieldCallCount: 2, terminateCallCount: 1)
656+
self.assert(suspendCallCount: 3, yieldCallCount: 2, terminateCallCount: 1)
657+
}
658+
#endif // compiler(>=6)
654659
}
655660

656661
func testWriterFinish_whenFinished() {

0 commit comments

Comments
 (0)