[GRASS-dev] [GRASS GIS] #1646: GRASS ctypes exception handling

GRASS GIS trac at osgeo.org
Tue Apr 24 04:35:52 EDT 2012


#1646: GRASS ctypes exception handling
-----------------------------------------------------+----------------------
 Reporter:  huhabla                                  |       Owner:  grass-dev@…              
     Type:  enhancement                              |      Status:  new                      
 Priority:  normal                                   |   Milestone:  7.0.0                    
Component:  Python ctypes                            |     Version:  svn-trunk                
 Keywords:  setjmp, longjmp, Exception, gently exit  |    Platform:  All                      
      Cpu:  All                                      |  
-----------------------------------------------------+----------------------
 This enhancement request is based on the GRASS GIS developers mailing list
 discussion in [1].

 The basic idea is to catch fatal error calls in Python when using the
 ctypes GRASS library wrapper. Catching the exit call in case of a fatal
 error is needed to gently exit the calling Python module. So open file
 descriptors or database connections can be closed safely, unfinished
 imports/exports or temporary data can be removed correctly,
 region/mapset/location state of the current session can be reconstructed
 (calling GRASS modules not library functions).

 Glynn suggested a solution to this question:

 Soeren: ''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?''

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

 Glynn suggested this code in the gis library to allow Python Exception
 calls in case of a fatal error:

 {{{
 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;
     }
 }
 }}}

 Trying to implement this conception, i struggled with converting multiple
 function arguments using void pointer handling in ctypes. The code below
 works only for specific functions. This file is called ''catch.c'' located
 in the lib/gis directory:

 {{{
 #include <setjmp.h>
 #include <grass/gis.h>

 static jmp_buf jbuf;

 static void error_handler (void *arg)
 {
  longjmp (jbuf, 1);
 }

 int G_call_with_catch (int (*func) (const char*, const char*), const char
 *name, const char *mapset, int *state)
 {
  if (setjmp (jbuf) == 0)
    {
      int ret;
      G_add_error_handler (error_handler, NULL);
      ret = (*func) (name, mapset);
      G_remove_error_handler (error_handler, NULL);
      *state = 0;
      return ret;

    }
  else
    {
      G_remove_error_handler (error_handler, NULL);
      *state = -1;
      return 0;
    }
 }
 }}}
 The entry in ''include/defs/gis.h'':
 {{{
 int G_call_with_catch (int (*func) (const char*, const char*), const char
 *, const char *, int *);
 }}}
 The Python code to catch the fatal error call of Rast_open_old:
 {{{
 import grass.lib.gis as gis
 import grass.lib.raster as raster
 from ctypes import *

 ropen = CFUNCTYPE(c_int, c_char_p, c_char_p)(raster.Rast_open_old)

 state = c_int()

 fd = gis.G_call_with_catch(ropen, "raster_float", "PERMANENT",
 byref(state))

 if state.value != 0:
        raise Exception("Error")
 }}}

 The problem is that the wrapped library functions have all kind/types of
 return values and arguments. Trying to catch this in Python using ctypes
 is far beyond my capabilities and IMHO tricky.

 My suggestion is to generate a wrapper around each function which may call
 fatal error, using the setjmp/longjmp approach from Glynn. Example:

 The raster open function
 {{{
 int Rast_open_old(char *name, char* mapset);
 }}}
 Will be wrapped by this function
 {{{
 /** This function will call Rast_open_old() and catch the exit call
  *  in case a fatal error occurs.
  *
  *  \param state This variable is set to 0 on success and -1 in case of a
 fatal error
  *  \param message This variable must be large enough to store the fatal
 error message
  */
 int Rast_open_old_noexit(char *name, char *mapset, int *state, char
 *message){
 /* doing setjmp stuff here, setting and unsetting error handler, ... */
 }
 }}}
 Python code using this wrapper may look like this
 {{{
 import grass.lib.gis as gis
 import grass.lib.raster as raster
 from ctypes import *

 name = "elevation"
 mapset = "PERMANENT"
 state = c_int()
 message = 2048 * c_char

 fd = raster.Rast_open_old_noexit(name, mapset, byref(state),
 byref(message))
 if state != 0:
     raise Exception("Fatal error message: %s"%message)
 }}}

 Such wrapping functions can be generated automatically in a pre-compile
 process in each library directory. Each function name will be extended
 with a ''_noexit'' prefix and two new variables will be added: ''state''
 and ''message''. A simple Python script can generate the wrapper and
 includes files.

 Any suggestions are welcome.

 Soeren

 [1] http://comments.gmane.org/gmane.comp.gis.grass.devel/47721

-- 
Ticket URL: <http://trac.osgeo.org/grass/ticket/1646>
GRASS GIS <http://grass.osgeo.org>



More information about the grass-dev mailing list