Skip to content
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

Function Templates with callback functions in Go #68

Merged
merged 8 commits into from
Feb 2, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
deal with Go -> C pointer madness
  • Loading branch information
rogchap committed Jan 29, 2021
commit ff95610f583601acdac5ccef9eb168302a955ec9
63 changes: 61 additions & 2 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,28 @@ import "C"
import (
"fmt"
"runtime"
"sync"
"unsafe"
)

// Due to the limitations of passing pointers to C from Go we need to create
// a registry so that we can lookup the Context from any given callback from V8.
// This is similar to what is described here: https://github.com/golang/go/wiki/cgo#function-variables
// To make sure we can still GC *Context we register the context only when we are
// running a script inside the context and then deregister.
type ctxRef struct {
ctx *Context
refCount int
}

var ctxMutex sync.RWMutex
var ctxRegistry = make(map[int]*ctxRef)
var ctxSeq = 0

// Context is a global root execution environment that allows separate,
// unrelated, JavaScript applications to run in a single instance of V8.
type Context struct {
ref int
ptr C.ContextPtr
iso *Isolate
}
Expand Down Expand Up @@ -48,9 +64,15 @@ func NewContext(opt ...ContextOption) (*Context, error) {
opts.gTmpl = &ObjectTemplate{&template{}}
}

ctxMutex.Lock()
ctxSeq++
ref := ctxSeq
ctxMutex.Unlock()

ctx := &Context{
ref: ref,
ptr: C.NewContext(opts.iso.ptr, opts.gTmpl.ptr, C.int(ref)),
iso: opts.iso,
ptr: C.NewContext(opts.iso.ptr, opts.gTmpl.ptr),
}
runtime.SetFinalizer(ctx, (*Context).finalizer)
// TODO: [RC] catch any C++ exceptions and return as error
Expand All @@ -73,7 +95,10 @@ func (c *Context) RunScript(source string, origin string) (*Value, error) {
defer C.free(unsafe.Pointer(cSource))
defer C.free(unsafe.Pointer(cOrigin))

c.register()
rtn := C.RunScript(c.ptr, cSource, cOrigin)
c.deregister()

return getValue(c, rtn), getError(rtn)
}

Expand All @@ -83,11 +108,45 @@ func (c *Context) Close() {
}

func (c *Context) finalizer() {
C.ContextDispose(c.ptr)
C.ContextFree(c.ptr)
c.ptr = nil
runtime.SetFinalizer(c, nil)
}

func (c *Context) register() {
ctxMutex.Lock()
r := ctxRegistry[c.ref]
if r == nil {
r = &ctxRef{ctx: c}
ctxRegistry[c.ref] = r
}
r.refCount++
ctxMutex.Unlock()
}

func (c *Context) deregister() {
ctxMutex.Lock()
defer ctxMutex.Unlock()
r := ctxRegistry[c.ref]
if r == nil {
return
}
r.refCount--
if r.refCount <= 0 {
delete(ctxRegistry, c.ref)
}
}

func getContext(ref int) *Context {
ctxMutex.RLock()
defer ctxMutex.RUnlock()
r := ctxRegistry[ref]
if r == nil {
return nil
}
return r.ctx
}

func getValue(ctx *Context, rtn C.RtnValue) *Value {
if rtn.value == nil {
return nil
Expand Down
24 changes: 16 additions & 8 deletions function_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,14 @@ import (
type FunctionCallback func(info *FunctionCallbackInfo) *Value

type FunctionCallbackInfo struct {
ctx *Context
args []*Value
}

func (i *FunctionCallbackInfo) Context() *Context {
return i.ctx
}

func (i *FunctionCallbackInfo) Args() []*Value {
return i.args
}
Expand All @@ -28,32 +33,35 @@ func NewFunctionTemplate(iso *Isolate, callback FunctionCallback) (*FunctionTemp
return nil, errors.New("v8go: failed to create new FunctionTemplate: Isolate cannot be <nil>")
}

cbref := iso.registerCallback(callback)

tmpl := &template{
ptr: C.NewFunctionTemplate(iso.ptr, unsafe.Pointer(&callback)),
ptr: C.NewFunctionTemplate(iso.ptr, C.int(cbref)),
iso: iso,
}
runtime.SetFinalizer(tmpl, (*template).finalizer)
return &FunctionTemplate{tmpl}, nil
}

//export goFunctionCallback
func goFunctionCallback(callback unsafe.Pointer, args *C.ValuePtr, args_count int) {
callbackFunc := *(*FunctionCallback)(callback)
func goFunctionCallback(ctxref int, cbref int, args *C.ValuePtr, args_count int) C.ValuePtr {
ctx := getContext(ctxref)

//TODO: This will need access to the Context to be able to create return values
info := &FunctionCallbackInfo{
ctx: ctx,
args: make([]*Value, args_count),
}

argv := (*[1 << 30]C.ValuePtr)(unsafe.Pointer(args))[:args_count:args_count]
for i, v := range argv {
//TODO(rogchap): We must pass the current context and add this to the value struct
val := &Value{ptr: v}
runtime.SetFinalizer(val, (*Value).finalizer)
info.args[i] = val
}

rtnVal := callbackFunc(info)
//TODO: deal with the rtnVal
_ = rtnVal
callbackFunc := ctx.iso.getCallback(cbref)
if val := callbackFunc(info); val != nil {
return val.ptr
}
return nil
}
28 changes: 23 additions & 5 deletions function_template_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package v8go_test

import (
"context"
"fmt"
"net/http"
"testing"

"rogchap.com/v8go"
Expand All @@ -13,17 +15,33 @@ func TestFunctionTemplate(t *testing.T) {
iso, _ := v8go.NewIsolate()
global, _ := v8go.NewObjectTemplate(iso)
printfn, _ := v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value {
fmt.Printf("Called print(): %+v\n", info.Args())
return nil
args := info.Args()
fmt.Printf("Called print(): %+v\n", args)
val, _ := v8go.NewValue(iso, int32(len(args)))
return val
})
logfn, _ := v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value {
logfn, _ := v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) (rtn *v8go.Value) {
fmt.Printf("Called log(): %+v\n", info.Args())
return nil
return
})
th := &thing{}
headfn, _ := v8go.NewFunctionTemplate(iso, th.cb)

global.Set("print", printfn)
global.Set("log", logfn)
global.Set("head", headfn)
ctx, _ := v8go.NewContext(iso, global)
ctx.RunScript("log();print('stuff', 'more', 4, 2n)", "")
val, _ := ctx.RunScript("head();log(print);print('stuff', 'more', 4, 2n)", "")
fmt.Printf("val = %+v\n", val)
}

type thing struct {
c context.Context
d *http.Client
}

func (t *thing) cb(info *v8go.FunctionCallbackInfo) *v8go.Value {
t.d = http.DefaultClient
go t.d.Head("http://rogchap.com")
return nil
}
36 changes: 29 additions & 7 deletions isolate.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ package v8go
import "C"

import (
"runtime"
"sync"
)

Expand All @@ -15,6 +14,10 @@ var v8once sync.Once
// with many V8 contexts for execution.
type Isolate struct {
ptr C.IsolatePtr

cbMutex sync.RWMutex
cbSeq int
cbs map[int]FunctionCallback
}

// HeapStatistics represents V8 isolate heap statistics
Expand All @@ -35,14 +38,18 @@ type HeapStatistics struct {
// NewIsolate creates a new V8 isolate. Only one thread may access
// a given isolate at a time, but different threads may access
// different isolates simultaneously.
// When an isolate is no longer used its resources should be freed
// by calling iso.Dispose().
// An *Isolate can be used as a v8go.ContextOption to create a new
// Context, rather than creating a new default Isolate.
func NewIsolate() (*Isolate, error) {
v8once.Do(func() {
C.Init()
})
iso := &Isolate{C.NewIsolate()}
runtime.SetFinalizer(iso, (*Isolate).finalizer)
iso := &Isolate{
ptr: C.NewIsolate(),
cbs: make(map[int]FunctionCallback),
}
// TODO: [RC] catch any C++ exceptions and return as error
return iso, nil
}
Expand Down Expand Up @@ -72,17 +79,32 @@ func (i *Isolate) GetHeapStatistics() HeapStatistics {
}
}

func (i *Isolate) finalizer() {
// Dispose will dispose the Isolate VM; subsequent calls will panic.
func (i *Isolate) Dispose() {
C.IsolateDispose(i.ptr)
i.ptr = nil
runtime.SetFinalizer(i, nil)
}

// Close will dispose the Isolate VM; subsequent calls will panic
// Close is Deprecated. Use `iso.Dispose()`.
rogchap marked this conversation as resolved.
Show resolved Hide resolved
func (i *Isolate) Close() {
i.finalizer()
i.Dispose()
}

func (i *Isolate) apply(opts *contextOptions) {
opts.iso = i
}

func (i *Isolate) registerCallback(cb FunctionCallback) int {
i.cbMutex.Lock()
i.cbSeq++
ref := i.cbSeq
i.cbs[ref] = cb
i.cbMutex.Unlock()
return ref
}

func (i *Isolate) getCallback(ref int) FunctionCallback {
i.cbMutex.RLock()
defer i.cbMutex.RUnlock()
return i.cbs[ref]
}
2 changes: 1 addition & 1 deletion template.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func (t *template) Set(name string, val interface{}, attributes ...PropertyAttri
}

func (o *template) finalizer() {
C.TemplateDispose(o.ptr)
C.TemplateFree(o.ptr)
o.ptr = nil
runtime.SetFinalizer(o, nil)
}
Loading