I have been experimenting with the V8 javascript bindings created by mattn (here on Github) and I am seeing some strange behavior that I don't understand.
Everything works fine when I create a v8 context in the main thread, but when I create a v8 context from within a goroutine, the program crashes. I'm new to working with cgo (the go-v8 bindings use cgo to interact with v8), so I'm not sure whether the problem is with how I am using the bindings, or if it's an issue with how the bindings are implemented.
Is there anything special about calling cgo functions from within goroutines? is there something obvious I'm missing about memory management with cgo?
Below is some example source code that causes a crash -- I create two v8 contexts within the main thread, and another within a go routine. If I comment out the go routine the program runs fine. Below that is the console output when the program crashes.
Thanks!
Jen---- example source code ----
package mainimport ("fmt""io""net/http")func HelloServer(w http.ResponseWriter, req *http.Request) {io.WriteString(w, "hello, world!\n")}func main() {// Create first v8 contextctx1 := v8.NewContext()// Setup console.log()ctx1.AddFunc("_console_log", func(args ...interface{}) interface{} {for _, arg := range args {fmt.Printf("%v ", arg)}fmt.Println()return ""})ctx1.Eval(`this.console = { "log": function(args) { _console_log(args) } }console.log("Setting this.console"); `)ctx1.Eval(` console.log("First V8 Context in main thread"); `)// --------------------------------------------------------------// Create second context in main threadctx2 := v8.NewContext()// Setup console.log() for second contextctx2.AddFunc("_console_log", func(args ...interface{}) interface{} {for _, arg := range args {fmt.Printf("%v ", arg)}fmt.Println()return ""})ctx2.Eval(`this.console = { "log": function(args) { _console_log(args) } }console.log("Setting this.console"); `)ctx2.Eval(`console.log("Second Context in main thread");`)// --------------------------------------------------------------// ** Comment this section out to prevent crash **// Create a v8 Context in a go routinego func() {ctxgo := v8.NewContext()// Setup console.logctxgo.AddFunc("_console_log", func(args ...interface{}) interface{} {for _, arg := range args {fmt.Printf("%v ", arg)}fmt.Println()return ""})ctxgo.Eval(`this.console = { "log": function(args) { _console_log(args) } }console.log("Setting this.console");`)
ctxgo.Eval(`console.log("Context in go routine");`)}()// --------------------------------------------------------------// Run serverhttp.HandleFunc("/hello", HelloServer)http.ListenAndServe(":12345", nil)}------ console output from program crash -----
jcarlile:v8Exp $ v8ExpSetting this.console""First V8 Context in main thread""Setting this.console""Second Context in main thread""unexpected fault address 0x2d70throw: fault[signal 0xa code=0x2 addr=0x2d70 pc=0x2335b67]goroutine 1 [chan receive]:net.(*pollServer).WaitRead(0xf840063480, 0xf8400a3480, 0xf840065660, 0x23, 0x1, ...)/Users/jcarlile/go/src/pkg/net/fd.go:268 +0x73net.(*netFD).accept(0xf8400a3480, 0xda347, 0x0, 0xf8400650f0, 0xf84008d040, ...)/Users/jcarlile/go/src/pkg/net/fd.go:622 +0x20dnet.(*TCPListener).AcceptTCP(0xf84008d2e0, 0x2a7ae28, 0x0, 0x0, 0x18, ...)/Users/jcarlile/go/src/pkg/net/tcpsock_posix.go:320 +0x71net.(*TCPListener).Accept(0xf84008d2e0, 0x0, 0x0, 0x0, 0x0, ...)/Users/jcarlile/go/src/pkg/net/tcpsock_posix.go:330 +0x49net/http.(*Server).Serve(0xf840063040, 0xf840063540, 0xf84008d2e0, 0x0, 0x0, ...)/Users/jcarlile/go/src/pkg/net/http/server.go:1014 +0x88net/http.(*Server).ListenAndServe(0xf840063040, 0xf840063040, 0x2000, 0x21a7)/Users/jcarlile/go/src/pkg/net/http/server.go:1004 +0xb6net/http.ListenAndServe(0x1f0884, 0x3332313a00000006, 0x0, 0x0, 0xf840069ba0, ...)/Users/jcarlile/go/src/pkg/net/http/server.go:1076 +0x69main.main()/Users/jcarlile/src/genie/experimental/jen/backend/src/v8Exp/v8exp.go:87 +0x145goroutine 2 [syscall]:created by runtime.main/Users/jcarlile/go/src/pkg/runtime/proc.c:221goroutine 3 [syscall]:github.com/mattn/go-v8._Cfunc_v8_create(0x2a7ce90, 0x100000001)/var/folders/pb/tsz4nr2d285fl8k2z58j07g80000gn/T/go-build753632265/github.com/mattn/go-v8/_obj/_cgo_defun.c:58 +0x2fgithub.com/mattn/go-v8.NewContext(0x6, 0x187b9)/var/folders/pb/tsz4nr2d285fl8k2z58j07g80000gn/T/go-build753632265/github.com/mattn/go-v8/_obj/v8.cgo1.go:69 +0x135main._func_003()/Users/jcarlile/src/genie/experimental/jen/backend/src/v8Exp/v8exp.go:63 +0x1ccreated by main.main/Users/jcarlile/src/genie/experimental/jen/backend/src/v8Exp/v8exp.go:82 +0x103goroutine 4 [syscall]:syscall.Syscall6()/Users/jcarlile/go/src/pkg/syscall/asm_darwin_amd64.s:38 +0x5syscall.kevent(0x6, 0x0, 0x0, 0xf8400a2188, 0xf80000000a, ...)/Users/jcarlile/go/src/pkg/syscall/zsyscall_darwin_amd64.go:199 +0x88syscall.Kevent(0xf800000006, 0x0, 0x0, 0xf8400a2188, 0xa0000000a, ...)/Users/jcarlile/go/src/pkg/syscall/syscall_bsd.go:538 +0xa4net.(*pollster).WaitFD(0xf8400a2180, 0xf840063480, 0x0, 0x0, 0x0, ...)/Users/jcarlile/go/src/pkg/net/fd_darwin.go:96 +0x185net.(*pollServer).Run(0xf840063480, 0x0)/Users/jcarlile/go/src/pkg/net/fd.go:236 +0xe4created by net.newPollServer/Users/jcarlile/go/src/pkg/net/newpollserver.go:35 +0x382jcarlile:v8Exp $
--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
A bit of googling suggests that v8 should be called from the main thread. This is a disappointing, but completely unsurprising result.
> d'oh, this is a pretty sad fact. i certainly don't expect that....
I did, which doesn't say a lot about my outlook on life.
I just ran an experiment where I create a single V8 context in a goroutine (and none in the main thread) and I still get a crash. As I understand it, Isolates are useful for running multiple v8 instances in parallel on different threads; my problem seems to be that I can't run v8 from a goroutine...I also tried calling runtime.LockOSThread() in the goroutine and got the same result... code is below
And the current go v8 binding APIs could only be called from the same OS thread that V8 initializes itselfon.Thus i think even using v8 apis in the main goroutine is problematic as it's not guaranteed to be first OSthread (that v8 initializes itself on).even if you LockOSThread() in another goroutine, if the goroutine is not executing on the expected OSthread, v8 will still crash.