![](https://secure.gravatar.com/avatar/9e1312e82732ddc84c53cabd2ef975b4.jpg?s=120&d=mm&r=g)
Thank you for detailed feedback! • Glyph [2025-01-26 23:21]:
On Jan 22, 2025, at 1:19 PM, km@krot.org wrote:
• Glyph [2025-01-22 18:25]:
In general, if you have to call reactor.stop() manually like this in a script, you're probably doing too much manual fiddling with its lifecycle. I have not fully debugged this example, but I suspect it's just because the error is synchronous before the reactor starts.
The provided scripts are just minimal examples which illustrate the problem I am having in a bigger piece of a daemon, which sets up a number of listeners on multiple interfaces and ports: if it is not able to bind to any of those, reactor should stop. It it does not stop by calling reactor.stop(), only by wrapping it in callWhenRunning or callLater. At first I just thought that it's just the way it is, but after I started the question WHY, it bothers me a little bit.
Are these listeners set up later than startup time, though? If you do something like:
async def main(): try: await myEndpoint.listen(...) except CannotListenError: print("can't listen, goodbye") return serveForever = Deferred() await serveForever
you will achieve that early-exit you want.
However, rather than carefully managing the reactor's startup state like this where half your code runs synchronously before reactor startup,
There is no synchronous code in the examples though, apart from setting up some things which would be called once reactor is running.
I guess "synchronous" is a slippery word here, "eager" might be a better one, but the salient difference is that TCP4ServerEndpoint.listen is going to call socket.socket.listen() immediately when you invoke it, and then it is going to raise an error and fire its Deferred immediately, which will result in running those callbacks early.
So I understand that although I write code in a non-synchronous way, some parts trigger the code running immediately.
E.g. the first two defs set up callback and erroback functions -- isn't reactor.running supposed to be True when callbacks/errorbacks attached to a deferred are called?
No, the reactor has nothing to do with Deferred. Now that we have managed to deprecate and remove the ill-conceived "Deferred.setTimeout" there is /no/ interaction between the two.
Consider this example, with no reactor anywhere:
> from twisted.internet.defer import Deferred > d = Deferred() > async def awaiter(): ... print("hello") ... value = await d ... print(f"goodbye: {value}") ... ... > d2 = Deferred.fromCoroutine(awaiter()) hello > 3 + 4 7 > d.callback(_) goodbye: 7
reactor.running means something /extremely/ subtle about the run-state of the reactor, which is only loosely related to whether you will get a ReactorNotRunning error calling stop() or not; the reactor continues running for a while /after/ stop is called, during which period .running is True, but .stop() will raise an exception. I realize the naming of the exception here is not the best.
so ReactorNotRunning can be raised both when reactor is not yet running, but also when it is running, but stop has been called?
The rule is "don't call reactor.stop() more than once", not "don't call reactor.stop() if reactor.running is True".
as an experiment, here's example which uses react():
#!/usr/local/bin/python3
from twisted.internet import reactor, defer, protocol from twisted.internet.protocol import Factory from twisted.internet.endpoints import TCP4ServerEndpoint from twisted.internet.task import react
def _port(port): print('got', port)
def _error(err): print('got err', err) print('is reactor running in error?', reactor.running) print('is reactor running in lambda?', (lambda: reactor.running)()) reactor.stop() # <- this fails # reactor.callWhenRunning(reactor.stop) # <- this works
async def main(reactor): print('running in main, huh?', reactor.running) d = TCP4ServerEndpoint(reactor, 123).listen(Factory()) d.addCallback(_port) d.addErrback(_error) d.addErrback(print)
What is happening here is that you get called, the reactor starts, then you immediately synchronously return without awaiting anything, shutting down the reactor. If you're using react(), it's react()'s job to call reactor.stop; all you can safely do is fire the Deferred (or complete the coroutine) returned from main().
react(main)
it outputs this:
running in main, huh? False got err [Failure instance: Traceback: <class 'twisted.internet.error.CannotListenError'>: Couldn't listen on any:123: [Errno 13] Permission denied. /usr/local/lib/python3.12/site-packages/twisted/internet/ defer.py:155:execute /usr/local/lib/python3.12/site-packages/twisted/internet/ posixbase.py:366:listenTCP /usr/local/lib/python3.12/site-packages/twisted/internet/ tcp.py:1360:startListening ] is reactor running in error? False is reactor running in lambda? False [Failure instance: Traceback: <class 'twisted.internet.error.ReactorNotRunning'>: Can't stop reactor that isn't running. /usr/local/lib/python3.12/site-packages/twisted/internet/ defer.py:1088:_runCallbacks /home/km/./foo:15:_error /usr/local/lib/python3.12/site-packages/twisted/internet/base.py:790:stop ]
Yep, this code stops the reactor twice: once in the code that concludes react() and once in _error. If you manage to fully exit the reactor before that second call to reactor.stop() happens (which is what I think your callWhenRunning does), then you won't see an exception. But that is an accident of scheduling, so I would not rely on it for correctness here.
-g
_______________________________________________ Twisted mailing list -- twisted@python.org To unsubscribe send an email to twisted-leave@python.org https://mail.python.org/mailman3/lists/twisted.python.org/ Message archived at https://mail.python.org/archives/list/twisted@python.org/message/CSD5IXMJDDA... Code of Conduct: https://twisted.org/conduct