Re: [go-nuts] crash when using go-v8 bindings in goroutine

297 views
Skip to first unread message

minux

unread,
Feb 4, 2013, 2:56:18 PM2/4/13
to Jen Carlile, golan...@googlegroups.com


On Tue, Feb 5, 2013 at 3:18 AM, Jen Carlile <j...@vannevartech.com> wrote:

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 main

import (
"fmt"
"io"
"net/http"
)

func HelloServer(w http.ResponseWriter, req *http.Request) {
io.WriteString(w, "hello, world!\n")
}

func main() {
// Create first v8 context
ctx1 := 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 thread
ctx2 := v8.NewContext()

// Setup console.log() for second context
ctx2.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 routine
go func() {
ctxgo := v8.NewContext()

// Setup console.log
ctxgo.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 server
http.HandleFunc("/hello", HelloServer)
http.ListenAndServe(":12345", nil)
}

------ console output from program crash -----

jcarlile:v8Exp $ v8Exp
Setting this.console
""
First V8 Context in main thread
""
Setting this.console
""
Second Context in main thread
""
unexpected fault address 0x2d70
throw: 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 +0x73
net.(*netFD).accept(0xf8400a3480, 0xda347, 0x0, 0xf8400650f0, 0xf84008d040, ...)
    /Users/jcarlile/go/src/pkg/net/fd.go:622 +0x20d
net.(*TCPListener).AcceptTCP(0xf84008d2e0, 0x2a7ae28, 0x0, 0x0, 0x18, ...)
    /Users/jcarlile/go/src/pkg/net/tcpsock_posix.go:320 +0x71
net.(*TCPListener).Accept(0xf84008d2e0, 0x0, 0x0, 0x0, 0x0, ...)
    /Users/jcarlile/go/src/pkg/net/tcpsock_posix.go:330 +0x49
net/http.(*Server).Serve(0xf840063040, 0xf840063540, 0xf84008d2e0, 0x0, 0x0, ...)
    /Users/jcarlile/go/src/pkg/net/http/server.go:1014 +0x88
net/http.(*Server).ListenAndServe(0xf840063040, 0xf840063040, 0x2000, 0x21a7)
    /Users/jcarlile/go/src/pkg/net/http/server.go:1004 +0xb6
net/http.ListenAndServe(0x1f0884, 0x3332313a00000006, 0x0, 0x0, 0xf840069ba0, ...)
    /Users/jcarlile/go/src/pkg/net/http/server.go:1076 +0x69
main.main()
    /Users/jcarlile/src/genie/experimental/jen/backend/src/v8Exp/v8exp.go:87 +0x145

goroutine 2 [syscall]:
created by runtime.main
    /Users/jcarlile/go/src/pkg/runtime/proc.c:221

goroutine 3 [syscall]:
    /var/folders/pb/tsz4nr2d285fl8k2z58j07g80000gn/T/go-build753632265/github.com/mattn/go-v8/_obj/_cgo_defun.c:58 +0x2f
    /var/folders/pb/tsz4nr2d285fl8k2z58j07g80000gn/T/go-build753632265/github.com/mattn/go-v8/_obj/v8.cgo1.go:69 +0x135
main._func_003()
    /Users/jcarlile/src/genie/experimental/jen/backend/src/v8Exp/v8exp.go:63 +0x1c
created by main.main
    /Users/jcarlile/src/genie/experimental/jen/backend/src/v8Exp/v8exp.go:82 +0x103

goroutine 4 [syscall]:
syscall.Syscall6()
    /Users/jcarlile/go/src/pkg/syscall/asm_darwin_amd64.s:38 +0x5
syscall.kevent(0x6, 0x0, 0x0, 0xf8400a2188, 0xf80000000a, ...)
    /Users/jcarlile/go/src/pkg/syscall/zsyscall_darwin_amd64.go:199 +0x88
syscall.Kevent(0xf800000006, 0x0, 0x0, 0xf8400a2188, 0xa0000000a, ...)
    /Users/jcarlile/go/src/pkg/syscall/syscall_bsd.go:538 +0xa4
net.(*pollster).WaitFD(0xf8400a2180, 0xf840063480, 0x0, 0x0, 0x0, ...)
    /Users/jcarlile/go/src/pkg/net/fd_darwin.go:96 +0x185
net.(*pollServer).Run(0xf840063480, 0x0)
    /Users/jcarlile/go/src/pkg/net/fd.go:236 +0xe4
created by net.newPollServer
    /Users/jcarlile/go/src/pkg/net/newpollserver.go:35 +0x382
jcarlile:v8Exp $
you can use gdb (7.1+) to discover the actual place that triggered the SIGBUS and
accurate stack trace.

Dave Cheney

unread,
Feb 4, 2013, 3:30:19 PM2/4/13
to minux, Jen Carlile, golan...@googlegroups.com
A bit of googling suggests that v8 should be called from the main thread. This is a disappointing, but completely unsurprising result. 
--
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.
 
 

minux

unread,
Feb 4, 2013, 3:33:21 PM2/4/13
to Dave Cheney, Jen Carlile, golan...@googlegroups.com
On Tue, Feb 5, 2013 at 4:30 AM, Dave Cheney <da...@cheney.net> wrote:
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....

Dave Cheney

unread,
Feb 4, 2013, 3:37:55 PM2/4/13
to minux, Jen Carlile, golan...@googlegroups.com
> 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.

Jen Carlile

unread,
Feb 4, 2013, 4:04:24 PM2/4/13
to golan...@googlegroups.com, minux, Jen Carlile
hmmm, that is unfortunate.  I should have suspected...
Thanks for your help! 

Jen

Dave Cheney

unread,
Feb 4, 2013, 4:11:19 PM2/4/13
to Jen Carlile, golan...@googlegroups.com, minux
I would confirm with your own googling first.

minux

unread,
Feb 4, 2013, 4:36:28 PM2/4/13
to Dave Cheney, Yasuhiro MATSUMOTO, Jen Carlile, golan...@googlegroups.com
On Tue, Feb 5, 2013 at 4:37 AM, Dave Cheney <da...@cheney.net> wrote:
> d'oh, this is a pretty sad fact. i certainly don't expect that....
i think relying on being called only from the main thread is a sign of bad design,
and it certainly shouldn't happen for v8.

I did, which doesn't say a lot about my outlook on life.
aha, indeed, it's not the case for v8.
this helps explaining that:

i think the v8 go binding doesn't handle this well.

before every v8 call, i'd call v8::Isolate::GetCurrent(), and if it's NULL, it should
panic and warning its user that they must do runtime.LockOSThread() first and
only call v8 functions from one goroutine (the that v8 binding package do not
provide the functionality of v8 isolates).

Jen Carlile

unread,
Feb 4, 2013, 5:34:20 PM2/4/13
to golan...@googlegroups.com, Dave Cheney, Yasuhiro MATSUMOTO, Jen Carlile
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

any thoughts?

Thanks,
Jen

--------------------------------------------------------
package main

import (
"fmt"
"io"
"net/http"
)

func HelloServer(w http.ResponseWriter, req *http.Request) { io.WriteString(w, "hello, world!\n") }

func main() {
// Create a v8 Context in a go routine
go func() {
ctxgo := v8.NewContext()

// Setup console.log
ctxgo.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 server
http.HandleFunc("/hello", HelloServer)
http.ListenAndServe(":12345", nil)
}

minux

unread,
Feb 4, 2013, 5:38:27 PM2/4/13
to Jen Carlile, golan...@googlegroups.com, Dave Cheney, Yasuhiro MATSUMOTO
On Tue, Feb 5, 2013 at 6:34 AM, Jen Carlile <j...@vannevartech.com> wrote:
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
Note that goroutines could be scheduled to run on any OS threads.
And the current go v8 binding APIs could only be called from the same OS thread that V8 initializes itself
on.

Thus i think even using v8 apis in the main goroutine is problematic as it's not guaranteed to be first OS
thread (that v8 initializes itself on).

even if you LockOSThread() in another goroutine, if the goroutine is not executing on the expected OS
thread, v8 will still crash.

mattn

unread,
Feb 4, 2013, 8:38:39 PM2/4/13
to golan...@googlegroups.com, Jen Carlile, Dave Cheney, Yasuhiro MATSUMOTO
Note that goroutines could be scheduled to run on any OS threads.
And the current go v8 binding APIs could only be called from the same OS thread that V8 initializes itself
on.

Thus i think even using v8 apis in the main goroutine is problematic as it's not guaranteed to be first OS
thread (that v8 initializes itself on).

even if you LockOSThread() in another goroutine, if the goroutine is not executing on the expected OS
thread, v8 will still crash.

I'm not make sure that go-v8 works on multi-thread. But I guess that it should be work on goroutine. data are passing through with JSON between go and v8. So if it get crash, it's a bug, I think.


Reply all
Reply to author
Forward
0 new messages