Repository: twisted/twisted

Issue:
Sometimes cfreactor fails twisted.internet.test.test_fdset.ReactorFDSetTestsBuilder_CFReactorTests.test_addReader with 120 second TimeoutError
```
===============================================================================
Error: 
Traceback (most recent call last):
  File "/Users/runner/work/twisted/twisted/.tox/macos-withcov-alldeps/lib/python3.11/site-packages/twisted/internet/test/test_fdset.py", line 91, in test_addReader
    self.runReactor(reactor)
  File "/Users/runner/work/twisted/twisted/.tox/macos-withcov-alldeps/lib/python3.11/site-packages/twisted/internet/test/reactormixins.py", line 337, in runReactor
    raise TestTimeoutError(f"reactor still running after {timeout} seconds")
twisted.internet.test.reactormixins.TestTimeoutError: reactor still running after 120.0 seconds

twisted.internet.test.test_fdset.ReactorFDSetTestsBuilder_CFReactorTests.test_addReader
===============================================================================
Error: 
Traceback (most recent call last):
  File "/Users/runner/work/twisted/twisted/.tox/macos-withcov-alldeps/lib/python3.11/site-packages/twisted/internet/base.py", line 1049, in runUntilCurrent
    call.func(*call.args, **call.kw)
  File "/Users/runner/work/twisted/twisted/.tox/macos-withcov-alldeps/lib/python3.11/site-packages/twisted/internet/test/reactormixins.py", line 332, in stop
    reactor.stop()
  File "/Users/runner/work/twisted/twisted/.tox/macos-withcov-alldeps/lib/python3.11/site-packages/twisted/internet/cfreactor.py", line 538, in stop
    PosixReactorBase.stop(self)
  File "/Users/runner/work/twisted/twisted/.tox/macos-withcov-alldeps/lib/python3.11/site-packages/twisted/internet/base.py", line 757, in stop
    raise error.ReactorNotRunning("Can't stop reactor that isn't running.")
twisted.internet.error.ReactorNotRunning: Can't stop reactor that isn't running.

twisted.internet.test.test_fdset.ReactorFDSetTestsBuilder_CFReactorTests.test_addReader
-------------------------------------------------------------------------------
```



Discussion:
Also twisted.internet.test.test_tcp.AbortConnectionTests_CFReactorTests.test_fullWriteBufferAfterByteExchange

I believe #12029 is a duplicate of this?
Sometimes it's `twisted.internet.test.test_process.ProcessTestsBuilder_CFReactorTests.test_pauseAndResumeProducing` too. Not sure if this is the same bug.

```
===============================================================================
Error: 
Traceback (most recent call last):
  File "/Users/runner/work/twisted/twisted/.tox/macos-withcov-alldeps/lib/python3.8/site-packages/twisted/internet/test/test_process.py", line 888, in test_pauseAndResumeProducing
    self.runReactor(reactor)
  File "/Users/runner/work/twisted/twisted/.tox/macos-withcov-alldeps/lib/python3.8/site-packages/twisted/internet/test/reactormixins.py", line 351, in runReactor
    raise TestTimeoutError(f"reactor still running after {timeout} seconds")
twisted.internet.test.reactormixins.TestTimeoutError: reactor still running after 120.0 seconds

twisted.internet.test.test_process.ProcessTestsBuilder_CFReactorTests.test_pauseAndResumeProducing
===============================================================================
Error: 
Traceback (most recent call last):
  File "/Users/runner/work/twisted/twisted/.tox/macos-withcov-alldeps/lib/python3.8/site-packages/twisted/internet/base.py", line 1090, in runUntilCurrent
    call.func(*call.args, **call.kw)
  File "/Users/runner/work/twisted/twisted/.tox/macos-withcov-alldeps/lib/python3.8/site-packages/twisted/internet/test/reactormixins.py", line 346, in stop
    reactor.stop()
  File "/Users/runner/work/twisted/twisted/.tox/macos-withcov-alldeps/lib/python3.8/site-packages/twisted/internet/cfreactor.py", line 536, in stop
    PosixReactorBase.stop(self)
  File "/Users/runner/work/twisted/twisted/.tox/macos-withcov-alldeps/lib/python3.8/site-packages/twisted/internet/base.py", line 794, in stop
    raise error.ReactorNotRunning("Can't stop reactor that isn't running.")
twisted.internet.error.ReactorNotRunning: Can't stop reactor that isn't running.

twisted.internet.test.test_process.ProcessTestsBuilder_CFReactorTests.test_pauseAndResumeProducing
-------------------------------------------------------------------------------
Ran 10791 tests in 176.943s
```

https://github.com/twisted/twisted/actions/runs/7521167302/job/20471684152?pr=12083

Resolving patch:
diff --git a/src/twisted/internet/cfreactor.py b/src/twisted/internet/cfreactor.py
--- a/src/twisted/internet/cfreactor.py
+++ b/src/twisted/internet/cfreactor.py
@@ -85,7 +85,7 @@ def doRead(self):
         next timed iteration.
         """
         result = super().doRead()
-        self.reactor._scheduleSimulate(True)
+        self.reactor._scheduleSimulate()
         return result
 
 
@@ -386,7 +386,7 @@ def startRunning(self, installSignalHandlers: bool = True) -> None:
         # it's possible to use startRunning to *attach* a reactor to an
         # already-running CFRunLoop, i.e. within a plugin for an application
         # that doesn't otherwise use Twisted, rather than calling it via run().
-        self._scheduleSimulate(force=True)
+        self._scheduleSimulate()
 
         # [1]: readers & writers are still active in the loop, but arguably
         #      they should not be.
@@ -484,19 +484,10 @@ def _stopSimulating(self) -> None:
         CFRunLoopTimerInvalidate(self._currentSimulator)
         self._currentSimulator = None
 
-    def _scheduleSimulate(self, force: bool = False) -> None:
+    def _scheduleSimulate(self) -> None:
         """
         Schedule a call to C{self.runUntilCurrent}.  This will cancel the
         currently scheduled call if it is already scheduled.
-
-        @param force: Even if there are no timed calls, make sure that
-            C{runUntilCurrent} runs immediately (in a 0-seconds-from-now
-            C{CFRunLoopTimer}).  This is necessary for calls which need to
-            trigger behavior of C{runUntilCurrent} other than running timed
-            calls, such as draining the thread call queue or calling C{crash()}
-            when the appropriate flags are set.
-
-        @type force: C{bool}
         """
         self._stopSimulating()
         if not self._started:
@@ -505,7 +496,10 @@ def _scheduleSimulate(self, force: bool = False) -> None:
             # CFRunLoopTimers against the global CFRunLoop.
             return
 
-        timeout = 0.0 if force else self.timeout()
+        # runUntilCurrent acts on 3 things: _justStopped to process the
+        # side-effect of reactor.stop(), threadCallQueue to handle any calls
+        # from threads, and _pendingTimedCalls.
+        timeout = 0.0 if (self._justStopped or self.threadCallQueue) else self.timeout()
         if timeout is None:
             return
 
@@ -529,12 +523,12 @@ def callLater(self, _seconds, _f, *args, **kw):
         self._scheduleSimulate()
         return delayedCall
 
-    def stop(self):
+    def stop(self) -> None:
         """
         Implement L{IReactorCore.stop}.
         """
         PosixReactorBase.stop(self)
-        self._scheduleSimulate(True)
+        self._scheduleSimulate()
 
     def crash(self):
         """


Test patch: