go-qml / qml

QML support for the Go language
Other
1.96k stars 187 forks source link

SIGCHILD handling in Mac OS #26

Open quarnster opened 10 years ago

quarnster commented 10 years ago

With this piece of code:

package main

import (
    "bytes"
    "github.com/niemeyer/qml"
    "log"
    "os/exec"
)

type tt struct{}

func (t *tt) Test() {
    log.Println("here")
    log.Println(exec.Command("echo", "hello").CombinedOutput())
    log.Println("But not here")
}

func main() {
    qml.Init(nil)
    engine := qml.NewEngine()
    engine.Context().SetVar("crash", &tt{})
    component, err := engine.Load("test.qml", bytes.NewReader([]byte(`
import QtQuick 2.0

Item {
    Component.onCompleted: crash.test()
}
`)))
    if err != nil {
        log.Fatalln(err)
    }

    component.Create(nil)

}

I get this output:

2013/12/26 15:07:38 here
fatal error: runtime: stack split during syscall

runtime stack:
runtime.throw(0x4299eac)
    /Users/quarnster/code/3rdparty/go/src/pkg/runtime/panic.c:462 +0x69
runtime.newstack()
    /Users/quarnster/code/3rdparty/go/src/pkg/runtime/stack.c:261 +0x6cb
runtime.morestack()
    /Users/quarnster/code/3rdparty/go/src/pkg/runtime/asm_amd64.s:225 +0x61

goroutine 4 [stack split]:
syscall.Syscall6(0x7, 0xf68, 0xc2100001b0, 0x0, 0xc21005f360, ...)
    /Users/quarnster/code/3rdparty/go/src/pkg/syscall/asm_darwin_amd64.s:41 +0x5 fp=0x5ffc078
syscall.wait4(0xf68, 0xc2100001b0, 0x0, 0xc21005f360, 0x40450a2, ...)
    /Users/quarnster/code/3rdparty/go/src/pkg/syscall/zsyscall_darwin_amd64.go:32 +0x7d fp=0x5ffc0d8
syscall.Wait4(0xf68, 0x5ffc15c, 0x0, 0xc21005f360, 0xc21000a260, ...)
    /Users/quarnster/code/3rdparty/go/src/pkg/syscall/syscall_bsd.go:126 +0x6e fp=0x5ffc120
os.(*Process).wait(0xc21000a520, 0x0, 0x0, 0x0)
    /Users/quarnster/code/3rdparty/go/src/pkg/os/exec_unix.go:22 +0x10e fp=0x5ffc1b0
os.(*Process).Wait(0xc21000a520, 0x0, 0x0, 0x18)
    /Users/quarnster/code/3rdparty/go/src/pkg/os/doc.go:43 +0x27 fp=0x5ffc1d8
os/exec.(*Cmd).Wait(0xc210065140, 0x0, 0x0)
    /Users/quarnster/code/3rdparty/go/src/pkg/os/exec/exec.go:311 +0x1d9 fp=0x5ffc278
os/exec.(*Cmd).Run(0xc210065140, 0x4113220, 0x42a6e68)
    /Users/quarnster/code/3rdparty/go/src/pkg/os/exec/exec.go:233 +0x54 fp=0x5ffc298
os/exec.(*Cmd).CombinedOutput(0xc210065140, 0x4, 0x5ffc370, 0x1, 0x1, ...)
    /Users/quarnster/code/3rdparty/go/src/pkg/os/exec/exec.go:355 +0x279 fp=0x5ffc328
main.(*tt).Test(0x42a7080)
    /private/tmp/test.go:14 +0x19b fp=0x5ffc440
runtime.call16(0x4114070, 0xc2100000e8)
    /Users/quarnster/code/3rdparty/go/src/pkg/runtime/asm_amd64.s:338 +0x32 fp=0x5ffc458
reflect.Value.call(0x4113fe0, 0x42a7080, 0x0, 0x138, 0x4138320, ...)
    /Users/quarnster/code/3rdparty/go/src/pkg/reflect/value.go:614 +0x148c fp=0x5ffc990
reflect.Value.Call(0x4113fe0, 0x42a7080, 0x0, 0x138, 0xc210065000, ...)
    /Users/quarnster/code/3rdparty/go/src/pkg/reflect/value.go:451 +0xa5 fp=0x5ffc9f8
github.com/niemeyer/qml.hookGoValueCallMethod(0x6050f40, 0xc210046040, 0x7fff00000000, 0x7fff5fbfc7f0)
    /Users/quarnster/code/go/src/github.com/niemeyer/qml/bridge.go:439 +0x460 fp=0x5ffcc98
----- stack segment boundary -----
runtime.cgocallbackg1()
    /Users/quarnster/code/3rdparty/go/src/pkg/runtime/cgocall.c:296 +0xbf fp=0x5ffcd70
runtime.cgocallbackg()
    /Users/quarnster/code/3rdparty/go/src/pkg/runtime/cgocall.c:266 +0x84 fp=0x5ffcd80
runtime.cgocallback_gofunc(0x402a5b3, 0x4001f00, 0x5ffcdf8)
    /Users/quarnster/code/3rdparty/go/src/pkg/runtime/asm_amd64.s:711 +0x67 fp=0x5ffcd90
runtime.asmcgocall(0x4001f00, 0x5ffcdf8)
    /Users/quarnster/code/3rdparty/go/src/pkg/runtime/asm_amd64.s:618 +0x2d fp=0x5ffcd98
runtime.cgocall(0x4001f00, 0x5ffcdf8)
    /Users/quarnster/code/3rdparty/go/src/pkg/runtime/cgocall.c:149 +0x133 fp=0x5ffcde0
github.com/niemeyer/qml._Cfunc_componentCreate(0x612dfc0, 0x0, 0x5ffce38)
    /var/folders/n1/yjqlqd2s04x7th1152h48hsr0000gn/T/go-build434387843/github.com/niemeyer/qml/_obj/_cgo_defun.c:91 +0x31 fp=0x5ffcdf8
github.com/niemeyer/qml.func·022()
    /Users/quarnster/code/go/src/github.com/niemeyer/qml/qml.go:614 +0x57 fp=0x5ffce20
github.com/niemeyer/qml.hookIdleTimer()
    /Users/quarnster/code/go/src/github.com/niemeyer/qml/bridge.go:167 +0x5b fp=0x5ffce50
----- stack segment boundary -----
runtime.cgocallbackg1()
    /Users/quarnster/code/3rdparty/go/src/pkg/runtime/cgocall.c:296 +0xbf fp=0x5ffcf08
runtime.cgocallbackg()
    /Users/quarnster/code/3rdparty/go/src/pkg/runtime/cgocall.c:266 +0x84 fp=0x5ffcf18
runtime.cgocallback_gofunc(0x402a5b3, 0x4001d90, 0x5ffcf90)
    /Users/quarnster/code/3rdparty/go/src/pkg/runtime/asm_amd64.s:711 +0x67 fp=0x5ffcf28
runtime.asmcgocall(0x4001d90, 0x5ffcf90)
    /Users/quarnster/code/3rdparty/go/src/pkg/runtime/asm_amd64.s:618 +0x2d fp=0x5ffcf30
runtime.cgocall(0x4001d90, 0x5ffcf90)
    /Users/quarnster/code/3rdparty/go/src/pkg/runtime/cgocall.c:149 +0x133 fp=0x5ffcf78
github.com/niemeyer/qml._Cfunc_applicationExec(0x42af320)
    /var/folders/n1/yjqlqd2s04x7th1152h48hsr0000gn/T/go-build434387843/github.com/niemeyer/qml/_obj/_cgo_defun.c:73 +0x31 fp=0x5ffcf90
github.com/niemeyer/qml.guiLoop()
    /Users/quarnster/code/go/src/github.com/niemeyer/qml/bridge.go:38 +0x5b fp=0x5ffcfa0
runtime.goexit()
    /Users/quarnster/code/3rdparty/go/src/pkg/runtime/proc.c:1394 fp=0x5ffcfa8
created by github.com/niemeyer/qml.Init
    /Users/quarnster/code/go/src/github.com/niemeyer/qml/qml.go:58 +0xac

goroutine 1 [chan receive]:
github.com/niemeyer/qml.gui(0xc21000a140)
    /Users/quarnster/code/go/src/github.com/niemeyer/qml/bridge.go:66 +0xa3
github.com/niemeyer/qml.(*Common).Create(0xc210044300, 0x0, 0x1a, 0x6411190)
    /Users/quarnster/code/go/src/github.com/niemeyer/qml/qml.go:615 +0x123
main.main()
    /private/tmp/test.go:33 +0x2e0

goroutine 3 [syscall]:
runtime.goexit()
    /Users/quarnster/code/3rdparty/go/src/pkg/runtime/proc.c:1394
exit status 2

Any ideas?

$ go version
go version devel +176779a4ebed Tue Dec 24 08:24:32 2013 -0800 darwin/amd64
quarnster commented 10 years ago

FWIW using C.system instead of the go equivalent works as a work around

niemeyer commented 10 years ago

Please note that these calls are not coming from the qml package. The qml package is just calling back onto the test code at:

main.(*tt).Test(0x42a7080)
    /private/tmp/test.go:14 +0x19b fp=0x5ffc440

Everything above it is not logic from qml.

It also looks like a problem in the Go runtime. I'll report this upstream.

niemeyer commented 10 years ago

Reported at http://golang.org/issue/7227

niemeyer commented 10 years ago

Are you still able to reproduce this issue? If so, can you please run it with GOTRACEBACK=2 and report back on http://golang.org/issue/7227?

Thanks!

xlab commented 10 years ago

Accidentally stepped on that rake too. My setup is OS X 10.9.3, Qt 5.3.0, Go — tested with both 1.3beta1 and 1.3beta2. The minimal reproducible code is

package main

import (
    "log"
    "os/exec"
    "gopkg.in/qml.v0"
)

func main() {
    qml.Init(nil)
    out, _ := exec.Command("date").Output() // [1]
    log.Println(string(out))
    engine := qml.NewEngine() // [2]
    log.Println("Hello?")
    _ = engine
}

Program hangs if [1] is getting called after qml.Init(nil) and if any of the go-qml's machinery was used after, like [2]. When hanged, 50% of CPU is used. In this particular case the program reacts on SIGABRT, but in my project, if a complex combination of go routines and for-select is used, this exec call just hangs my program and even the ^C is ignored.So I just kill -9.

I've visited http://golang.org/issue/7227, but seems that there is no any solution yet. :cry:

quarnster commented 10 years ago

There's a workaround via monkeypatching sigaction et. al. tough

niemeyer commented 9 years ago

@quarnster Are you using that in an app? Maybe we should just do something similar in the qml package itself, to fix the SIGCHILD situation in Mac OS. There's already some memory fiddling to enable testing (see testing.go), but I was not so unhappy about these given it was only for testing. That said, if there's no way to have SIGCHILD working at all otherwise, maybe we should also do that in general for these functions.

quarnster commented 9 years ago

I am using it in various apps that appear to be more stable with such a work around in place. However it isn't a catch all solution as applications can still try and override the signal handlers by using the syscall directly. It is nonetheless a viable workaround that I'm presuming would catch most issues.

Reminder per this and this comment, it's enough to simply reset the signal handler trampoline on OSX for the issue to appear even if the signal handler itself isn't touched. This impacts any signal that any lib might try and install even if it's then trying to be "good" and chain the handlers as the standard library doesn't expose the ability to set the trampoline resulting in it getting overwritten when not using the syscall directly.

While Linux allows signal handlers to be chained, the right flags must be used when installing the new handler, so it's still a potential issue on Linux.

The actual code used is in hack.go and utils.c. cinit is used because doing for example &C.signal returns an address to a CGO wrapper function rather than the actual libc function we want to patch.

I don't know of any issues caused by applying the monkey patching.