esimov/ascii-fluid

Uncaught Error: Go program has already exited

EtoDemerzel0427 opened this issue · 16 comments

I used the newest version of ascii-fluid, and it seems after a certain time period, the rendered video would suddenly stuck, and the browser would throw this error:
"Uncaught Error: Go program has already exited
at global.Go._resume (wasm_exec.js:576)
at wasm_exec.js:589".

Also this: "panic: JavaScript error: Failed to execute 'getImageData' on 'CanvasRenderingContext2D': Out of memory at ImageData creation". Seems like some memory leakage? We should delete some object? Sorry I don't know about JS at all.

BTW, I wonder why not just transfer data using the existing WebSocket but create another TCP connection?

Can you share more insight about your system? Does it happens occasionally or frequently? After what running period happened to crash?

Regarding your question:

BTW, I wonder why not just transfer data using the existing WebSocket but create another TCP connection?

Websocket couldn't run out of the web browser, that's why it's needed to transfer the data to another terminal instance and the most appropriate way to make it is to spawn a new TCP connection.

I ran on MacOS Big Sur 11.5.2, Go 1.16.5. The crash can be reproduced on Microsoft Edge, Google Chrome and Apple Safari. Usually happens after about one minute. Safari will warn that this webpage consumes too large memory after open the webpage for a while. I also took a look at the activity monitor, the memory of this single webpage grows from 2.51 GB to 6.08 GB in one minute, and then it crashes.

After the first round of investigation it looks like the GC is not freeing up the memory at a faster interval. It frees up in chunks, but only after the memory consumption reach a quite significant level. My assumption is that it's related to this syscall/js js.CopyBytesToGo function. If I go further with my investigation and check how this function is implemented will get the explanation: this function invokes runtime.KeepAlive(src) which prevents of freeing up the garbage collection when the variable is going out of scope.

I will investigate what can be done to prevent this happening.

Thanks, I just realized the same codebase is also reused in many of your other projects, (for example, this one), does this problem occur there too?

Probably yes, so this problem should be address there too, just I need to find a solution.

I've tried a few different approaches none of them worked. I'm pretty confident that the issue is related to the js.CopyBytesToGo function. I've opened an issue for this reason: golang/go#47956. In the meantime I will check what else could I optimize.

Thanks, I also think it is the js.CopyBytesToGo's problem, but the runtime.KeepAlive looks innocent to me after checking the runtime package documentation. Hopefully the Golang community will give us an answer.

I did some further investigations, which I summarized here: golang/go#47956 (comment)

The trick to invoke the code section I mentioned in the comment above should work, but will still cause the increased memory consumption issue after certain time when no faces are detected. So this trick is effective only when a face is detected. I've tried different approaches like reset the variables to their default values in case a face detection is not performed, but none of them worked.

Thank you, and I just replied to your issue under the golang repo. It seems the solution can be quite simple, please kindly verify if that works.

Can you point out on which example the variable has been declared inside the rendering function?

image

Line 80 and 86 are my only changes in ascii-fluid/wasm/canvas/canvas.go. And it works.

You mentioned what has been stated below, that's why I'm asking where did you found that it was declared inside the js.FuncOf?

@esimov I spot the difference between these two versions: In https://github.com/esimov/ascii-fluid/blob/master/wasm/canvas/canvas.go#L78, the data is declared outside the js.FuncOf, while in the example of pigo-wasm it is inside the js.FuncOf. There might be some tricky parts here to use a global go variable for js and maybe this causes the problem.

@EtoDemerzel0427 that was the solution. In fact I've done some small adjustments on the code, in the sense that I kept the variable defined outside of the main thread just for not allocating a new memory address on each run, but I decided to reset the slice to its default (zero) values after each iteration, but the essence remained the same: looks like the GC recognized this operation as an addition to the existing slice and for this reason it didn't cleaned it up, however my assumption is that normally it should.

Edit: I did another test and seems like behind the scene the js.CopyBytesToGo function expands the slice capacity on each invocation. Not sure if that's how it was intended or not, but normally it shouldn't since the data slice is defined with a specific capacity.

Anyway thanks for your support and for rising this problem.

Good to know this. I believe you have tested it, so I did not. Go's WebAssembly support is still experimental, let's see how Golang team can improve on it. Thanks for your always immediate reply!