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
- 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
- Add the device to the map.
- 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.