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

x/exp/shiny: driver.Main not returning (and also can be made to panic unexpectedly) #21796

Open
williamhanisch opened this issue Sep 7, 2017 · 9 comments

Comments

@williamhanisch
Copy link

commented Sep 7, 2017

Please answer these questions before submitting your issue. Thanks!
See below.

What version of Go are you using (go version)?

go version go1.9 darwin/amd64

Does this issue reproduce with the latest release?

Yes.

What operating system and processor architecture are you using (go env)?

GOARCH="amd64"
GOBIN=""
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPATH="/Users/william"
GORACE=""
GOROOT="/usr/local/go"
GOTOOLDIR="/usr/local/go/pkg/tool/darwin_amd64"
GCCGO="gccgo"
CC="clang"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/60/zldw5qqn401b20vjmzxx1_140000gn/T/go-build661525149=/tmp/go-build -gno-record-gcc-switches -fno-common"
CXX="clang++"
CGO_ENABLED="1"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"

What did you do?

Below are simplified fully runnable programs that illustrate the issue. I'm trying to add cleanup code after the function passed to driver.Main returns. The documentation for driver.Main sates that "It returns when f returns." However, when returning from the passed-in function (closure), the program exits. In lieu of cleanup code, I'm using print statements in the example code below. Although I could achieve what I need using a defer call in the passed-in function (closure), there still seems to be a bug (or at any rate, a documentation bug) worth reporting.

For additional information, see this thread on go-nuts between Dave MacFarlane (who encouraged me to file this report) and me: https://groups.google.com/forum/#!topic/golang-nuts/DmEV4K3xIZY

If possible, provide a recipe for reproducing the error.
A complete runnable program is good.
A link on play.golang.org is best.
Here is the first illustration program (since this is a shiny based program, using the go playground won't work, I don't think):

package main

import (
"fmt"

"golang.org/x/exp/shiny/driver"
"golang.org/x/exp/shiny/screen"

)

func main() {
fmt.Println("Starting.")
driver.Main(func(s screen.Screen) {
fmt.Println("I'm printed; you can see me.")
return
})
fmt.Println("I'm not printed; alas, I can't be seen.")
}

What did you expect to see?

All three print statements being called.

What did you see instead?

Only the first two were called.

After adding code to create a window and running it, it panics. That is, this code (note there's also one more print statement than the code above):

package main

import (
"fmt"
"log"

"golang.org/x/exp/shiny/driver"
"golang.org/x/exp/shiny/screen"

)

func main() {
fmt.Println("Starting.")
driver.Main(func(s screen.Screen) {
fmt.Println("I'm printed; you can see me.")
w, err := s.NewWindow(&screen.NewWindowOptions{Title: "I Don't Return"})
if err != nil {
log.Fatal(err)
}
defer w.Release()
fmt.Println("You can see me too.")
return
})
fmt.Println("I'm not printed; alas, I can't be seen.")
}

produces this:

william@hardy% go run main.go
Starting.
I'm printed; you can see me.
You can see me too.
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x10 pc=0x40bab37]

goroutine 1 [running, locked to thread]:
golang.org/x/exp/shiny/driver/gldriver.preparedOpenGL(0x4555610, 0x463cd10, 0x1)
/Users/william/src/golang.org/x/exp/shiny/driver/gldriver/cocoa.go:91 +0xb7
golang.org/x/exp/shiny/driver/gldriver._cgoexpwrap_78313b9f6607_preparedOpenGL(0x4555610, 0x463cd10, 0x1)
golang.org/x/exp/shiny/driver/gldriver/_obj/_cgo_gotypes.go:345 +0x3f
golang.org/x/exp/shiny/driver/gldriver._Cfunc_startDriver()
golang.org/x/exp/shiny/driver/gldriver/_obj/_cgo_gotypes.go:305 +0x41
golang.org/x/exp/shiny/driver/gldriver.main(0x4108530, 0x0, 0xc420053f10)
/Users/william/src/golang.org/x/exp/shiny/driver/gldriver/cocoa.go:107 +0x51
golang.org/x/exp/shiny/driver/gldriver.Main(0x4108530)
/Users/william/src/golang.org/x/exp/shiny/driver/gldriver/gldriver.go:26 +0x2f
golang.org/x/exp/shiny/driver.main(0x4108530)
/Users/william/src/golang.org/x/exp/shiny/driver/driver_darwin.go:15 +0x2b
golang.org/x/exp/shiny/driver.Main(0x4108530)
/Users/william/src/golang.org/x/exp/shiny/driver/driver.go:24 +0x2b
main.main()
/Users/william/src/play/shiny/main.go:15 +0x7b
exit status 2
william@hardy%

I don't see any documentation to think that this should be expected. It may have something to do with lifecycle events not being called. I also had a very similar panic when calling Send on the window's Event.Deque from another goroutine (one that was started from within the closure passed to driver.Main). I figured that that goroutine was calling Send too soon, i.e., before certain lifecycle events were placed on the EventDeque, but I'm only guessing.

The panic doesn't occur if I add code to process the events from the window's EventDeque, but the original issue of driver.Main not returning still persists. That is, this code:

package main

import (
"fmt"
"image"
"log"

"golang.org/x/exp/shiny/driver"
"golang.org/x/exp/shiny/screen"
"golang.org/x/mobile/event/key"
"golang.org/x/mobile/event/lifecycle"
"golang.org/x/mobile/event/paint"
"golang.org/x/mobile/event/size"

)

func main() {
fmt.Println("Starting.")
driver.Main(func(s screen.Screen) {
fmt.Println("I'm printed; you can see me.")
w, err := s.NewWindow(&screen.NewWindowOptions{Title: "I Don't Return"})
if err != nil {
log.Fatal(err)
}
defer w.Release()

	var b screen.Buffer
	defer func() {
		if b != nil {
			b.Release()
		}
	}()

	fmt.Println("You can see me too.")
	for {
		switch e := w.NextEvent().(type) {
		case lifecycle.Event:
			if e.To == lifecycle.StageDead {
				return
			}
		case key.Event:
			if e.Direction == key.DirPress && e.Modifiers == key.ModMeta {
				switch e.Code {
				case key.CodeQ:
					return
				}
			}
		case paint.Event:
			w.Upload(image.Point{}, b, b.Bounds())
			w.Publish()
		case size.Event:
			if b != nil {
				b.Release()
			}
			b, err = s.NewBuffer(e.Size())
			if err != nil {
				log.Fatal(err)
			}
		case error:
			log.Print(e)
		}
	}
})
fmt.Println("I'm not printed; alas, I can't be seen.")

}

produces this:

william@hardy% go run main.go
Starting.
I'm printed; you can see me.
You can see me too.

and I just get my prompt back after either choosing the Quit menu option on the top menu bar or hitting Command-Q. That is, the last print statement never gets called.

I understand that shiny is experimental and also that development has somewhat paused recently. However, I still think it's worthwhile submitting this report.

Please let me know if I can be of any help.

Thanks,
William

@ianlancetaylor ianlancetaylor changed the title shiny driver.Main not returning (and also can be made to panic unexpectedly) x/exp/shiny: driver.Main not returning (and also can be made to panic unexpectedly) Sep 7, 2017

@ianlancetaylor

This comment has been minimized.

Copy link
Contributor

commented Sep 7, 2017

@gopherbot gopherbot added this to the Unreleased milestone Sep 7, 2017

@williamhanisch

This comment has been minimized.

Copy link
Author

commented Sep 7, 2017

This may be a clue. I added a defer statement at the start of the closure passed to driver.Main (let's call this closure f) like so:

package main

import (
"fmt"
"image"
"log"

"golang.org/x/exp/shiny/driver"
"golang.org/x/exp/shiny/screen"
"golang.org/x/mobile/event/key"
"golang.org/x/mobile/event/lifecycle"
"golang.org/x/mobile/event/paint"
"golang.org/x/mobile/event/size"

)

func main() {
fmt.Println("Starting.")
driver.Main(func(s screen.Screen) {
defer func() {
fmt.Println("I'm seen!")
}()

	fmt.Println("I'm printed; you can see me.")
	w, err := s.NewWindow(&screen.NewWindowOptions{Title: "I Don't Return"})
	if err != nil {
		log.Fatal(err)
	}
	defer w.Release()

	var b screen.Buffer
	defer func() {
		if b != nil {
			b.Release()
		}
	}()

	fmt.Println("You can see me too.")

	for {
		switch e := w.NextEvent().(type) {
		case lifecycle.Event:
			if e.To == lifecycle.StageDead {
				return
			}
		case key.Event:
			if e.Direction == key.DirPress && e.Modifiers == key.ModMeta {
				switch e.Code {
				case key.CodeQ:
					return
				}
			}
		case paint.Event:
			w.Upload(image.Point{}, b, b.Bounds())
			w.Publish()
		case size.Event:
			if b != nil {
				b.Release()
			}
			b, err = s.NewBuffer(e.Size())
			if err != nil {
				log.Fatal(err)
			}
		case error:
			log.Print(e)
		}
	}
})
fmt.Println("I'm not printed; alas, I can't be seen.")

}

However, "I'm seen!" is not printed after quitting. A bug? the same one as above? a different one? If the the latter, related?

Yet, if I move those three lines from the beginning of f to after the window is created like so:

package main

import (
"fmt"
"image"
"log"

"golang.org/x/exp/shiny/driver"
"golang.org/x/exp/shiny/screen"
"golang.org/x/mobile/event/key"
"golang.org/x/mobile/event/lifecycle"
"golang.org/x/mobile/event/paint"
"golang.org/x/mobile/event/size"

)

func main() {
fmt.Println("Starting.")
driver.Main(func(s screen.Screen) {
fmt.Println("I'm printed; you can see me.")
w, err := s.NewWindow(&screen.NewWindowOptions{Title: "I Don't Return"})
if err != nil {
log.Fatal(err)
}
defer w.Release()

	defer func() {
		fmt.Println("I'm seen!")
	}()

	var b screen.Buffer
	defer func() {
		if b != nil {
			b.Release()
		}
	}()

	fmt.Println("You can see me too.")

	for {
		switch e := w.NextEvent().(type) {
		case lifecycle.Event:
			if e.To == lifecycle.StageDead {
				return
			}
		case key.Event:
			if e.Direction == key.DirPress && e.Modifiers == key.ModMeta {
				switch e.Code {
				case key.CodeQ:
					return
				}
			}
		case paint.Event:
			w.Upload(image.Point{}, b, b.Bounds())
			w.Publish()
		case size.Event:
			if b != nil {
				b.Release()
			}
			b, err = s.NewBuffer(e.Size())
			if err != nil {
				log.Fatal(err)
			}
		case error:
			log.Print(e)
		}
	}
})
fmt.Println("I'm not printed; alas, I can't be seen.")

}

Then the deferred closure is called. That is, "I'm seen!" is printed.

It's as if adding a defer in f before screen.NewWindow is called somehow gets deactivated, yet calling defer after works.

I'm guessing this behavior is caused by the same bug as the one above, or at any rate, one closely related to it.

@nigeltao

This comment has been minimized.

Copy link
Contributor

commented Sep 7, 2017

Yes, sounds like a bug, but as you already know, I don't really have much spare time to look at it right now.

@driusan

This comment has been minimized.

Copy link

commented Sep 8, 2017

For what it's worth, the bug seems to be specific to the MacOS cocoa driver. I just tried and the first minimal example printed the final statement under the x11driver in both DragonFly and Linux, and also under the gldriver with Linux.

@alexbrainman

This comment has been minimized.

Copy link
Member

commented Sep 8, 2017

This works on Windows too:

C:\Users\Alex\dev\src\a>type main.go
package main

import (
"fmt"

"golang.org/x/exp/shiny/driver"
"golang.org/x/exp/shiny/screen"
)

func main() {
fmt.Println("Starting.")
driver.Main(func(s screen.Screen) {
fmt.Println("I'm printed; you can see me.")
return
})
fmt.Println("I'm not printed; alas, I can't be seen.")
}
C:\Users\Alex\dev\src\a>go run main.go
Starting.
I'm printed; you can see me.
I'm not printed; alas, I can't be seen.

C:\Users\Alex\dev\src\a>

If someone knows how to fix this, I am sure there are enough interested people to review the fix and submit the change.

Alex

@williamhanisch

This comment has been minimized.

Copy link
Author

commented Sep 8, 2017

I'm going to study the gldriver and cocoa code and see if I can find anything. By the way, Dave and Alex, does the defer code (see my second post on this thread above) work properly on those other systems as well?

@alexbrainman

This comment has been minimized.

Copy link
Member

commented Sep 8, 2017

does the defer code (see my second post on this thread above) work properly on those other systems as well?

I am not certain I did what you asked, but:

C:\Users\Alex\dev\src\a>type main.go
package main

import (
"fmt"
"image"
"log"

"golang.org/x/exp/shiny/driver"
"golang.org/x/exp/shiny/screen"
"golang.org/x/mobile/event/key"
"golang.org/x/mobile/event/lifecycle"
"golang.org/x/mobile/event/paint"
"golang.org/x/mobile/event/size"
)

func main() {
fmt.Println("Starting.")
driver.Main(func(s screen.Screen) {
defer func() {
fmt.Println("I'm seen!")
}()

        fmt.Println("I'm printed; you can see me.")
        w, err := s.NewWindow(&screen.NewWindowOptions{Title: "I Don't Return"})
        if err != nil {
                log.Fatal(err)
        }
        defer w.Release()

        var b screen.Buffer
        defer func() {
                if b != nil {
                        b.Release()
                }
        }()

        fmt.Println("You can see me too.")

        for {
                switch e := w.NextEvent().(type) {
                case lifecycle.Event:
                        if e.To == lifecycle.StageDead {
                                return
                        }
                case key.Event:
                        if e.Direction == key.DirPress && e.Modifiers == key.ModMeta {
                                switch e.Code {
                                case key.CodeQ:
                                        return
                                }
                        }
                case paint.Event:
                        w.Upload(image.Point{}, b, b.Bounds())
                        w.Publish()
                case size.Event:
                        if b != nil {
                                b.Release()
                        }
                        b, err = s.NewBuffer(e.Size())
                        if err != nil {
                                log.Fatal(err)
                        }
                case error:
                        log.Print(e)
                }
        }
})
fmt.Println("I'm not printed; alas, I can't be seen.")
}
C:\Users\Alex\dev\src\a>go run main.go
Starting.
I'm printed; you can see me.
You can see me too.
I'm seen!
I'm not printed; alas, I can't be seen.

C:\Users\Alex\dev\src\a>

As I run main.go, the window opens and I close it and then program exists.

Alex

@williamhanisch

This comment has been minimized.

Copy link
Author

commented Sep 8, 2017

Thanks Alex! It looks like it works properly on Windows. I assume the last two lines:

I'm seen!
I'm not printed; alas, I can't be seen.

were printed after you close the window.

I just thought of something else. If it's not too much trouble, could you try on Windows the version which panics on macOS? I'll copy the code again here for easy access:

package main

import (
"fmt"
"log"

"golang.org/x/exp/shiny/driver"
"golang.org/x/exp/shiny/screen"
)

func main() {
fmt.Println("Starting.")
driver.Main(func(s screen.Screen) {
fmt.Println("I'm printed; you can see me.")
w, err := s.NewWindow(&screen.NewWindowOptions{Title: "I Don't Return"})
if err != nil {
log.Fatal(err)
}
defer w.Release()
fmt.Println("You can see me too.")
return
})
fmt.Println("I'm not printed; alas, I can't be seen.")
}

Thanks!
William

@alexbrainman

This comment has been minimized.

Copy link
Member

commented Sep 8, 2017

I assume the last two lines:

I'm seen!
I'm not printed; alas, I can't be seen.

were printed after you close the window.

They were.

Alex

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
6 participants
You can’t perform that action at this time.