Defer Fails to Execute When Its Enclosing Scope is Cancelled by a Composed Race Expression

Summary

A defer statement fails to execute if its enclosing scope (e.g., a block or function) is cancelled by a race expression, particularly when this race is composed with races. Simpler, non-composed race expressions with defer appear to work as expected. The issue seems to arise when a race cancels a scope containing a defer in these more complex, composed scenarios, causing the defer to be ignored.

Please select what you are reporting on:

Verse

What Type of Bug are you experiencing?

Verse

Steps to Reproduce

  1. Create a creative_device with the following OnBegin method
using { /Fortnite.com/Devices }
using { /Verse.org/Colors }
using { /Verse.org/Simulation }

defer_test_device := class(creative_device):
    OnBegin<override>()<suspends>:void =
        # This first race (non-composed with nested races) works fine:
        # race:
        #     block:
        #         Print("Add first defer (simple)", ?Color:=NamedColors.Orange)
        #         defer:
        #             Print("First defer done (simple)", ?Color:=NamedColors.Orange)
        #         Sleep(999.0)
        #     Sleep(5.0)
        # return

        # This composed race demonstrates the issue:
        race:
            block: // This block is the scope for the first defer
                Print("Add first defer (composed)", ?Color:=NamedColors.Blue)
                defer:
                    Print("First defer done (composed)", ?Color:=NamedColors.Blue) // This defer is missed
                race:
                    block:
                        Print("Add second defer (composed)", ?Color:=NamedColors.Green)
                        defer:
                            Print("Second defer done (composed)", ?Color:=NamedColors.Green)
                        Sleep(999.0) 
                    Sleep(999.0) 
            Sleep(5.0) # This sleep wins the outer race, cancelling the block above
  1. Add the device to the map.
  2. Launch a session and observe the output log.

Expected Result

log

Add first defer (composed)
Add second defer (composed)
Second defer done (composed)
First defer done (composed)

Observed Result

log, The “First defer done” message is missing.

Add first defer (composed)
Add second defer (composed)
Second defer done (composed)

Platform(s)

windows

Additional Notes

This bug is particularly problematic when implementing systems with composed concurrency, such as dynamic UI elements in a complex widget system. In these scenarios, defer is often used in a manner similar to RAII to ensure UI elements are properly cleaned up (e.g., removed from the canvas) when their managing concurrency scope ends.