Re: Request for Comment: Callable CRM114 Classifiers (libcrm114) --> OOBC

Bill Yerazunis <[email protected]>
Newsgroups gmane.mail.spam.crm114
Message-ID <[email protected]>
   From: "Ger Hobbelt" <[email protected]>

   Nothing overengineered: the code is currently littered with stuff like this:

   ----------------------------
       if (internal_trace)
	   fprintf(stderr, "executing a LEARN\n");
   ----------------------------

   which I'd rather see turn into this:

   ----------------------------
       this->trace_callback(this, CRM_DBG_INTERNAL, "executing a LEARN\n");
   ----------------------------

...which invokes function lookup, register saving, argument building,
a subroutine call, then teardown, register restoring, and context
resuming on every pass by this statement... whether or not we're
actually tracing.

The reason for "if (internal_trace)..." is that this code is by
necessity executed every time the program runs and it contributes zero
value to the computation most of the time.  Hence, make it as
lightweight as possible.


   so that all std I/O gets thrown to the wolves *outside* libcrm114 on a
   /purpose/ basis (tracing, error reporting, etc.): you don't have
   stdout/stderr in GUI and embedded environs anyway. The above would
   then be completed with an interface bit a la:

Conversely, you _don't_ have a GUI in a batch / demon / embedded
environment either.  You _do_ however almost always have something
attached to stdout, even if it's a logfile or an RPC to syslog.

And if it's not already attached to syslog, it's easy enough to
do so at invocation:

   crm fooble.crm 2>/tmp/my_pipe & cat /tmp/mypipe wherever_you_want

in which case you get whatever you want.


   ----------------------------
   typedef void crm_trace_callback_t(crm_config_t *obj, int level, const
   char *fmt_msg, ...);
   ----------------------------
   (which, incidentally, you can augment in GCC (without hurting
   portability to other compilers) to tell GCC he should check this
   callback interface as if it were a regular printf() style formatted
   vararg, so you'd get nice warnings when screwing up your %d, %s, etc.
   elements)


   plus the extra hook in the crm class object:
   ----------------------------
   typedef struct
   {
       crm_trace_callback_t *trace_callback;
   } crm_config_t;
   ----------------------------

It's not a "class object".  Stop thinking that way.  You can't
subclass it, you can't add slots nor methods to it, and it does not
suffer from "name munging".  It's a struct.  That's all.

What it _is_ is the place where all non-stack memory objects must
be kept track of; that's required for thread safety.

As to errors- let's not fall into the trap of non-thread-safe 
error numbers as well as the annoyance of having to invoke a
translator to get their human-interpretable and regexable value.  
Just make it a counted-length member of the struct (and if the 
length is zero, by definition it is not an error.)


   and a /user defined/ function for a console app:
   ----------------------------
   void my_crm_trace(crm_config_t *obj, int level, const char *fmt, ...)
   {
       va_list args;
       my_own_custom_state_object_t *cfg;

       va_start(args, fmt);
       // get my own state object back (functor!)
       cfg = (my_own_custom_state_object_t *)obj->propagator;
       if ((cfg->internal_trace && level == CRM_DBG_INTERNAL)
	   || (cfg->internal_trace && level == CRM_DBG_INTERNAL))
       {
	   vfprintf(stderr, fmt, args);
       }
       va_end(args);
   }
   ----------------------------
   (Note: GUI app writers and embedded folk would copy&paste that, but
   replace the vfprintf() with a GUI message or syslog() call or other.
   Given this callback architecture, that's finally doable!)

Way too complex...  If I had to do that before I get to step 1
of a classification, I'd go find a new library!

   plus 'registration' code somewhere at the start of your run:
   ----------------------------
   crm_config_t *obj = ...;

   ....
   obj->trace_callback = my_crm_trace;

   ...

   // call libcrm114 methods:
   result = obj->learn(.......);
   ....
   /// and more using libcrm114...
   --------------------------------


   Which is nothing spectacular, just basic stuff for professional
   software engineers. ;-)

It's a complex-o-tron.  That's something I'm trying to avoid; when
you need more than three calls do to something, you've already lost.


   And a direct replacement, no frills, for the current code (as shown at top).

Semantically, yes.

Performance-wise, not at all.

When a callback gets invoked 1000 times per email message (and
we might in fact be at that level) and in the general case it
_does nothing_, that's a problem right there.  


   Folks might recognize this as a Visitor Pattern (Gamma et al) done in
   'C'. (Who said you couldn't do OO in 'C', eh? cfront did it. So can
   we. ... I might be joking. ;-) )



   EXTRA:
   ---------

   The only thing in there that I'd like to see 'improved' regarding the
   trace stuff is an 'improved' use of that 'level' argument there, which
   can be easily done using a few platinum blonde macros, so that one can
   filter messages not only at USER vs. INTERNAL, but per function/code
   section within libcrm114: this would mean I'd introduce a 'code
   section' ID code and mix that in with the 'severity level'
   INTERNAL/USER/... so things would look like this in Bill's:

   ----------------------------
       this->trace_callback(this, CRM_TRACE_LEVEL(CRM_DBG_INTERNAL,
   CRM_S_LEARN_SETUP), "executing a LEARN\n");
   ----------------------------

   which would of course be shortened to (with help of a little wrap macro):
   ----------------------------
       this->trace_callback(this, CRM_TRACE_INTERNAL(CRM_S_LEARN_SETUP),
   "executing a LEARN\n");
   ----------------------------


   where you'd have definitions like these to go with that in your .h:
   ----------------------------
   // trace 'severity' levels:
   #define CRM_DBG_USER        1
   #define CRM_DBG_INTERNAL  2

   // code section ID's
   ...
   #define CRM_S_LEARN_SETUP      42
   #define CRM_S_VT_CORE             43
   #define CRM_S_LEARN_FEATURES   44
   #define CRM_S_LEARN_FINALIZE  45
   ...

   // and here's the macro stuff to go with that:
   #define CRM_TRACE_LEVEL(severity, section)  (((severity) << 24) ||
   ((section) & 0xFFF))
   #define CRM_GET_TRACE_SECTION(code)  ((code) & 0xFFF)
   #define CRM_GET_TRACE_SEVERITY(code)  ((code) >> 24)
   ----------------------------





   Error Handlers in libcrm114:
   -----------------------------------------

   For error handlers, I had the idea to do exactly the same for them,
   using an error_callback hook + VMS severity levels a la the above, but
   your suggestion for using a OpenSSL-like error /stack/ is much nicer
   and does not preclude that: right now, nonfatalerror()+fatalerror()s
   get obscured by subsequent ones within the same statement, and using
   an error /stack/ would resolve that. C# and other folks would
   recognize that stuff as a stack trace / exception trace equivalent,
   which is _very_ handy for diagnosing trouble.

Again, I ask:

Why are you building a complexotron?  What's the upside (in usability)
over the downside (in coding complexity, both for implementors
and for users?).

   However, I'd be OK with it if I had to 'do' that error stack 'on the
   outside': what I want for bare metal minimum is an error/warning/fatal
   abort-retry-panic callback like the above.

The code "inside the library" _must_ not depend on having the code
outside the library do the right thing.  Callbacks in particular
are dangerous this way.

For example, consider the compression classifier.  If it has a hiccup
halfway through a LEARN, and does a proper stackish error logging and the
outside logger fails to do the right thing, the index chains now have
bogus values and broken links. 

In short, every call in the library should be Hippocratic - that is,
even in the worst of situations, they should "first, do no harm".
Putting things onto an error stack where the "right thing" needs
to happen upon return is really rather dangerous.  

Ask the database developers about that...  :)


   Besides, talking about resources: I'd rather [help to] create this
   code then having to pick up some well meaning but sad code all over
   again, you know. (And yes, I still have to start work on that
   libcrm114 demo I was talking about last week, thanks to delays due to
   spurious failures -- well, you've probably seen my trails on the
   general and dev ML the last week; I moved off to dev because I felt it
   was really getting out of hand for 'general')

And I have fixes for most of them; they're typoes, not systematic
or design errors.

Oh, if only I could rsync up the wget version.

       - Bill Yerazunis

-------------------------------------------------------------------------
This SF.Net email is sponsored by the Moblin Your Move Developer's challenge
Build the coolest Linux based applications with Moblin SDK & win great prizes
Grand prize is a trip for two to an Open Source event anywhere in the world
http://moblin-contest.org/redirect.php?banner_id=100&url=/
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.