cfyzium / bearlibterminal

Interface library for applications with text-based console-like output
Other
119 stars 18 forks source link

Go bindings hang with [fatal] 'refresh' was not called from the main thread. #2

Closed bilebucket closed 4 years ago

bilebucket commented 4 years ago

I've created a simple example utilizing the go bindings Gist.

Initially everything works as it should, but after some time (even without providing any keyboard input) the program hangs with [fatal] 'refresh' was not called from the main thread.

Calling blt.Delay in main loop seems to make the hang less likely, but it still occurs after some time.

I've yet to wrap my head around bealibterminals source to fully understand the reason for the hang but I've identified the line causing the hang.

cfyzium commented 4 years ago

The problem is that window and rendering functions are required to be called from the main thread (sometimes it works if it is at least the same thread throughout the program execution). Since goroutines are not permanently tied to system threads by default, the foreign library calls may happen from any thread and even concurrently.

Someone needs to do the synchronization, making sure that at least all rendering and event processing functions (Set, Refresh, HasInput, Read, Peek, Delay) are called from the main thread. Ideally, all functions should be called from the same main thread because concurrent invocation of modification functions like Put or Print will mess with non-thread-safe internal state and crash the program.

It might be possible to isolate most of it in the library itself but 1) it has not been done in this version, and 2) it will introduce an overhead because the library will not know about the logical scope and will have to synchronize everything to the smallest bits.

It seems that in Go you need to use the LockOSThread function in the init function to bind the main goroutine to the main system thread and then run all the foreign library related code from that main goroutine.

The situation is not exactly unique to Go, and is usually solved either by running the whole program from the single thread (easiest but not quite Go way) or setting up a mechanism for executing pieces of code on a particular thread. In may be done by setting up a communication channel with the main thread and passing the library calls wrapped in lambdas/function objects to execute there in some loop on the main thread.

bilebucket commented 4 years ago

Indeed calling runtime.LockOS seems to be the solution so I'm closing this issue for now. Thank you for the speedy and detailed response!