[zeromq-dev] changing the C++ api to not throw exceptions on EFSM

john skaller skaller at users.sourceforge.net
Thu Jan 26 03:02:11 CET 2012


On 26/01/2012, at 6:14 AM, Yi Ding wrote:

> The C++ API is currently written in such a way that any return value on zmq_recv, except for EAGAIN, triggers an exception.
> 
> However, if we use REQ/REP in conjunction with ZMQ_FD, as we currently do in our code and nzmqt (I'm sure it's also used in other bindings) EFSM is basically unavoidable as triggering a EFSM is required if we want to "reset" the receive state for the edge-triggering.
> 
> There are a couple of ways I can think of dealing with this (from least to most radical):
> 1) try catches every time we call recv.  This is how we do this currently, but having exceptions that aren't exceptional doesn't seem to be particularly good programming practice.
> 2) don't throw an exception on EFSM in the C++ binding, and instead return false
> 3) if ZMQ_NOBLOCK is passed in, replace EFSM with EAGAIN.
> 4) Get rid of REQ-REP entirely, and just subsume it with ROUTER-DEALER

Always throwing an exception is a bit primitive. There is a better way I am
implementing the Felix binding that could also work in C++. This technique
requires some template meta-programming. I will explain at the end how it helps
with the above problem.

The idea is this: first, you have a class of the raw functions returning the usual error codes.
Lets call this ZeroMQ (because I do .. :)

Then, you make a template which in Felix looks like this:

  class Check[T] 
  {
    proc int_to_proc (var x:int) { if x == -1 do ehandler; done }
    fun int_to_int (var x:int) = { if x == -1 do ehandler; done return x; }
    fun pointer_to_pointer[U] (var p:&U) = { if C_hack::isNULL p do #ehandler; done return p; }
    virtual proc ehandler: unit;
  }

In C++ these are all static members. The virtual "ehandler" is simply declared but 
not implemented. The type variable T here is a *phantom type*. It's not used for
anything. Its a trick.

Now, you make a new class derived from both ZeroMQ and Check

template<class T> class Checked_ZeroMQ<T> : public ZeroMQ, public Check[T] { ...

   void checked_recv ( .. ) {  int_to_proc (recv (... ))

In this class, you wrap the functions up to call "int_to_int", "int_to_proc" etc
on the return codes. These functions check for an error and call ehandler if there
is one. So now you have nice functions you can call without bothering to
do error handling. You can of course also do a special handling of EAGAIN
in the Check class.

Note, the Check class above is NOT part of ZMQ in my implementation:
it's actually in the Errno module, since it can be used with ANY Posix like function.

Now we instantiate the class, i.e. we make a specialisation:

class thrower {};
template <> class Check<thrower> { void ehandler() { throw errno; } };

and finally specialise the main class:

template <> class ZMQ : public Checked_ZeroMQ<thrower> {};

The idea is you can have whole suites of functions with different error handling
strategies. You only need to implement a single function, ehandler, to do this.
For example instead of "throw" you could call a user error callback.

In all of this, you ALWAYS have the fallback to the raw interface.
What's more, you can deploy several error handling strategies together,
you just need to explicitly qualify the function calls to say what strategy
to use, eg use

	Checked_ZeroMQ<strategy>::recv ( ... )

Unlike Felix, you can totally change the functions in a specialisation in C++,
including the return type. [This is really bad design, but here it could be
some fun since you have have two recv variants, one which throws always
and one which lets EAGAIN through]


--
john skaller
skaller at users.sourceforge.net







More information about the zeromq-dev mailing list