[Twisted-Python] cReactor fixes

Hi all.. For some reason not yet entirely clear to me, I started to put some time into bringing cReactor up to date. My hope was to make it functional enough to pass the full test suite. At the moment, it fails 120 of the 413 tests in the suite (that doesn't include test_threads, which I had to disable to prevent the test suite from hanging forever). In tracking down the reasons for these failures, I've come across a number of places where the reactor interface (as defined by IReactorCore and friends in twisted.internet.interfaces) was ambiguous about something, and the default reactor has implemented different functionality than the cReactor. Several tests have come to rely upon default.py's implementation, and fail when run under cReactor even though it appears to implement the same interface. To resolve these, we need to nail down the ambiguities in the Interface, so that all reactors can implement the same thing. The two issues I've encountered so far: IReactorCore.addSystemEventTrigger() [1 test failure] The interface mentions three (startup, shutdown, persist) as examples of "system events", and says they will be fired internally by the Reactor. It also says: An implementor of this interface must only implement those events described here. The cReactor implements this literally, and refuses to accept calls to addSystemEventTrigger or fireSystemEvent with event names outside of ['startup', 'shutdown', 'persist']. The default reactor (and the ones that inherit system event code from it, which is all of them except cReactor) use a dict for the events, and therefore accept any string as a system event type. test_internet.InterfaceTestCase.testTriggerSystemEvent creates several new event types ("test", "defer", "remove") and runs tests with them. This test thus fails under cReactor. RESOLUTION: I think we decided that cReactor should accept arbitrary event names, and I'm working on code to implement that. That restrictive sentence should be removed from the docs. I'd also like to see a better explanation of what event types will be fired by whom: if the API allows an arbitrary string, but the Reactor will only fire "startup" and "shutdown" for you, then you have to know that it is your own responsibility to fire the new triggers that you've added. IReactorCore.stop() [38 test failures] The interface description for .stop and .crash make the claim that once the Reactor is shut down with .stop(), it may not be restarted with .run(). Reactors which have been stopped with .crash() *may* be restarted. cReactor implements this literally. Once reactor.stop() has been called, the reactor is moved into a state from which .run() and .iterate() raise a RuntimeError "the reactor is shut down!" exception. The default reactor does not enforce this: .run and .stop may be called as many times as you like. All tests in a given invocation of bin/trial or admin/runtests are executed with the same Reactor instance. Indeed everything done by a single python process is performed with the same Reactor instance. Normal apps don't care: they start the reactor in main() or under twistd, and it runs until the app shuts down. The tests, however, start and stop the reactor all the time, generally once per test case. Under cReactor, the first test to do reactor.stop() will terminate the reactor permanently, causing failures of all later tests that attempt reactor.run() or reactor.iterate(). This also tends to kill tests which re-bind to the same port multiple times, as the reactor.iterate() needed to release the port fails, leaving the port bound, causing a "Address already in use" error on the second test. POTENTIAL RESOLUTION: We need to decide whether the default reactor should obey the restriction defined by IReactorCore, or whether the interface (and cReactor) should be changed to match the behavior of the default reactor. Another possibility which might be easier to implement would be to enforce the no-restart restriction, but also create IReactorCore.reset(), and declare that it will return the reactor to the state it had at boot time, allowing .run() to be called on it once again. This method would also remove all DelayedCall events, remove any TCP/UDP sockets or listening ports, delete any systemEventTriggers that were added, and generally get rid of any other state which might have accumulated since startup. We could have the test suite use this to reset the reactor just after each test. The other test failures are the result of functionality that is missing from cReactor. In general, cReactor has not been maintained as new features were added to the other reactors. Here is a summary of all the failures. (note that some tests fail for multiple reasons). IReactorTCP.connectTCP [8 failures] client support (outbound sockets) is missing IListeningPort.getHost [22 failures] simple missing function, mostly used to determine the port number of a system-selected listening port IReactorProcess [9 failures] launching child processes is unimplemented rebuild attempts: missing __dict__ [5 failures] twisted.python.rebuild attempts to rebuild a C module with no .__dict__ IReactorMulticast [5 failures] multicast support is not implemented IReactorUDP [23 failures] UDP support is unimplemented IReactorCore.addSystemEventTrigger() [1 test failure] IReactorCore.stop() [38 test failures] Some of these aren't hard to fix: IListeningPort.getHost should only take a few lines, connectTCP is pretty straightforward, and IReactorUDP should be a fairly easy cut-and-paste job from the TCP code. But others are tricky: IReactorProcess is fairly high-level, and IReactorMulticast might be relying upon some portability code in the python socket module that wouldn't be available at the C layer. I think it is now pretty clear what work needs to be done. Everyone is welcome to take a crack at it :). I will probably finish the getDelayedCall and addSystemEventTrigger changes I'm working on, commit them, and then let others pursue the pieces that are useful to them. (the default reactor is currently good enough for my needs; I only put this much time into cReactor because I wanted to understand the test failures well enough to know that my changes weren't the cause). cheers, -Brian

On Sun, Jan 05, 2003 at 02:34:10PM -0800, Brian Warner wrote:
Another possibility which might be easier to implement would be to enforce the no-restart restriction, but also create IReactorCore.reset(), and declare that it will return the reactor to the state it had at boot time, allowing .run() to be called on it once again. This method would also remove all DelayedCall events, remove any TCP/UDP sockets or listening ports, delete any systemEventTriggers that were added, and generally get rid of any other state which might have accumulated since startup. We could have the test suite use this to reset the reactor just after each test.
+1 -Andrew.

On Sun, Jan 05, 2003 at 02:34:10PM -0800, Brian Warner wrote:
Another possibility which might be easier to implement would be to enforce the no-restart restriction, but also create IReactorCore.reset(), and declare that it will return the reactor to the state it had at boot time, allowing .run() to be called on it once again. This method would also remove all DelayedCall events, remove any TCP/UDP sockets or listening ports, delete any systemEventTriggers that were added, and generally get rid of any other state which might have accumulated since startup. We could have the test suite use this to reset the reactor just after each test.
+infinity This is definitely the way it should work. Thanks, Brian, for taking the time to analyze the existing cReactor code in so much depth. I really owe you one. I will need more time to fully read and comprehend the rest of the message, but this part has been on my personal TODO list for a while. Ideally, I'd prefer that the reactor is *always* in a consistent state. That would probably involve redefining or removing the "crash" facility, since there would only be "pause and return so that run() continues" or "stop properly so run() restarts". Whatever compromise you come up with in this vein is probably acceptable, though. -- | <`'> | Glyph Lefkowitz: Traveling Sorcerer | | < _/ > | Lead Developer, the Twisted project | | < ___/ > | http://www.twistedmatrix.com |

Glyph Lefkowitz <glyph@twistedmatrix.com> writes:
Ideally, I'd prefer that the reactor is *always* in a consistent state. That would probably involve redefining or removing the "crash" facility, since there would only be "pause and return so that run() continues" or "stop properly so run() restarts". Whatever compromise you come up with in this vein is probably acceptable, though.
What do you mean by "consistent"? Let me verify my understanding: we could say that the two states are running and stopped, and that you can always transition from one to the other. I'm guessing the reason for .crash and the prohibition on running .start a second time is the systemEventTriggers, and a desire to keep them from running more than once in any given process. If we move to only two states and allow arbitrary transitions between them, we'd probably have to declare that the triggers run on each transition. Normal apps won't care, since they only start and stop once. The unit tests would start/stop 0 or more times per test case, running triggers each time, but the only triggers in place would probably be the ones added by the tests themselves. The other use for .crash listed in the docs is to avoid running shutdown triggers. I've had programs where the shutdown.tap -writing routine raised an exception, which appeared to cause the shutdown to abort and the program to go back to the main loop. I had to kill it with SIGTERM to make it stop. I imagine .crash could be used in an exception handler that wrapped app.save() to terminate a program that was having trouble with the "after"-"shutdown" event triggers. If that is useful, .crash would be just like .stop except it wouldn't run the triggers. So: 1: At boot time, we enter the "stopped" state 2: While we are in the "stopped" state, you may only run .start 3: .start moves us from "stopped" to "running" and runs "startup" triggers 4: While we are in the "running" state, you may only run .stop or .crash 5: .stop moves from "running" to "stopped" and runs "shutdown" triggers 6: .crash moves from "running" to "stopped" without running triggers 7: .reset does an implicit .stop, if necessary, before cleaning all reactor state All triggers remain attached until individually removed or reactor.reset() is called, so an app that does .run(); .stop(); .run(); .stop(); is going to have the triggers run multiple times. All other reactor state is preserved by .stop and .crash (timers, sockets). I think I've just restated what you said before, but this way if I got it wrong somebody has the opportunity to correct my understanding before I get around to writing any code :) thanks, -Brian

On Sun, Jan 05, 2003 at 02:34:10PM -0800, Brian Warner wrote:
Hi all..
Excellent write up of the problems, thanks :)
[snip]
rebuild attempts: missing __dict__ [5 failures] twisted.python.rebuild attempts to rebuild a C module with no .__dict__
Checked in a fixed this group tonight. Jp -- Lowery's Law: If it jams -- force it. If it breaks, it needed replacing anyway. -- 12:00am up 20 days, 9:46, 2 users, load average: 0.10, 0.14, 0.10

On Sun, 05 Jan 2003 14:34:10 -0800 (PST) Brian Warner <warner@lothar.com> wrote:
IReactorProcess [9 failures] launching child processes is unimplemented
IReactorMulticast [5 failures] multicast support is not implemented
IReactorUDP [23 failures] UDP support is unimplemented
I'll fix the test suite to only run these tests if the appropriate interface is implemented. There's no reason cReactor has to support UDP or multicast, though it'd be nice if it implemented IFDSelector or whatever it's called so that it could use the python UDP, SSL, process and multicast code. -- Itamar Shtull-Trauring http://itamarst.org/ Available for Python, Twisted, Zope and Java consulting ***> http://VoteNoWar.org -- vote/donate/volunteer <***

I'll fix the test suite to only run these tests if the appropriate interface is implemented. There's no reason cReactor has to support UDP or multicast, though it'd be nice if it implemented IFDSelector or whatever it's called so that it could use the python UDP, SSL, process and multicast code.
You might want to provide an easy way to turn those tests back on (bypassing the "is-it-implemented" test). Unit tests are a good way to point out functionality that hasn't been implemented yet. If the test suite appears to pass cleanly, someone might be fooled into thinking that cReactor works just as well as the others. Of course, there might be other/better ways to signal that this reactor behaves differently than all the others, and that this is a design decision and not just as-yet-unimplemented functionality. (__implements__ comes to mind, but I don't know how visible that would be to a user who's left wondering why their program fails when run under cReactor). Is there anyone here who is using cReactor? If so, what is important to you about it? Is UDP a burning desire? thanks, -Brian

On Mon, 06 Jan 2003 11:33:32 -0800 (PST) Brian Warner <warner@lothar.com> wrote:
You might want to provide an easy way to turn those tests back on (bypassing the "is-it-implemented" test). Unit tests are a good way to point out functionality that hasn't been implemented yet. If the test suite appears to pass cleanly, someone might be fooled into thinking that cReactor works just as well as the others.
I don't see it as being that important, we just need a good README for cReactor saying what it supports.
Of course, there might be other/better ways to signal that this reactor behaves differently than all the others, and that this is a design decision and not just as-yet-unimplemented functionality. (__implements__ comes to mind, but I don't know how visible that would be to a user who's left wondering why their program fails when run under cReactor).
__implements__ is how I'm going to check if a reactor implements something. As I said, if cReactor has addReader/addWriter interface twisted.internet.interfaces.IReactorFDSet, we get ssl, udp, multicast, serial and process support almost for free using the python code for these, until a C version is written. -- Itamar Shtull-Trauring http://itamarst.org/ Available for Python, Twisted, Zope and Java consulting ***> http://VoteNoWar.org -- vote/donate/volunteer <***

Itamar Shtull-Trauring <twisted@itamarst.org> writes:
As I said, if cReactor has addReader/addWriter interface twisted.internet.interfaces.IReactorFDSet, we get ssl, udp, multicast, serial and process support almost for free using the python code for these, until a C version is written.
Good point. And, the performance difference between having the fileno originate from python code vs. having it originate from C code won't be an issue until you're creating lots of sockets, so as long as the functionality is present, moving it to C can wait until someone has a need for it. I'll look into gluing IReactorFDSet into cReactor. -Brian
participants (5)
-
Andrew Bennetts
-
Brian Warner
-
Glyph Lefkowitz
-
Itamar Shtull-Trauring
-
Jp Calderone