Ticket #1646 (new enhancement)
GRASS ctypes exception handling
| Reported by: | huhabla | Owned by: | grass-dev@… |
|---|---|---|---|
| Priority: | normal | Milestone: | 7.0.0 |
| Component: | Python ctypes | Version: | svn-trunk |
| Keywords: | setjmp, longjmp, Exception, gently exit | Cc: | |
| Platform: | All | CPU: | All |
Description
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
