Skip to content

Goroutine runs forever #229

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

Closed
rusco opened this issue May 25, 2015 · 16 comments
Closed

Goroutine runs forever #229

rusco opened this issue May 25, 2015 · 16 comments

Comments

@rusco
Copy link
Member

rusco commented May 25, 2015

http://play.golang.org/p/xn99smXFsK

Compare the behaviour in go vs. gopherjs: After the time.Sleep() interval the "debounce" goroutine stops in go but not in gopherjs

@dmitshur
Copy link
Member

I can reproduce.

http://www.gopherjs.org/playground/#/xn99smXFsK is the GopherJS playground link.

I'm guessing that Go kills all running goroutines when main() returns, but GopherJS may not be doing that.

@dmitshur
Copy link
Member

For reference, as a workaround, if you modify the code to manually wind down the goroutines when main() returns, it behaves as expected. E.g., see http://www.gopherjs.org/playground/#/pWLkbHaHOa.

@rusco
Copy link
Member Author

rusco commented May 25, 2015

Good point, I will do this for now as a workaround !

@dominikh
Copy link
Member

I'm guessing that Go kills all running goroutines when main() returns

The program terminates when main returns. Goroutines aren't explicitly killed, it's a side effect of, well, the program not running anymore.

With GopherJS in the browser, the "application" terminates when the page is closed. I'm not sure if GopherJS should shut itself down when main returns.

@dmitshur
Copy link
Member

Those are very good points. It may make sense to keep current behavior as WAI.

Consider a script that does something like:

func main() {
    item.AddEventListener("click", false, func(event dom.Event) {
        // Do something on every click.
    })
}

Wouldn't it make sense for the click callback handler continue to work even after main() returns?

Or should we require users to place a select {} at the end of their main() funcs?

@rusco
Copy link
Member Author

rusco commented May 25, 2015

In my opinion GopherJS should follow (as until now) Go's behaviour as close as technically possible, otherwise we are entering a gray area which makes it hard to predict GopherJs created code, often copied from server side code (Isomorphism).

Who wants ever running code uses "for {} " or hooks up event listeners to the Dom, which indeed only get stopped on page leave.

@dmitshur
Copy link
Member

for {} would be a busy loop and cause 100% CPU utilization.

Since event listeners continue running after main() returns, they can execute more Go code and spawn new goroutines, etc.

I agree that GopherJS should follow Go's behavior, and I think it's most consistent if goroutines and other potential code continues to work and run until program is terminated. In the case of OS X, etc., program is terminated when either main() returns or os.Exit() is called. In browser, program is terminated when page is closed.

What do you think?

@dominikh
Copy link
Member

Who wants ever running code uses "for {} " or hooks up event listeners to the Dom, which indeed only get stopped on page leave

What about the following piece of code?

func main() {
    ch := make(chan int)
    go func() {
        println(<-ch)
    }()
    item.AddEventListener("click", false, func(event dom.Event) {
        ch <- 1
    })
}

When you say that hooks still work even after main returns, you're already deviating from "normal" Go, where the program terminates, so might as well keep goroutines running.

Our use of Go is more like the planned shared library support for Go. In fact, https://docs.google.com/document/d/1tU_yNmwu1raKZm9atHJ8THiQizkYTKykdWraIb15TSE/edit#heading=h.qnind36h4b6s discusses this exact issue (the Initialization/main.main section). One of the possible solutions they mention matches our current behaviour. Most of the other solutions that would also work for us just don't call it main, but either use init or an entirely new function instead.

The lifetime of goroutines should be bound to the lifetime of the "application", which in our case is the page loaded in a browser.

@dmitshur
Copy link
Member

I'm in agreement with @dominikh on this.

The lifetime of goroutines should be bound to the lifetime of the "application", which in our case is the page loaded in a browser.

This appears to be the only consistent solution (and simplest).

@rusco
Copy link
Member Author

rusco commented May 25, 2015

Hi @dominikh , without the "for {}" loop your channel receives only once, was this by intention ?

@shurcooL : you are right, I meant putting the for loop in a goroutine, see further down

I have no problem in understanding the following program: The receiving goroutine continues active since the click event gets hooked up to the dom and thereby the main loop continues running.
This is not the case in my playground example, no dom is involved.

This is at least my understanding for such a use case where the dom is involved, and from what I know Dart make the same assumptions. In my first playground example no dom is involved, so I need to know for what reasons gopherjs continues with active goroutines.

package main
//html page imports jquery.js and has a #someButtonId Button
import "github.com/gopherjs/gopherjs/js"
var jQuery = js.Global.Get("jQuery")

func main() {

    ch := make(chan int)
    go func() {
        for {
            print("prepared to receive...")
            println(<-ch)
        }
    }()
    jQuery.New("#someButtonId").Call("on", "click", func(this *js.Object) {
        print("sending now...")
        ch <- 1
    })
}

@dominikh
Copy link
Member

Hi @dominikh , without the "for {}" loop your channel receives only once, was this by intention ?

It doesn't make a difference, the sole purpose was to show a goroutine that needs to outlive main. Whether it needs to outlive it for one iteration or many doesn't matter.

the click event gets hooked up to the dom and thereby the main loop continues running

There's no "main loop", and no, main doesn't continue running. jquery.New().Call returns instantly. main returns afterwards. What lives on is a callback. If goroutines did terminate after main returns, the goroutine in your example would also terminate; main returns. In fact, GopherJS doesn't, and shouldn't have, an understanding of the DOM, or have any insight into what the jquery package is doing. Call might as well be defined as follows:

func Call(string, string, func(*js.Object)) {
  // Throw away everything. No DOM interaction, no use of the callback. Now, what happens to the goroutine?
}

@dominikh
Copy link
Member

Two more examples, one even without any DOM interaction.

package main

import "honnef.co/go/js/dom"

func main() {
    defer println("main returned")
    ch := make(chan string)
    go func() {
        println(<-ch)
    }()
    dom.GetWindow().Document().GetElementsByTagName("body")[0].AddEventListener("click", false, func(dom.Event) {
        ch <- "callback fired"
    })
}

// main returned
// callback fired

and

package main

import "github.com/gopherjs/gopherjs/js"

func main() {
    defer println("main returned")
    ch := make(chan string)
    go func() {
        println(<-ch)
    }()
    js.Global.Call("setTimeout", func() {
        ch <- "timeout"
    }, 100)
}

// main returned
// timeout

@rusco
Copy link
Member Author

rusco commented May 25, 2015

@dominikh 👍 Thanks for your explication, now I have a better understanding of GopherJs' implementation of goroutines

@neelance
Copy link
Member

@rusco You are right that the Go specification says Program execution begins by initializing the main package and then invoking the function main. When that function invocation returns, the program exits. It does not wait for other (non-main) goroutines to complete.

However, I think in most cases, e.g. the examples above, this is not the behaviour that you'll want in the browser environment. Maybe the best way to think of it is that your program's main method is not the topmost function of the application, but is called from the browser engine's code. That way, the end of the main function is not the end of the application, which may still invoke callbacks that you registered via the js package.

For deadlock detection (all goroutines asleep), I already need to look for callbacks. Deadlock detection gets simply disabled as soon as you pass a func to JS. I think this is fine for deadlock detection, since it is only there to display some more helpful error, but I'd rather not change the termination behaviour based on this.

@rusco
Copy link
Member Author

rusco commented May 26, 2015

Hi Richard,
thanks for the explication, I propose to document this deviation from standard Go somewhere. I put together some demos to investigate how Golangs Goroutines compare to RxJS (currently at http://rusco.github.io) and this was one of the pitfalls I felt into.

@neelance
Copy link
Member

@rusco Wow, this issue is really old. Sorry about that. I added some documentation on the lifecycle branch, could you take a look?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants