NativeDestruct + RemoveFromParent() = Crash on exit

I had this issue on 4.10.4 and upgrade to 4.11.2 to see if it fixed the problem, it did not.

This whole setup/bug is 100% C++

Setup:
I have a user widget that creates a second user widget and stores the reference in a variable. When the first user widget is destroyed it fires NativeDestruct which checks if second user widget reference is valid and destroys it.

Bug:
While second user widget is created and i exit the game it causes a hard crash.

Work around:
I have found a work around which involves destroying the second user widget through other means than NativeDestruct but now my code is over complicated and “Hacky”.

Hey AndrewM47-

Can you provide the callstack and log files from the crash? Are you able to reproduce the crash in a new clean project? If so, please include the setup steps or sample code used to cause the crash.

I was able to reproduce the bug in a clean C++ FPS project in 4.11.2 and i’m uploading the project to dropbox now.

Edit : Here is the drop box link. [Project][1]

here’s what it will look like :slight_smile:

94583-bug.png

,

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.

I understand most of what you have said but when i mark SecondUserWidgetPointer with UPROPERTY it still doesn’t get destroyed when i destroy FirstUserWidget?

Can you elaborate any? The changes that Jon mentioned prevented the crash for me as well. Additionally, I’m not sure if there is a need from a gameplay perspective, but I also found that removing the Set Input Mode UIOnly node from the player blueprint prevents the crash from occurring without the changes.

,

I guess I’m not understanding what you are trying to do here then. Once all the references to the widget have been removed it should be cleared for garbage collection.

If you still need to do some clean up around the time of destruction, I’d recommend doing it in BeginDestroy (and still leaving the second widget as a UPROPERTY). That way, you’re guaranteed that Widget 2 will not be destructed before Widget 1 (as Widget 1 still has a valid reference). However, this is still unsafe because you are not aware what state the game itself is in.

Are you manually destroying the first widget somewhere?

So my current setup works like this, i have an inventory that stores widget items and when the cursor hovers over the item it creates a second widget that displays the hover information (see the image below), now the way i have my inventory setup is when i move an item it destroys the item and creates it in the new location, but if it destroys the item while the hover info is displaying then OnMouseLeave wont trigger so it wont destroy it. NativeDestruct solves this problem by destroying the hover info if it is created but it caused a crash when i exited the game while hover info was created.

94765-capture.jpg

,

From what you’ve described, the biggest problem is that you really only need to manually destroy the Second Widget when the first one is manually destroyed. Otherwise, things will happen automatically. That makes a solution fairly straightforward. Basically, just move the NativeDestruct logic into a BlueprintCallable method:

UFUNCTION(BlueprintCallable, Category = "CustomUserWidget")
void Close()
{
    if (SecondUserWidgetPointer)
    {
        SecondUserWidget->RemoveFromViewport();
    }
    RemoveFromViewport();
}

Then, you could do something like this in Blueprint (or anywhere else from C++):

Then, when you detect your widget has been moved, just call Close instead of directly removing it from the viewport.

94750-giphy.gif

Now, you’ve eliminated the issue. You know that when your code calls “Close” everything is valid. However, if the Widgets get destructed some other way, they aren’t doing anything dangerous.

Thank you for your time and help, i ended up going with your solution :slight_smile: