Thank you for detailed feedback!
• Glyph [2025-01-26 23:21]:
On Jan 22, 2025, at 1:19 PM, k...@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-le...@python.org
https://mail.python.org/mailman3/lists/twisted.python.org/
Message archived at
https://mail.python.org/archives/list/twisted@python.org/message/CSD5IXMJDDAZVA35IBBQHH7VY72SRQUR/
Code of Conduct: https://twisted.org/conduct
_______________________________________________
Twisted mailing list -- twisted@python.org
To unsubscribe send an email to twisted-le...@python.org
https://mail.python.org/mailman3/lists/twisted.python.org/
Message archived at
https://mail.python.org/archives/list/twisted@python.org/message/CWY6DAQFLXBVB6PCS2MSR3XWSVOB6WSW/
Code of Conduct: https://twisted.org/conduct