2

Hi I am using the qml library for go to create UI's. I am trying to learn how to pass information from the UI (qml) to go to then "do something" with. QML is working if it is just a UI. I can run that fine when I do:

func main() {
    if len(os.Args) != 2 {
        fmt.Fprintf(os.Stderr, "usage: %s <qml file>\n", os.Args[0])
        os.Exit(1)
    }
    if err := qml.Run(run); err != nil {
        fmt.Fprintf(os.Stderr, "error: %v\n", err)
        os.Exit(1)
    }
}

func run() error {
    engine := qml.NewEngine()

    engine.On("quit", func() { os.Exit(0) })

    component, err := engine.LoadFile(os.Args[1])
    if err != nil {
        return err
    }
    window := component.CreateWindow(nil)
    window.Show()
    window.Wait()
    return nil
}

However when I add some code, to try and "learn" something from the UI I get the run time error:

panic: runtime error: cgo argument has Go pointer to Go pointer

The code I am adding is:

window.On("visibleChanged", func(visible bool) {
    if (visible) {
            fmt.Println("Width:", window.Int("width"))
    }
})

I am running "go version go1.6 darwin/amd64" on OSX El Capitan

Any ideas why? A google suggests this was an error in Go 1.6 Beta, but I am running the latest stable version (installed a couple of days ago).

If it's not a simple fix, can someone explain "why" this is occuring?

2
  • issue/170 - There may or may not be a bug, but you can forgo the pointer checks with GODEBUG=cgocheck=0 Commented Mar 24, 2016 at 20:42
  • Thanks, this is a good start. Downgrading to 1.5 if I have any problems, but I hope the library gets an update Commented Mar 25, 2016 at 0:44

3 Answers 3

6

The problem is that when C code stores a Go pointer (in this case, a pointer to your callback function), the garbage collector cannot track that pointer in the C code, and may garbage collect the memory that the pointer is pointing to if no Go code is referencing it. This will cause the C code to crash when it attempts to access that memory. All the runtime knows is that the C code retained the pointer (that's why it can panic), but it doesn't know what the C code will do with it later and for how long it will keep it.

To avoid this, the trick used by most libraries was to hold on to a pointer in Go as well (e.g. in a global map), to ensure that the memory is protected from the garbage collector. go-qml uses this trick as well. This trick works, but the compiler and garbage collector have no idea that it does, they cannot verify that you're not making a mistake (e.g. deleting the Go pointer, while the C code still has its pointer).

With Go 1.6, the Go developers decided to be very strict about this, and they no longer allow C code to retain a Go pointer at all. However, if you disable this check, everything will still work in this case, because go-qml implements the trick correctly (it may break in the future however, e.g. if go implements a moving garbage collector).

Here's the issue about it: https://github.com/go-qml/qml/issues/170

Side note: In this specific case, what gets passed to C is a pointer to an interface{}, which itself contains a pointer to the function. That's why you get the error "cgo argument has Go pointer to Go pointer". The reason this isn't allowed is that it's more difficult to protect these pointers from the GC for the duration of the C call, and it's not worth it, so it's forbidden instead (https://github.com/golang/go/issues/12416#issuecomment-136473697). However, even if this were allowed, the code would still be violating the rule about C code keeping a copy of the Go pointer. This isn't actually a problem in Go 1.6, since it doesn't implement a moving garbage collector, but the rules were made so that it can be implemented later.

Sign up to request clarification or add additional context in comments.

7 Comments

I was hoping to get some explanation. This helps alot. Thanks
Maybe it is okay for now, but I think the error message indicates a different problem. Not that the Go memory was held onto a long time and therefore that GC might be fooled into collecting it, but that the Go memory passed to cgo itself contained another Go pointer. I've found another package, gopherjs,that actually worked around this problem by calling malloc for the second level of memory so they wouldn't be passing a Go pointer to a Go pointer to cgo.
I think the reason go pointers inside go pointers are disallowed is that the go runtime can't detect what happens to those. If you just pass in a go pointer, the runtime can detect whether the C code kept it or not, and it's only an error if it keeps it. But the reason for both rules is that the Go GC needs to know about all pointers to Go memory. (golang.org/cmd/cgo/#hdr-Passing_pointers) Actually, this comment explains the reason behind that: github.com/golang/go/issues/12416#issuecomment-136473697
So even if go pointers in go pointers were allowed (e.g. by transitively pinning them for the duration of the C call), go-qml would still be retaining them, so it would still violate the rules.
I have been puzzled for months by this issue, especially why none of the questions are about closures passed to cgo. Those links were good to read, I hadn't seen them both. Here is another one where a Go developer states that sometimes the pointer to pointer error message is actually wrong because cgo, at least in 1.6, can't completely pull apart the expression passed to it. github.com/golang/go/issues/14210
|
1

If you're just playing around, I suggest trying with go 1.5.3. Go 1.6 introduced a different set of constraints on pointers to memory when using cgo, a more restrictive set, and it's possible some go packages that were designed for the older version of go are now breaking a go rule or two.

If this is the case, getting the older package to work with go 1.6, where C is allowed to call go closures, could be harder to fix. But I don't have first hand experience with that yet.

4 Comments

if you want to run go1.6 without the cgo checks, just set GODEBUG=cgocheck=0
That's good to know. That won't change the rules the garbage collector uses though so code that might have worked with go 1.5.3 might now fail with 1.6?
The garbage collector "rules" haven't changed. If there's problem under go1.6 it technically wasn't correct under go1.5 either. There are a couple cases where the cgo checks can't tell what you're doing, and you have to be a little more verbose in go1.6 to pass. I don't know what go-qml is doing to say whether it's unsafe, or it simply needs to revise how it passes some data to C.
The code will still work fine with 1.6 if you disable the check. See my answer why.
-2

Thanks for all the help here. I have written up a beginners tutorial on using QML with Go. It can be viewed here. I will continually update it if I run into any more errors and find fixes. Thanks everyone for your help. QML/GO is an awesome combination.

https://golog.co/blog/article/Using_Go_with_QML_part_1

1 Comment

Your link is broken and the answer does not address the specific question.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.