Skip to content

syscall: fallback to process.exit when available #710

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
wants to merge 2 commits into from

Conversation

Lekensteyn
Copy link

@Lekensteyn Lekensteyn commented Oct 12, 2017

"go test" currently always fails with "fatal error: all goroutines are
asleep - deadlock!". In fact, all programs that use "os.Exit" will fail
with this.

It turns out that the current "runtime.Goexit" function throws an
exception which propagates up which violates the expected behavior of
the exit syscall (which should not return). Therefore call
"process.exit" when available.

Fixes #626


Test with go test on any package or just:

package main

import "os"

func main() {
	os.Exit(0)
}

Also note that any functions registered with process.on("exit", ...) will still be executed. Not ideal, but this is better than nothing when syscalls are not available.

@theclapp
Copy link

Do we care about the issue mentioned here, which concludes

If you can you should set the exit code and allow Node to exit gracefully:

process.exitCode = 1;

@Lekensteyn
Copy link
Author

Nope we do not, we want to exit immediately and not just set the status code. See https://nodejs.org/api/process.html#process_process_exit_code

Btw, this issue was also known before as #395, #417, #531, #624 and also as (still open) #342.

@flimzy
Copy link
Member

flimzy commented Oct 12, 2017

Nope we do not,

Can you elaborate? Why do we not care? This seems to have the potential to break expected behavior, if stdout isn't flushed before exit.

@dmitshur
Copy link
Member

dmitshur commented Oct 12, 2017

This seems to have the potential to break expected behavior, if stdout isn't flushed before exit.

@flimzy, os.Exit exits the process immediately as well, it doesn't wait for buffers to flush or anything. That's the expected behavior.

@dmitshur
Copy link
Member

Thanks for this @Lekensteyn. At a high level, this makes sense.

@@ -67,6 +67,10 @@ func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno) {
return uintptr(array.Length()), 0, 0
}
if trap == SYS_EXIT {
process := js.Global.Get("process")
if process != js.Undefined {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style-wise, this is a good time to inline process := js.Global.Get("process") into the if statement:

if process := js.Global.Get("process"); process != js.Undefined {
	process.Call("exit", a1)
}

process is only needed inside the if statement, so it's good to give it a smaller scope.

(/cc @gmlewis FYI.)

@flimzy
Copy link
Member

flimzy commented Oct 12, 2017

Does output to stdout not normally block in Go?

@dmitshur
Copy link
Member

I think it does.

Are you referring to how writing to os.Stdout in GopherJS includes some internal buffering for browsers, so that a single line of output maps to a single line in browser console, and we might need to take that into account before exiting?

@flimzy
Copy link
Member

flimzy commented Oct 12, 2017

My concern, without any attempt to test this, is that something like this might lead to lost output, particularly in tests running in Node.js (which I think is only place this patch matters, right?

fmt.Println("something")
t.FailNow()

If my understanding of the SO link above, plus Go/GopherJS is correct, the Println would be translated to an async nidejs syscall, but GopherJS would treat it as if it were a completed blocking operation. So it the Node.js call may not actually complete before the program exists.

It's certainly quite possible that I'm wrong about one or more of the pieces to this puzzle, though. In that case, please accept my apologies for wasting time. :)

"go test" currently always fails with "fatal error: all goroutines are
asleep - deadlock!". In fact, all programs that use "os.Exit" will fail
with this.

It turns out that the current "runtime.Goexit" function throws an
exception which propagates up which violates the expected behavior of
the exit syscall (which should not return). Therefore call
"process.exit" when available.

Fixes gopherjs#626
printToConsole does some internal line buffering (not even involving the
stdio layer) which is only flushed on normal exit via $flushConsole.
When using process.exit, this is not called though. Therefore avoid the
internal buffering, and use Node's stdio directly.
@Lekensteyn Lekensteyn force-pushed the syscall-exit-fallback branch from b91438e to 3416b8f Compare October 13, 2017 13:13
@Lekensteyn
Copy link
Author

Lekensteyn commented Oct 13, 2017

@flimzy Your test (or a main function which calls fmt.Println before os.Exit) does seem to print stuff correctly here. However fmt.Print("x") does not. Tested with node 8.6.0. By the time syscall(SYS_EXIT) gets executed, it is probably already too late to flush buffers (if any).

fmt.Print("x") did not work because printToConsole is doing (unconditional) line buffering. Fine for a browser, not so nice though for node. Two options: use process.stdout.write directly or invoke the internal $flushConsole call before exit.

I have just pushed one that implements the former, WDYT? The generated code is a bit ugly though,

buffer = $ifaceNil;
bufferFrom = $global.Buffer.from;
if (!(bufferFrom === undefined)) {
	buffer = new $jsObjectPtr(bufferFrom(array));
} else {
	buffer = new $jsObjectPtr(new ($global.Buffer)(array));
}
if (a1 === 1) {
	process.stdout.write($externalize(buffer, $emptyInterface));
} else {
	process.stderr.write($externalize(buffer, $emptyInterface));
}

it could also have been written as:

if (Buffer.from) {
    buffer = Buffer.from(array);
} else {
    buffer = new Buffer(array);
}
if (a1 === 1) {
    process.stdout.write(buffer);
} else {
    process.stderr.write(buffer);
}

Relevant other resources:

@flimzy
Copy link
Member

flimzy commented Oct 13, 2017

@Lekensteyn Thanks for testing that.

Your approach of using process.stdout.write makes sense to me.

@dmitshur
Copy link
Member

dmitshur commented Oct 15, 2017

We should take this documented behavior into account:

In the browser, calling os.Exit (e.g. indirectly by log.Fatal) also does not terminate the execution of the program. For convenience, it calls runtime.Goexit to immediately terminate the calling goroutine.

(Source: https://github.com/gopherjs/gopherjs#application-lifecycle.)

Do we want to change that? It might break existing programs. There might've been a good reason for making that design decision, we should remember what it was.

(Or does it not apply because this change is affects node behavior only?)

@Lekensteyn
Copy link
Author

The rationale for the documented behavior for browsers is noted in #229 (comment). It does not apply to node. At least, not when you run the script directly, I don't think that importing a gopherjs program as package is supported, is it?

Also note that with the syscalls package installed, os.Exit also does not return.

@Lekensteyn
Copy link
Author

Ping

@dmitshur
Copy link
Member

dmitshur commented Nov 2, 2017

Thanks for the ping, and sorry about the delay. I will try to find some time to work on this.

I still need to get a better understanding of the change and gain confidence that it doesn't have any unexpected unintended side-effects.

@Lekensteyn
Copy link
Author

Poke :-)

@Lekensteyn
Copy link
Author

Any updates on this one?

@flimzy
Copy link
Member

flimzy commented Jun 14, 2023

Is any of this still relevant after the syscall rewwork

cc/ @nevkontakte

@nevkontakte
Copy link
Member

I think this is obsolete as of #1111.

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

Successfully merging this pull request may close these issues.

Document that gopherjs test requires system calls Node module (or make it not require it, if possible).
5 participants