New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Go bindings #325
base: master
Are you sure you want to change the base?
Go bindings #325
Conversation
xapian-bindings/go/xapian.go
Outdated
func NewDatabase(args ...interface{}) (db raw.Database, err error) { | ||
defer func() { | ||
if r := recover(); r != nil { | ||
if errMsg, ok := r.(string); ok { | ||
for msgPrefix, errType := range errorMap { | ||
if strings.HasPrefix(errMsg, msgPrefix) { | ||
err = errType | ||
return | ||
} | ||
} | ||
} | ||
panic(r) | ||
} | ||
}() | ||
|
||
db = raw.NewDatabase(args...) | ||
return | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ojwb this is the basic idea behind how to handle errors/exceptions on the go binding
The implementation here is very poor because the error message contains extra information that is not relevant when asserting which kind of error it is (we could use a direct hash access if the error message was just the class/error name)
I believe this could be fixed if we can change how swig generates the error message to include the error code or any other direct identifier we could intercept on Go.
This is the entire error handling function:
static void _swig_gopanic(const char *p) {
struct {
const char *p;
} SWIGSTRUCTPACKED a;
a.p = p;
crosscall2(_cgo_panic, &a, (int) sizeof a);
}
#define SWIG_exception(code, msg) _swig_gopanic(msg)
The declaration of crosscall2 is:
void crosscall2(void (*fn)(void *), void *, int);
So if we can change it to include the code
from SWIG_exception
I believe the error handling would be simpler
Also, if there is a way to change any of that, we could also stop calling _cgo_panic
and instead call a Go-provided function that wouldn't cause any panic (recoverable or otherwise)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW, I notice SWIG itself has moved away from using crosscall2
for this in swig/swig@6ca5d5d:
swig -go: don't use crosscall2 for panicking
Instead rely only on documented and exported interfaces.
SWIG/Go doesn't seem to make use of SWIG_RuntimeError
, etc but if you want to turn the C++ exception into a numeric code you can probably rethrow and catch it in C++ to dispatch by exception type. Look at SetPythonException()
in xapian-bindings/python/except.i
(generated) for example.
xapian-bindings/go/xapian.go
Outdated
@@ -0,0 +1,53 @@ | |||
package xapian |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My proposal to have an actual error handling (instead of having to let users rely on recover
by themselves) is to provide a thin wrapper to the raw SDK and making sure that whenever we get a panic
from swig we transform that into an error
With these changes, we no longer get a SIGABRT
from Go whenever a panic is called from Swig, this way we can recover()
from the panic and keep the process going.
I don't know if this addresses the issue with destructors not being called after the panic
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't seem ideal that this seems to require writing a wrapper for every wrapped function. We use SWIG because it automates away all that drudgery. We've had hand-coded Perl XS and Java JNI bindings in the past and they always ended up lagging behind the C++ API because of the extra work needed, whereas for SWIG-generated bindings in most cases a new method gets wrapped automatically, while new classes just need setting up according to what sort of classes they are (e.g. user-subclassable classes get special handling).
I'll take a look and see what we can do.
I think ideally SWIG itself would support a more Go-like way to wrap exceptions, but there doesn't seem to be much progress on that front.
} else { | ||
$env:PATH="C:\mingw-w64\x86_64-7.2.0-posix-seh-rt_v5-rev1\mingw64\bin;C:\msys64\usr\bin;$env:PATH" | ||
$env:CONFIGOPTS+=" --host=x86_64-w64-mingw32" | ||
$env:CONFIGOPTS+=" --host=x86_64-w64-mingw32 --with-go" | ||
$env:BINDINGS+=" xapian-bindings" | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The changes to this file don't seem to actually do anything useful - the BINDINGS
environment variable is set but never used. The added --with-go
option has no effect unless xapian-bindings actually gets configured (and if it does get configured, it should automatically enable any languages the tools/libraries/etc are found for - all --with-go
would accomplish is to (a) give an error if the things needed to enable Go bindings aren't found and (b) disable that auto-detection for other languages. Maybe (a) is arguably useful (it would catch the case where the CI image is updated and something needed for a particular language is no longer found - currently we'd quietly stop testing it).
In the GHA CI we just install the various dependencies and rely on the auto-detection, so I think it'd be better to make a deliberate choice for whether we want to pass --with-X
for all the languages we expect to be building and testing bindings for, and if so todo that consistently across the CI systems we're using.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In #292 (review) I noted that the use of $BINDINGS
was removed, apparently while resolving a merge conflict.
if test -z "${GOROOT}" ; then | ||
AC_MSG_ERROR([goroot not set correctly]) | ||
fi | ||
fi |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're missing AC_MSG_RESULT
for the failure paths above, which will give mangled output. I raised this on the original PR but it was never addressed.
It also shouldn't error out unless $with_go
is set to yes
- if it's empty we enable the language is everything we need is detected, and disable it otherwise - that way a plain ./configure
enables all the bindings it can.
if test -n "${USE_CLANG_LIB}" ; then | ||
AC_MSG_RESULT([${USE_CLANG_LIB}]) | ||
AC_SUBST(USE_CLANG_GO_LIB,'-lc++') | ||
AC_MSG_RESULT([${USE_CLANG_GO_LIB}]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no AC_MSG_CHECKING
corresponding to some of these AC_MSG_RESULT
calls which will result in garbled output.
if test -z "$GO" ; then | ||
AC_MSG_ERROR("go not installed or go path has not been set") | ||
fi | ||
fi |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to duplicate much of what's added above.
You shouldn't be running $XAPIAN_CONFIG
directly - the XO_LIB_XAPIAN
macro has already done that. Just use the already substituted XAPIAN_CXXFLAGS
instead of GO_CXX
, and similarly for other values.
Don't try to handle the "xapian-core not found" case - that's not the job of each language binding to worry about, and anyway XO_LIB_XAPIAN
will have aborted with an error if it didn't find it.
./bootstrap && ./configure && ./make && make install | ||
|
||
if you have already installed xapian-core : | ||
just do make install: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is very specific to building from git, and not what should be in the Go-specific documentation. Look at what other bindings have here - e.g. https://github.com/xapian/xapian/blob/master/xapian-bindings/php7/docs/index.rst
xapian-bindings/go/xapian.go
Outdated
@@ -0,0 +1,53 @@ | |||
package xapian |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't seem ideal that this seems to require writing a wrapper for every wrapped function. We use SWIG because it automates away all that drudgery. We've had hand-coded Perl XS and Java JNI bindings in the past and they always ended up lagging behind the C++ API because of the extra work needed, whereas for SWIG-generated bindings in most cases a new method gets wrapped automatically, while new classes just need setting up according to what sort of classes they are (e.g. user-subclassable classes get special handling).
I'll take a look and see what we can do.
I think ideally SWIG itself would support a more Go-like way to wrap exceptions, but there doesn't seem to be much progress on that front.
xapian-bindings/go/xapian.go
Outdated
func NewDatabase(args ...interface{}) (db raw.Database, err error) { | ||
defer func() { | ||
if r := recover(); r != nil { | ||
if errMsg, ok := r.(string); ok { | ||
for msgPrefix, errType := range errorMap { | ||
if strings.HasPrefix(errMsg, msgPrefix) { | ||
err = errType | ||
return | ||
} | ||
} | ||
} | ||
panic(r) | ||
} | ||
}() | ||
|
||
db = raw.NewDatabase(args...) | ||
return | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW, I notice SWIG itself has moved away from using crosscall2
for this in swig/swig@6ca5d5d:
swig -go: don't use crosscall2 for panicking
Instead rely only on documented and exported interfaces.
SWIG/Go doesn't seem to make use of SWIG_RuntimeError
, etc but if you want to turn the C++ exception into a numeric code you can probably rethrow and catch it in C++ to dispatch by exception type. Look at SetPythonException()
in xapian-bindings/python/except.i
(generated) for example.
@@ -245,7 +245,7 @@ STANDARD_IGNORES(Xapian, Query) | |||
|
|||
%warnfilter(SWIGWARN_TYPE_UNDEFINED_CLASS) Xapian::Query::Internal; | |||
#if defined SWIGCSHARP || defined SWIGJAVA || defined SWIGPERL || \ | |||
defined SWIGPYTHON || defined SWIGRUBY | |||
defined SWIGPYTHON || defined SWIGRUBY || defined SWIGGO | |||
// C#, Java, Perl, Python and Ruby wrap these "by hand" to give a nicer API |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment no longer matches the code.
|
||
make | ||
|
||
make install |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would be useful to document (if the commands given actually work - I'm dubious how well tested it is, given the --nocofirm
in the second pacman command seems like a typo) but isn't related to Go support so would be better as a separate patch.
AC_MSG_RESULT([${USE_CLANG_LIB}]) | ||
if test -n "${USE_CLANG_LIB}" ; then | ||
AC_MSG_RESULT([${USE_CLANG_LIB}]) | ||
AC_SUBST(USE_CLANG_GO_LIB,'-lc++') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copying over a comment from the old PR which was never responded to:
This also looks very dubious to me, and could misdetect if the stdlib is present elsewhere - e.g. GXX=/home/stdlib/bin/gcc, or misdetect the other way if CXXFLAGS=-stdlib=libc++ is used instead of CXX='clang -stdlib=libc++'.
What is this actually trying to achieve?
I realise you may well not know, but if so it seems better to drop this and see what (if anything) breaks so we actually understand the need for it, and can perhaps address it better.
This commit also makes sure that the `xapian.go` generated on the source tree contains the necessary CGO flags to compile
func NewDatabase(a ...interface{}) (db Database, err error) { | ||
defer func() { | ||
if r := recover(); r != nil { | ||
if errMsg, ok := r.(string); ok { | ||
err = errors.New(errMsg) | ||
return | ||
} | ||
panic(r) | ||
} | ||
}() | ||
|
||
db = NewWrappedDatabase(a...) | ||
return | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ojwb following your last comment I got rid of the static xapian.go
file and am now relying only on the go.i
file
To still be able to return either a database instance or an error (the idiomatic go way of doing errors) there is no way around recover
ing from panics, which in this case is a quite verbose way of having a catch
Also, in recent versions, it looks like Go now exposes the _cgo_panic
function expecting the parameter to be a struct with a cstr inside, so there is no way to return a more complex struct (with code and message) at the moment
We could receive the message and split by :
and use the first index as code and second as message, but I am not sure at the moment if this would be the best approach
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I forgot to mention that I am, of course, using Xapian::Database::Database
just as example, and that other functions that can also throw errors would follow the same pattern
@rafaelsierra @ojwb what's the status of this PR ? I'd like to take over and start another one (based on this PR, and rebased to new master), if, may I allowed to do this ? |
Hey @ttys3 , please feel free to start a new PR. Unfortunately I didn't had much time to go back to this =/ |
This PR builds on top of #292