[GRASS-dev] Emitting a Python exception instead of calling exit in GRASS

Dear devs,
while thinking about a high level Python interface based on the low
level ctypes interface a question raised in my mind:

Is it possible to raise a Python exception instead of calling exit in
case of a fatal error when using ctypes wrapped GRASS library
functions? So the calling module can gently exit, closing open
database connections and so on?

I am not a Python expert and know quite nothing about its C
implementation of exception handling,
but it seems to me that this will be quite hard to use in GRASS,
because it seems to me that Python C functions like
PyErr_SetString()[1]
simply set an error indicator which must be evaluated by the calling functions?

In the vtk-grass-bridge i am using setjmp/longjmp to shut down the
calling module gently in case of a fatall error. Does anybody know how
to do something similar with exceptions in the ctypes interface?
Something like a TRY/EXCEPT implementation in libgis using
setjmp/longjmp?

Untested and maybe wrong Example:

In libgis:
{{{
extern jmp_buf stack_buffer;
extern const char* error_message;

int TRY(){
    return !setjmp(stack_buffer);
}
}}}

The error function set with G_set_error_routine(error_handler):
{{{
static int error_handler(const char *msg, int fatal)
{
    if (fatal == 0)
    {
        fprintf(stderr, "%s\n", msg);
        return 1;
    }
    else
    {
        fprintf(stderr, "\n############## Exceptiont called ###########\n");
        error_message = msg;
        longjmp(stack_buffer, 1);
    }
    return 1;
}
}}}

Using this in Python:
{{{
import grass.lib.gis as libgis
import grass.lib.raster as libraster

if libgis.TRY():
    fd = libraster.Rast_open_old(name, mapset)
else:
    # Clean up
    # ...
    raise Exception(libgis.error_message)
}}}

does this makes any sense?

Thanks for any suggestions and best regards
Soeren

[1] http://docs.python.org/c-api/exceptions.html#PyErr_SetString

Sören Gebbert wrote:

while thinking about a high level Python interface based on the low
level ctypes interface a question raised in my mind:

Is it possible to raise a Python exception instead of calling exit in
case of a fatal error when using ctypes wrapped GRASS library
functions? So the calling module can gently exit, closing open
database connections and so on?

Yes, but you would have to wrap each function individually.

In the vtk-grass-bridge i am using setjmp/longjmp to shut down the
calling module gently in case of a fatall error. Does anybody know how
to do something similar with exceptions in the ctypes interface?
Something like a TRY/EXCEPT implementation in libgis using
setjmp/longjmp?

You can't separate the "try" from its body. The best you can do at a
library level is to provide a wrapper, e.g.:

  static jmp_buf jbuf;
  
  static void error_handler(void *arg)
  {
      longjmp(jbuf, 1);
  }
  
  int call_with_catch(void (*func)(void *), void *arg)
  {
      if (setjmp(jbuf) == 0) {
          G_add_error_handler(error_handler, NULL);
          (*func)(arg);
          G_remove_error_handler(error_handler, NULL);
          return 0;
      }
      else {
          G_remove_error_handler(error_handler, NULL);
          return -1;
      }
  }

Untested and maybe wrong Example:

int TRY(){
    return !setjmp(stack_buffer);
}

You can't wrap setjmp within another function; the jmp_buf becomes
invalid once you return from the function in which setjmp was
"called". Also, setjmp() may only be used in specific contexts (7.13):

       [#4] An invocation of the setjmp macro shall appear only in
       one of the following contexts:

         -- the entire controlling expression of a selection or
            iteration statement;

         -- one operand of a relational or equality operator with
            the other operand an integer constant expression, with
            the resulting expression being the entire controlling
            expression of a selection or iteration statement;

         -- the operand of a unary ! operator with the resulting
            expression being the entire controlling expression of a
            selection or iteration statement; or

         -- the entire expression of an expression statement
            (possibly cast to void).

       [#5] If the invocation appears in any other context, the
       behavior is undefined.

--
Glynn Clements <glynn@gclements.plus.com>

Hi Glynn,
many thanks for your feedback.
I seems that i still have no idea how the stack/heap works ... but i
am willingly to learn.
Anyway, i would like to continue this discussion using the trac ticket
system [1].
My suggestion is to generate wrapper functions around fatal error
effected library functions automatically.

Best regards
Soeren

[1] http://trac.osgeo.org/grass/ticket/1646

2012/4/23 Glynn Clements <glynn@gclements.plus.com>:

Sören Gebbert wrote:

while thinking about a high level Python interface based on the low
level ctypes interface a question raised in my mind:

Is it possible to raise a Python exception instead of calling exit in
case of a fatal error when using ctypes wrapped GRASS library
functions? So the calling module can gently exit, closing open
database connections and so on?

Yes, but you would have to wrap each function individually.

In the vtk-grass-bridge i am using setjmp/longjmp to shut down the
calling module gently in case of a fatall error. Does anybody know how
to do something similar with exceptions in the ctypes interface?
Something like a TRY/EXCEPT implementation in libgis using
setjmp/longjmp?

You can't separate the "try" from its body. The best you can do at a
library level is to provide a wrapper, e.g.:

   static jmp\_buf jbuf;

   static void error\_handler\(void \*arg\)
   \{
       longjmp\(jbuf, 1\);
   \}

   int call\_with\_catch\(void \(\*func\)\(void \*\), void \*arg\)
   \{
       if \(setjmp\(jbuf\) == 0\) \{
           G\_add\_error\_handler\(error\_handler, NULL\);
           \(\*func\)\(arg\);
           G\_remove\_error\_handler\(error\_handler, NULL\);
           return 0;
       \}
       else \{
           G\_remove\_error\_handler\(error\_handler, NULL\);
           return \-1;
       \}
   \}

Untested and maybe wrong Example:

int TRY(){
return !setjmp(stack_buffer);
}

You can't wrap setjmp within another function; the jmp_buf becomes
invalid once you return from the function in which setjmp was
"called". Also, setjmp() may only be used in specific contexts (7.13):

  \[\#4\] An invocation of the setjmp macro shall appear only  in
  one of the following contexts:

    \-\- the  entire  controlling  expression  of a selection or
       iteration statement;

    \-\- one operand of a relational or equality  operator  with
       the  other operand an integer constant expression, with
       the resulting expression being the  entire  controlling
       expression of a selection or iteration statement;

    \-\- the  operand  of  a unary \! operator with the resulting
       expression being the entire controlling expression of a
       selection or iteration statement; or

    \-\- the   entire  expression  of  an  expression  statement
       \(possibly cast to void\)\.

  \[\#5\] If the invocation appears in  any  other  context,  the
  behavior is undefined\.

--
Glynn Clements <glynn@gclements.plus.com>