Implement error handling in terms of Go panic. * Clownfish `THROW` calls Go `panic`. * Clownfish `trap` utilizes Go `recover`.
Project: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/repo Commit: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/commit/c320f1fd Tree: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/tree/c320f1fd Diff: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/diff/c320f1fd Branch: refs/heads/master Commit: c320f1fd5a65b971a496787b59309ef82d8f4845 Parents: 56d26bc Author: Marvin Humphrey <[email protected]> Authored: Sat Nov 15 19:45:17 2014 -0800 Committer: Marvin Humphrey <[email protected]> Committed: Sun Mar 15 19:02:11 2015 -0700 ---------------------------------------------------------------------- runtime/go/clownfish/clownfish.go | 84 ++++++++++++++++++++++++++++++++++ runtime/go/clownfish/err_test.go | 42 +++++++++++++++++ runtime/go/ext/clownfish.c | 42 ++++++----------- 3 files changed, 141 insertions(+), 27 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/c320f1fd/runtime/go/clownfish/clownfish.go ---------------------------------------------------------------------- diff --git a/runtime/go/clownfish/clownfish.go b/runtime/go/clownfish/clownfish.go index 75a3314..5b9b515 100644 --- a/runtime/go/clownfish/clownfish.go +++ b/runtime/go/clownfish/clownfish.go @@ -31,12 +31,40 @@ package clownfish #include "Clownfish/LockFreeRegistry.h" #include "Clownfish/Method.h" +extern void +GoCfish_PanicErr_internal(cfish_Err *error); +typedef void +(*cfish_Err_do_throw_t)(cfish_Err *error); +extern cfish_Err_do_throw_t GoCfish_PanicErr; + +extern cfish_Err* +GoCfish_TrapErr_internal(CFISH_Err_Attempt_t routine, void *context); +typedef cfish_Err* +(*cfish_Err_trap_t)(CFISH_Err_Attempt_t routine, void *context); +extern cfish_Err_trap_t GoCfish_TrapErr; + +// C symbols linked into a Go-built package archive are not visible to +// external C code -- but internal code *can* see symbols from outside. +// This allows us to fake up symbol export by assigning values only known +// interally to external symbols during Go package initialization. +static CHY_INLINE void +GoCfish_glue_exported_symbols() { + GoCfish_PanicErr = GoCfish_PanicErr_internal; + GoCfish_TrapErr = GoCfish_TrapErr_internal; +} + +static CHY_INLINE void +GoCfish_RunRoutine(CFISH_Err_Attempt_t routine, void *context) { + routine(context); +} + */ import "C" import "runtime" import "unsafe" func init() { + C.GoCfish_glue_exported_symbols() C.cfish_bootstrap_parcel() } @@ -108,3 +136,59 @@ func CFStringToGo(ptr unsafe.Pointer) string { size := C.int(C.CFISH_Str_Get_Size(cfString)) return C.GoStringN(data, size) } + +// TODO: Err should be an interface. +func NewError(mess string) error { + str := C.CString(mess) + len := C.size_t(len(mess)) + messC := C.cfish_Str_new_steal_utf8(str, len) + obj := &Err{C.cfish_Err_new(messC)} + runtime.SetFinalizer(obj, (*Err).callDecRef) + return obj +} + +func (obj *Err) callDecRef() { + C.cfish_dec_refcount(unsafe.Pointer(obj.ref)) + obj.ref = nil +} + +func (obj *Err) Error() string { + return CFStringToGo(unsafe.Pointer(C.CFISH_Err_Get_Mess(obj.ref))) +} + +//export GoCfish_PanicErr_internal +func GoCfish_PanicErr_internal(cfErr *C.cfish_Err) { + goErr := &Err{cfErr} + C.cfish_inc_refcount(unsafe.Pointer(cfErr)) + runtime.SetFinalizer(goErr, (*Err).callDecRef) + panic(goErr) +} + +//export GoCfish_TrapErr_internal +func GoCfish_TrapErr_internal(routine C.CFISH_Err_Attempt_t, + context unsafe.Pointer) *C.cfish_Err { + err := TrapErr(func() { C.GoCfish_RunRoutine(routine, context) }) + if err != nil { + return (err.(*Err)).ref + } + return nil +} + +// Run the supplied routine, and if it panics with a *clownfish.Err, trap and +// return it. +func TrapErr(routine func()) (trapped error) { + defer func() { + if r := recover(); r != nil { + // TODO: pass whitelist of Err types to trap. + myErr, ok := r.(*Err) + if ok { + trapped = myErr + } else { + // re-panic + panic(r) + } + } + }() + routine() + return trapped +} http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/c320f1fd/runtime/go/clownfish/err_test.go ---------------------------------------------------------------------- diff --git a/runtime/go/clownfish/err_test.go b/runtime/go/clownfish/err_test.go new file mode 100644 index 0000000..060cf1e --- /dev/null +++ b/runtime/go/clownfish/err_test.go @@ -0,0 +1,42 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package clownfish_test + +import "git-wip-us.apache.org/repos/asf/lucy-clownfish.git/runtime/go/clownfish" +import "testing" +import "errors" + +func TestTrapErr(t *testing.T) { + err := clownfish.TrapErr( + func() { panic(clownfish.NewError("mistakes were made")) }, + ) + if err == nil { + t.Error("Failed to trap *clownfish.Err") + } +} + +func TestTrapErr_no_trap_string(t *testing.T) { + defer func() { recover() }() + clownfish.TrapErr(func() { panic("foo") }) + t.Error("Trapped plain string") // shouldn't reach here +} + +func TestTrapErr_no_trap_error(t *testing.T) { + defer func() { recover() }() + clownfish.TrapErr(func() { panic(errors.New("foo")) }) + t.Error("Trapped non-clownfish.Error error type") // shouldn't reach here +} http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/c320f1fd/runtime/go/ext/clownfish.c ---------------------------------------------------------------------- diff --git a/runtime/go/ext/clownfish.c b/runtime/go/ext/clownfish.c index 196f90c..851012b 100644 --- a/runtime/go/ext/clownfish.c +++ b/runtime/go/ext/clownfish.c @@ -21,7 +21,6 @@ #define C_CFISH_ERR #define C_CFISH_LOCKFREEREGISTRY -#include <setjmp.h> #include <stdio.h> #include <stdlib.h> @@ -36,6 +35,11 @@ #include "Clownfish/VArray.h" #include "Clownfish/LockFreeRegistry.h" +/* These symbols must be assigned real values during Go initialization, + * which we'll confirm in Err_init(). */ +void (*GoCfish_PanicErr)(Err *error); +Err* (*GoCfish_TrapErr)(Err_Attempt_t routine, void *context); + /******************************** Obj **************************************/ static CFISH_INLINE bool @@ -199,11 +203,17 @@ Method_Host_Name_IMP(Method *self) { /* TODO: Thread safety */ static Err *current_error; -static Err *thrown_error; -static jmp_buf *current_env; void Err_init_class(void) { + if (GoCfish_PanicErr == NULL + || GoCfish_TrapErr == NULL + ) { + fprintf(stderr, "Error at file %s line %d: Unexpected internal " + "failure to initialize functions during bootstrapping\n", + __FILE__, __LINE__); + exit(1); + } } Err* @@ -221,17 +231,7 @@ Err_set_error(Err *error) { void Err_do_throw(Err *error) { - if (current_env) { - thrown_error = error; - longjmp(*current_env, 1); - } - else { - String *message = Err_Get_Mess(error); - char *utf8 = Str_To_Utf8(message); - fprintf(stderr, "%s", utf8); - FREEMEM(utf8); - exit(EXIT_FAILURE); - } + GoCfish_PanicErr(error); } void* @@ -258,19 +258,7 @@ Err_warn_mess(String *message) { Err* Err_trap(Err_Attempt_t routine, void *context) { - jmp_buf env; - jmp_buf *prev_env = current_env; - current_env = &env; - - if (!setjmp(env)) { - routine(context); - } - - current_env = prev_env; - - Err *error = thrown_error; - thrown_error = NULL; - return error; + return GoCfish_TrapErr(routine, context); } /************************** LockFreeRegistry *******************************/
