Skip to content

Proposal: emit an event when main package has exited #383

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

Open
flimzy opened this issue Jan 27, 2016 · 5 comments
Open

Proposal: emit an event when main package has exited #383

flimzy opened this issue Jan 27, 2016 · 5 comments
Labels

Comments

@flimzy
Copy link
Member

flimzy commented Jan 27, 2016

The problem: I'm working on running tests in the browser, and have a proof of concept working to run a good portion of the GopherJS tests in a browser. I'm running each test file in an iframe, and capturing the output to console and displaying it in the parent window's DOM. The big weakness at the moment is that there's no way to tell when a test has completed. For a single test, this is a mild annoyance in the browser, as I must continually check for new output to the console, until the user closes the page. In an automated test environment (i.e. PhantomJS) it's more debilitating, leaving me to fall back to a "reasonable" timeout (if no console updates for N seconds, consider the test run complete).

It's quite a problem when running multiple tests (as in the entire GopherJS test suite) in the browser. I must either wait a 'reasonable' time between tests, or risk locking the browser. Determining a reasonable time isn't easy; some tests run in < 100ms, others multiple tens of seconds.

Possible solutions: An easy solution, for tests I control, is to just to output 'Done.' after all my tests have completed, and I can watch for this output. This doesn't help when running third-party tests (as the GopherJS and upstream Go tests).

Another option might be to heuristically determine, based on standard Go test output format, when a test has completed. I don't think this is realistic, given that the number of tests to run isn't known before hand, and it would break when using other test output formats, which certainly do exist.

So this leaves me trying to divine the state of the GopherJS process, and has lead me to three possible options. I'm hopeful that one of you guys will have a better idea that doesn't require changing GopherJS at all.

  1. Trigger an event after the main package returns:

     var $mainPkg = $packages["main"];                                                                                                                                                                                               
     $packages["runtime"].$init();
     $go($mainPkg.$init, [], true);
    +$global.dispatchEvent("mainFinished");
     $flushConsole();
    
     }).call(this);
    
  2. If defined, execute a user-defined function after the main package exits (an event seems cleaner and more idiomatic, though):

     var $mainPkg = $packages["main"];                                                                                                                                                                                               
     $packages["runtime"].$init();
     $go($mainPkg.$init, [], true);
    +if ( $global.mainFinished ) {
    +    $global.mainFinished();
    +}
     $flushConsole();
    
     }).call(this);
    
  3. Expose some internal state through a global variable/function.

     var $dummyGoroutine = { asleep: false, exit: false, deferStack: [], panicStack: [], canBlock: false };
     var $curGoroutine = $dummyGoroutine, $totalGoroutines = 0, $awakeGoroutines = 0, $checkForDeadlock = true;
     var $mainFinished = false;
    +$global.gopherjsMainFinished = function() {
    +    return $mainFinished;
    +};
     var $go = function(fun, args, direct) {
     $totalGoroutines++;
     $awakeGoroutines++;
    

Any thoughts, or better suggestions?

@dmitshur
Copy link
Member

Can TestMain be used to assist with this?

@flimzy
Copy link
Member Author

flimzy commented Jan 27, 2016

That can work when I am writing the tests. But when wrapping third-party tests, I don't have that level of control.

@flimzy
Copy link
Member Author

flimzy commented Feb 3, 2016

I have put up a prototype of something like what I have in mind here: http://aslan.flimzy.com/~jonhall/tests/ (warning: it will likely hang your browser tab for several tens of seconds on certain tests).

It uses an ugly hack to determine when a Go program finishes, by applying this change to the output of gopherjs test -c:

perl -p0e 's/(};\s*?var \$schedule\s?=\s?function)/if(\$scheduled.length==0){parent.postMessage({id:frameElement.id,end:performance.now()},"*");}$1/s'

In addition, I have a custom error handler to catch any unhandled errors (and treating them as program termination).

I don't know if others feel that running arbitrary tests this way is especially valuable.

For my goals I'd like to be able to run my own tests, and the tests of any library dependencies, in such a manner, for all of my target browsers.

For this to be maximally useful, a couple of other hooks would really be necessary, too. It would be ideal to be able to pass 'command line arguments' to these tests ('-test.short' for instance), and returning the exit status would also be nice.

I believe this functionality belongs outside of GopherJS itself, but hooking into the GopherJS runtime is essential, so finding a clean, future-proofish way of doing that is my goal in this issue.

@nevkontakte
Copy link
Member

Copying @broady from #525:

It might sound strange, but I'd like some way to hook into mainFinished - kind of as a finalizer.

The Node.js runtime can block program termination with object refs. See net.Socket.ref/net.Socket.unref for example. This is useful for shutting down keep-alive connections.

I'd like to call "unref" on mainFinished to close down any sockets when all of the Go code has finished executing. I can explain in more detail if it still doesn't make sense.

runtime.AddProgramFinalizer(func()) runtime.OnMainFinished(func())

Essentially:

$mainFinished = true;
for (var i = 0; i < $mainFinishers.length; i++) {
  $mainFinishers[i]();
}

@nevkontakte
Copy link
Member

I think emitting a custom event (option #1) is a reasonable solution here. In my opinion it has several advantages:

  • Both Go and external JavaScript code can receive such event.
  • If not used, its runtime cost is near-zero.
  • This is clearly a GopherJS-specific feature and not something available in Go, so implementing it as an API in a common Go package (e.g. runtime) will be highly confusing.
  • It is very easy to implement :)

Side note, I looked is something similar is implemented in Go-wasm, but found nothing that would be exposed in a public interface. That is, outside of the async Go.run() function, which doesn't have an analog in GopherJS.

As a possible extension to the proposal we could emit an event when all goroutines have exited. This may be a bit tricker, since a callback can potentially create new goroutines long after all original ones exited, but it may be useful nevertheless.

@nevkontakte nevkontakte changed the title Feature request / How to detect when main package has exited Proposal: emit an event when main package has exited Oct 27, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants