[Twisted-Python] Deferred in C++

Jamu Kakar jkakar at kakar.ca
Wed Oct 22 05:05:43 EDT 2008


Hi Amaury,

On Tue, Oct 21, 2008 at 5:14 PM, Amaury Forgeot d'Arc
<amauryfa at gmail.com> wrote:
> So I am not the only one who tries to use Twisted concepts in C++.

Heh, it's lonely being a C++ nerd, isn't it. ;) I'm mostly
interested in seeing what's possible and trying to understand how to
port very interesting APIs from Python to C++.

> I did write some kind of a Deferred class in C++ myself, together with
> a re-implementation of a Twisted framework (two reactors featuring TCP
> client & server, callLater, callInThread...; a selectReactor and a
> native MFC reactor that merges nicely in a Windows application, yes,
> there are some; Protocols, LineReceiver, HttpClient & server...)
> and I already have a running business application that uses all of it
> on Windows.
>
> The Deferred part was really hard to implement, and may be the reason
> why it was never done before. It really pushed me out of my C++
> knowledge.
> I tried to convert my code to use your class, which seems easier to
> use than mine. Great job!

The most difficult part for me was dealing with Deferred-returning
callbacks and getting the pausing/unpausing behaviour to work
correctly.  The difficult part was that all the template functions
caused order of definition issues.  The DeferredTypeInfo was
introduced for this case to make Deferred a template parameter and,
therefore, work around the ordering issues.  It's really heinously
complicated inside and I have to decipher it each time I look at it.
It's always a bad sign when you're the author of the code you can't
comprehend. :)

> - First, good news, your code almost compiles on Windows (VS 2005). I
> had to typedef int32_t (why don't you simply use int?), comment out
> the StackFrameFactory, and declare some functions/classes with
> __declspec(dllexport) (with the help of a macro) so we can build a
> DLL.

Cool, glad to hear it.  I didn't expect problems, but just haven't
had time to port it (and also have VS2005 as my base target compiler
for Windows).  I plan to s/int32_t/int/, but haven't gotten around
to it.  It's a historic artifact in that code from many years ago
that I've continued for the time being to keep things consistent.

> - In my eyes, the notation "addCallback" is more readable than
> "add_callback" (and it's the one used by twisted python), but that's a
> matter of taste. On the other hand I found disturbing at first to fire
> deferreds with the methods "succeed()" and "fail()" (instead of
> callback() and errback()

The style issue is entirely personal, so yeah, I can see why you
feel that way.  As far as succeed() and fail() vs. callback() and
errback(), this change was a suggestion from Christopher Armstrong.
Apparently, a lot of Twisted users have been confused by callback
(the callable you add to a Deferred) vs callback (the method you
call to fire the Deferred), and similar for errback.

> - I found that my callbacks often have a void result, and deferred are
> sometimes fired without a value. For example, a twisted DeferredList
> often just waits for its children to finish. The results may be
> gathered somewhere, but the callbacks return None.
> I took the liberty to change your code and add *a lot* of template
> specializations for the "void" ReturnType or ResultType.
> I suggest that when a callback takes no parameter, it can be added to
> any Deferred, whatever its current state (and the current value is
> simply discarded).

This is on my list of things to add, but haven't gotten around to it
yet.  My plan is to throw a NoResultError or something whenever a
callback is added to a Deferred that already has void-returning
callback at the end of the chain.  Though, hmm... I guess you might
still want to add an errback to handle errors from the
void-returning callback.

> - In some cases my callbacks take additional arguments (the twisted
> "callbackArgs"). To make it possible I used the
> boost::bind functions, and a wrapper class that converts any
> boost::function to a ion::ICallback.

Ah, nice.  Yeah, I was wondering about that and have been
considering implementing a specialized ICallback that can store
arguments.

> - Several times I had a "Can't add invalid callback" error, and I
> found it difficult to figure which type was in the deferred state (the
> previous callback returned a Deferred, and so on...) So I extended
> ITypeInfo with a "const char* typename" member, that I fill with
> "typeid(Object).name()". Much easier to see with the debugger...

Good call.  Yeah, I've had the same issue interpreting
InvalidCallbackError's.

> - I tried to implement a DeferredList. I almost succeeded, except that
> it simply inherit from Deferred and it seems a bad idea: when the
> DeferredList is returned as a plain Deferred, all state is lost... and
> the application crashes.
> Maybe Deferred should be a kind of "shared pointer" to the real
> object: DeferredState or DeferredListState

Deferred is a "shared pointer" to DeferredState...?  Maybe I'm not
understanding your point?

> - Errr... I'm not sure I understood the "trule" concept. When do you
> use it? What are its semantics regarding ownership? is it similar to
> std::auto_ptr?

This is an implementation of the Holder/Trule pattern described in
Josuttis' "C++ Templates" book.  There is a reason, which I can't
recall right now, that makes returning a ptr (or auto_ptr) from a
function potentially dangerous (and also passing one in) that trule
fixes.

My plan is to migrate to the Boost pointers that will become part of
the standard library in C++0x.  This will make working with smart
pointers in ion much nicer.  The ptr/trule pattern is quite
cumbersome, especially when templates are involved.

> - How is one supposed to get the content of a Failure? I use code like
> this, but it seems too long for this simple task:
>  void MyProcess::displayError(ion::trule<ion::Failure> failure)
>  {
>    std::string message = ion::ptr<ion::Failure>(failure)->get_message();
>    [...]
>    throw ion::UnhandledFailureError(failure);
>  }
>
> - likewise, it should be possible to simply
>    throw Failure("Some error message");

The displayError above is what you're expected to do, so far.  If
there's a better way I'd love to hear it.  There's actually a deeper
issue that makes Failure/error handling in this implementation kind
of crappy.  The type of an exception is lost when caught in a catch
block for a base class of the thrown exception.  Which means that
it's not possible, currently, to determine the concrete exception
type from a Failure.  One thing I've been considering is making the
ion::Exception class provide some extra functionality to make this
possible, but I'm unhappy that doing that will make implementing
custom ion::Exception's harder.

Being able to throw a Failure with a string message is a good idea.

>> The reliance on templates would negatively affect
>> compile time, for one thing.  More importantly, I've found
>> deciphering the error messages produced by simple mistakes can be
>> tricky.
>
> I confirm: it is.

Heh. :)

>> Nonetheless, my main motivation was to see if it was
>> possible at all and so I'm pleased that it works in the end.
>
> I think my program can be considered as a non-trivial application (I
> cannot show its code, though, except for the non-business framework)
> It is definitely possible to implement a Twisted framework in C++. The
> hard part is to make it nice for the developer. Programming with
> callbacks already twists your mind, it becomes very difficult if the
> language syntax hides the important part of the code.

This "make it nice for the developer" is the primary goal I have
with this effort.

> The "ion" project seems promising! What extend do you intend to give it?

I'm interested in getting to the point where there's a Twisted-style
reactor and, eventually, support for using AMP-based protocols.

Thanks,
J.




More information about the Twisted-Python mailing list