NativeDestruct + RemoveFromParent() = Crash on exit

,

The issue here is that you’re trying to manage the lifetime of Widget2 manually. When you call the CreateWidget method, you expose the Widget to the Garbage Collector and other systems. The problem when trying to reference other actors from destructors like this is that you’ll end up with dangling references until the GC is finished.

So, what was happening here was that Widget2 was being destroyed by the Garbage Collector, then Widget1 started to be destroyed. The pointer held in Widget1 (MyUserWidget) wasn’t properly zeroed out so it looked valid but it wasn’t.

You can completely avoid this situation by just letting UE manage the lifecycles of these objects. Another upside is that it’s less work for you too. In this specific case all you have to do is mark up UMyUserWidget::SecondUserWidgetPointer with UPROPERTY, and you can completely remove UMyUserWidget::NativeDestruct. This prevents the crash.

If you’re still not convinced, I added “PrintString” nodes to both FirstUserWidget and SecondUserWidget “Event Destruct” events. Here is a snippet from my log file before the fix:

LogWorld: Bringing World // Unimportant
LogWorld: Bringing up level for play took: 0.008848
// Unimportant line
PIE: Info Play in editor start time for // Unimportant
LogBlueprintUserMessages: Late PlayInEditor Detection: // Unimportant
LogBlueprintUserMessages: Early EndPlayMap Detection: // Unimportant
LogBlueprintUserMessages: [SecondUserWidget_C_0] Widget 2 Destroyed
LogCrashTracker:
// Woops, it crashed because we tried to access Widget 2 from Widget 1's Native Destruct!

Notice Widget 1 Destroyed is never observed. That’s because that event crashes part way through because we accessed the garbage memory for Widget 2. Here is a snippet after the fix:

LogWorld: Bringing World // Unimportant
LogWorld: Bringing up level for play took: 0.008988
// Unimportant line
PIE: Info Play in editor start time for // Unimportant
LogBlueprintUserMessages: Late PlayInEditor Detection: // Unimportant
LogBlueprintUserMessages: Early EndPlayMap Detection: // Unimportant
LogBlueprintUserMessages: [FirstUserWidget_C_0] Widget 1 Destroyed
LogBlueprintUserMessages: [SecondUserWidget_C_0] Widget 2 Destroyed
LogBlueprintUserMessages: Late EndPlayMap Detection: // Unimportant
// Exited successfully!

You can see that in this case both Widget 1 and Widget 2 are properly destroyed.

Thanks,
Jon N.