Manage service render window from server main thread.
- closes async/paraview#157
The issue
The link above has a good summary of the problem. To be clear, the render service invoked certain Cocoa window system API which is only meant to be invoked from the main thread - corresponds to the main thread of the server(in a remote session) or client(built-in session). On macOS, all pv*
executables in built-in/remote sessions crashed because of a Cocoa API exception on the render service thread. A workaround would've been to avoid rs startup, use ds for geometry delivery, and a separate pvrenderserver
process or something similar for rendering. However, it is tricky right now because the rs-ds are started by default in the existing code. This approach may be possible once we've got a nice API that can start/connect to external services.
The fix
Re-route thread-sensitive API from the background thread onto the main thread by taking advantage of rxcpp
run loops. When a background thread knows it's about to execute a thread-sensitive block of code, it will enqueue that block of code - can be a lambda/function with args on the main thread's rxcpp
run loop. Since the background thread expects the result or completion of an action, it blocks until the main thread finishes the task. As we only do this for create/resize/context of a window, it is negligible. When proposing to move certain blocks of code on the main thread this way, the bar of entry is going to be really really high - only if the app crashes/memory corruption, etc; since we want to keep the main thread as free as possible.
Create/Resize window in Mac OS:
- A proxy property can now define a new XML attribute
run_on_main_thread
that lets us apply properties from the server's main thread instead of a background thread. - To that end,
vtkRemotingCoreUtilities::RunAsBlocking(func, coordination, args...)
was introduced. It can enqueue a function on the server's main thread or any other thread. This method waits for completion and returns the result of the function. We use this function for creating a render window as well. This is a C++-only method and must be used with great care. When invoked with a coordination corresponding to the caller's thread, it will deadlock. For now, it satisfies the purpose, we can extend it with future(s) as needed later on.
Render()
in Mac OS:
- With those two changes, the render service thread no longer crashed during Create/Resize window on macOS, however,
vtkCocoaRenderWindow
would call certain Cocoa API during every render from thevtkOpenGLRenderWindow::Start()
method. As a result, all executables crash just after creating the server window during the first render. To remedy this problem, a new factory override is set up forvtkRenderWindow
on Apple MacOSX. It subclassesvtkCocoaRenderWindow
, and overrides thevtkCocoaRenderWindow::Start
method such that a non-main thread enqueues sensitive superclass API on the server's main thread.
VTK override
- This MR also contains an initial attempt at overriding vtk-master classes. Some of these ideas can be used when we sync up with VTK master notes