|
| 1 | +commit f656af6b796a9ee5e103c38cafd2346b95fdc759 |
| 2 | +Author: Johannes Weiss < [email protected]> |
| 3 | +Date: Thu Aug 28 13:08:45 2025 +0100 |
| 4 | + |
| 5 | + SelectableEventLoop.debugDescription: fix debugDescription deadlock (#3360) |
| 6 | + |
| 7 | + ### Motivation: |
| 8 | + |
| 9 | + In #3297, I introduced a deadlock involving `SelectableEventLoop`'s |
| 10 | + `debugDescription`. I was under the impression that it's not possible to |
| 11 | + call this from outside the `NIO` module so I deemed it safe. That was a |
| 12 | + mistake :). |
| 13 | + |
| 14 | + ### Modifications: |
| 15 | + |
| 16 | + - Make it impossible to deadlock around |
| 17 | + `SelectableEventLoop.debugDescription`. |
| 18 | + |
| 19 | + ### Result: |
| 20 | + |
| 21 | + - Fewer deadlocks |
| 22 | + |
| 23 | +diff --git a/Tests/NIOPosixTests/EventLoopTest.swift b/Tests/NIOPosixTests/EventLoopTest.swift |
| 24 | +index 65bbc653..93a5a700 100644 |
| 25 | +--- a/Tests/NIOPosixTests/EventLoopTest.swift |
| 26 | ++++ b/Tests/NIOPosixTests/EventLoopTest.swift |
| 27 | +@@ -2070,6 +2070,40 @@ final class EventLoopTest: XCTestCase { |
| 28 | + XCTAssertEqual("cool", actual) |
| 29 | + } |
| 30 | + #endif |
| 31 | ++ |
| 32 | ++ func testRegressionSelectableEventLoopDeadlock() throws { |
| 33 | ++ let iterations = 1_000 |
| 34 | ++ let loop = MultiThreadedEventLoopGroup.singleton.next() as! SelectableEventLoop |
| 35 | ++ let threadsReadySem = DispatchSemaphore(value: 0) |
| 36 | ++ let go = DispatchSemaphore(value: 0) |
| 37 | ++ |
| 38 | ++ let scheduleds = NIOThreadPool.singleton.runIfActive(eventLoop: loop) { |
| 39 | ++ threadsReadySem.signal() |
| 40 | ++ go.wait() |
| 41 | ++ var tasks: [Scheduled<()>] = [] |
| 42 | ++ for _ in 0..<iterations { |
| 43 | ++ tasks.append(loop.scheduleTask(in: .milliseconds(1)) {}) |
| 44 | ++ } |
| 45 | ++ return tasks |
| 46 | ++ } |
| 47 | ++ |
| 48 | ++ let descriptions = NIOThreadPool.singleton.runIfActive(eventLoop: loop) { |
| 49 | ++ threadsReadySem.signal() |
| 50 | ++ go.wait() |
| 51 | ++ var descriptions: [String] = [] |
| 52 | ++ for _ in 0..<iterations { |
| 53 | ++ descriptions.append(loop.debugDescription) |
| 54 | ++ } |
| 55 | ++ return descriptions |
| 56 | ++ } |
| 57 | ++ |
| 58 | ++ threadsReadySem.wait() |
| 59 | ++ threadsReadySem.wait() |
| 60 | ++ go.signal() |
| 61 | ++ go.signal() |
| 62 | ++ XCTAssertEqual(iterations, try scheduleds.wait().map { $0.cancel() }.count) |
| 63 | ++ XCTAssertEqual(iterations, try descriptions.wait().count) |
| 64 | ++ } |
| 65 | + } |
| 66 | + |
| 67 | + private final class EventLoopWithPreSucceededFuture: EventLoop { |
0 commit comments