Caliburn.Micro
Caliburn.Micro copied to clipboard
[UWP] Stack overflow on main UI thread
Overview:
While using the library, we've encountered a stack overflow exception on the main UI thread. Prior to the exception, we observed degrading performance over the course of 5 minutes on average. Performance eventually degraded to the point that appeared the application had hung. Eventually the stack overflow exception will occur. We've observed that the performance begins to degrade when our application opens a new WinRTContainer, which we refer to as a claim. If the claim is closed at any point prior to the crash, the performance of the application will return to the normal, functioning state.
A description of the workarounds we've discovered, along with the actions that lead to the performance degradation, follow the remainder of the stack overflow problem description.
Stack Overflow:
In the cases where we've encountered the stack overflow exception, we've observed the stack overflow occurred on the thread responsible for handling UI events. This stack was more than 11,000 method calls deep prior to the exception occurring. Within the stack, we noticed that the application init wasn't invoked until 45 lines from the top of the stack.
Client.Program.Main(System.String[])), calling 00007ff987e403b0 (stub for Windows.UI.Xaml.Application.Start(Windows.UI.Xaml.ApplicationInitializationCallback))
At the bottom of the stack we see the thread start and UI events setup. Then we notice this 124 lines from the start of the thread:
000000af9e6fd700 00007ff988958415 (MethodDesc 00007ff988c5e428 +0x65 Caliburn.Micro.ActionMessage+<>c.<.cctor>b__34_0(Caliburn.Micro.ActionExecutionContext)) 000000af9e6fd720 00007ff988955ae9 (MethodDesc 00007ff988c1ea00 +0x29 Caliburn.Micro.ActionExecutionContext.get_View()), calling coreclr!JIT_IsInstanceOfClass 000000af9e6fd750 00007ff988958366 (MethodDesc 00007ff988c5e090 +0x346 Caliburn.Micro.ActionMessage.Invoke(System.Object)) 000000af9e6fd800 00007ff988957ff1 (MethodDesc 00007ff988c5dd10 +0x61 Caliburn.Micro.TriggerAction`1[[System.__Canon, mscorlib]].Execute(System.Object, System.Object)) 000000af9e6fd840 00007ff988957ed6 (MethodDesc 00007ff988096770 +0xd6 Microsoft.Xaml.Interactivity.Interaction.ExecuteActions(System.Object, Microsoft.Xaml.Interactivity.ActionCollection, System.Object)) 000000af9e6fd870 00007ff988957dc2 (MethodDesc 00007ff988c5ced0 +0x12 Microsoft.Xaml.Interactions.Core.EventTriggerBehavior.OnEvent(System.Object, System.Object)), calling (MethodDesc 00007ff988c5ce30 +0 Microsoft.Xaml.Interactions.Core.EventTriggerBehavior.get_Actions())
We see that Caliburn.Micro begins executing and running static initializers. This is very near the bottom of the stack immediately after starting the thread and setting up the window events.
We see this recurring pattern throughout the remainder of the stack prior to the app init eveer being called:
00000069ac8b8690 00007ff98e9b4ff5 (MethodDesc 00007ff98ec89088 +0x65 Caliburn.Micro.ActionMessage+<>c.<.cctor>b__34_0(Caliburn.Micro.ActionExecutionContext)) 00000069ac8b86b0 00007ff98e9b2f49 (MethodDesc 00007ff98ec58f48 +0x29 Caliburn.Micro.ActionExecutionContext.get_View()), calling coreclr!JIT_IsInstanceOfClass 00000069ac8b86e0 00007ff98e9b4b36 (MethodDesc 00007ff98ec88cf0 +0x346 Caliburn.Micro.ActionMessage.Invoke(System.Object))
We eventually begin to JIT and need to do a garbage collection. During the garbage collection we also noticed that the Client.Program.Main is invoked 45 lines prior to the stack overflow.
Re-entry:
In addition, we are seeing two threads with Program.Main called. The initial entry is on thread 0 and expected. However, thread 27 also has a Program.Main called very late in the stack and this thread is where the stack overflow occurs.
Workarounds:
We've also observed another method which will restore the performance when the application begins to suffer. This approach does not require us to close the 'claim', but simply move our application to the background. If we switch to another application and then return to the app, we notice performance returns to normal.
Stack Overflow File: stackOverflow.txt
Has anyone experienced a similar issue?
Thanks for the report, this is certainly a new one, can you go into a bit more detail about the process you use to "open a claim", other than creating a new WinRTContainer
?
In the attached file, I have put supporting files that are pertinent to showing how we "open a claim". The ClaimManagerViewModel has a "WorkClaim" method. The ClaimMasterViewModel is a simplified version of our production issue but I hope it's enough to move forward.
Thanks, haven't had a chance to dig into in detail yet, I couldn't see where you were creating a new WinRTContainer
?
My best guess at the moment is something in the secondary window service and trying to get things on the right UI thread.
Do you have background threads updating the view model in either the main or claim views?
Yes, our secondary window view models have asynchronous calls to calculate values and update the view model on the secondary windows.
So there's on major limitation in Caliburn.Micro when it comes to multiple views.
Both PropertyChangedBase
and BindableCollection<T>
attempt to marshal their property and collection changed events on to the UI thread through Execute.OnUIThread
. It does this using the dispatcher.
The problem is that when there's multiple views in UWP there are multiple UI threads and multiple dispatchers with no real way to determine which is the current one.
We do try to keep it up to date with the current 'active window using [
CaliburnApplication`](https://github.com/Caliburn-Micro/Caliburn.Micro/blob/master/src/Caliburn.Micro.Platform/win8/CaliburnApplication.cs#L117), but this more for Windows 8 rather than 10. Given your second work around I suspect it's this is what's helping here. I don't know of any way to determine which is the correct UI thread for a given view model.
Execute
does check that if we're already on a UI thread we don't try and move to another, so if you can manually keep view model updates to the correct UI thread then you shouldn't have any problems.
For your scenario it sounds like there's a problem here and it's potentially bouncing updates back and forth between UI threads
To help resolve the UI thread issues, what WinRT APIs are you calling from Execute.OnUIThread to marshal back to the UI thread?
No WinRT API's directly, just the property and collection changed notifications.