#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>