Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Bug Render loop stack overflow crash on iOS

Discussion in 'General Graphics' started by perholmes, May 29, 2023.

  1. perholmes

    perholmes

    Joined:
    Dec 29, 2017
    Posts:
    295
    In our combination of Unity Editor and iOS version, we have a consistent crash happening when running on a device. The app crashes within 5 seconds of launching, because the Unity player loop triggers itself until there's a stack overflow.

    We have a workaround below, basically to prevent recursion in the player loop in the generated XCode project. But we are confused, and would love some insight about what this all means.

    This is Unity Editor 2023.1.0a25 together with XCode 14.3, and an app running on an iPad with iOS 15.6.1. The app crashes after 5 seconds. The machine language indicates that it's running out of stack space.

    upload_2023-5-29_22-20-27.png

    The stack trace shows that processTouchEvents is being called over and over, until the stack runs out. I've removed some in-between stack frames:

    Code (CSharp):
    1.  
    2.     frame #787: 0x000000010e79f8f8 UnityFramework`-[UnityAppController(self=<unavailable>, _cmd=<unavailable>) processTouchEvents] at UnityAppController+Rendering.mm:45:9 [opt]
    3.     frame #788: 0x0000000184bbecec QuartzCore`CA::Display::DisplayLink::dispatch_items(unsigned long long, unsigned long long, unsigned long long) + 760
    4.     frame #789: 0x0000000184bc5880 QuartzCore`display_timer_callback(__CFMachPort*, void*, long, void*) + 368
    5.     frame #795: 0x000000010e79f8f8 UnityFramework`-[UnityAppController(self=<unavailable>, _cmd=<unavailable>) processTouchEvents] at UnityAppController+Rendering.mm:45:9 [opt]
    6.     frame #796: 0x0000000184bbecec QuartzCore`CA::Display::DisplayLink::dispatch_items(unsigned long long, unsigned long long, unsigned long long) + 760
    7.     frame #797: 0x0000000184bc5880 QuartzCore`display_timer_callback(__CFMachPort*, void*, long, void*) + 368
    8.     frame #803: 0x000000010e79f8f8 UnityFramework`-[UnityAppController(self=<unavailable>, _cmd=<unavailable>) processTouchEvents] at UnityAppController+Rendering.mm:45:9 [opt]
    9.     frame #804: 0x0000000184bbecec QuartzCore`CA::Display::DisplayLink::dispatch_items(unsigned long long, unsigned long long, unsigned long long) + 760
    10.     frame #805: 0x0000000184bc5880 QuartzCore`display_timer_callback(__CFMachPort*, void*, long, void*) + 368
    11.     frame #811: 0x000000010e79f8f8 UnityFramework`-[UnityAppController(self=<unavailable>, _cmd=<unavailable>) processTouchEvents] at UnityAppController+Rendering.mm:45:9 [opt]
    12.     frame #812: 0x0000000184bbecec QuartzCore`CA::Display::DisplayLink::dispatch_items(unsigned long long, unsigned long long, unsigned long long) + 760
    13.     frame #813: 0x0000000184bc5880 QuartzCore`display_timer_callback(__CFMachPort*, void*, long, void*) + 368
    14.     frame #819: 0x000000010e79f8f8 UnityFramework`-[UnityAppController(self=<unavailable>, _cmd=<unavailable>) processTouchEvents] at UnityAppController+Rendering.mm:45:9 [opt]
    15.     frame #820: 0x0000000184bbecec QuartzCore`CA::Display::DisplayLink::dispatch_items(unsigned long long, unsigned long long, unsigned long long) + 760
    16.     frame #821: 0x0000000184bc5880 QuartzCore`display_timer_callback(__CFMachPort*, void*, long, void*) + 368
    17.     frame #827: 0x000000010e79f8f8 UnityFramework`-[UnityAppController(self=<unavailable>, _cmd=<unavailable>) processTouchEvents] at UnityAppController+Rendering.mm:45:9 [opt]
    18.     frame #828: 0x0000000184bbecec QuartzCore`CA::Display::DisplayLink::dispatch_items(unsigned long long, unsigned long long, unsigned long long) + 760
    19.     frame #829: 0x0000000184bc5880 QuartzCore`display_timer_callback(__CFMachPort*, void*, long, void*) + 368
    20.     frame #835: 0x000000010e79f8f8 UnityFramework`-[UnityAppController(self=<unavailable>, _cmd=<unavailable>) processTouchEvents] at UnityAppController+Rendering.mm:45:9 [opt]
    21.     frame #836: 0x0000000184bbecec QuartzCore`CA::Display::DisplayLink::dispatch_items(unsigned long long, unsigned long long, unsigned long long) + 760
    22.     frame #837: 0x0000000184bc5880 QuartzCore`display_timer_callback(__CFMachPort*, void*, long, void*) + 368
    23.     frame #843: 0x000000010e79f8f8 UnityFramework`-[UnityAppController(self=<unavailable>, _cmd=<unavailable>) processTouchEvents] at UnityAppController+Rendering.mm:45:9 [opt]
    24.     frame #844: 0x0000000184bbecec QuartzCore`CA::Display::DisplayLink::dispatch_items(unsigned long long, unsigned long long, unsigned long long) + 760
    25.     frame #845: 0x0000000184bc5880 QuartzCore`display_timer_callback(__CFMachPort*, void*, long, void*) + 368
    26.     frame #851: 0x000000010e79f8f8 UnityFramework`-[UnityAppController(self=<unavailable>, _cmd=<unavailable>) processTouchEvents] at UnityAppController+Rendering.mm:45:9 [opt]
    27.     frame #852: 0x0000000184bbecec QuartzCore`CA::Display::DisplayLink::dispatch_items(unsigned long long, unsigned long long, unsigned long long) + 760
    28.     frame #853: 0x0000000184bc5880 QuartzCore`display_timer_callback(__CFMachPort*, void*, long, void*) + 368
    29.     frame #859: 0x000000010e79f8f8 UnityFramework`-[UnityAppController(self=<unavailable>, _cmd=<unavailable>) processTouchEvents] at UnityAppController+Rendering.mm:45:9 [opt]
    30.     frame #860: 0x0000000184bbecec QuartzCore`CA::Display::DisplayLink::dispatch_items(unsigned long long, unsigned long long, unsigned long long) + 760
    31.     frame #861: 0x0000000184bc5880 QuartzCore`display_timer_callback(__CFMachPort*, void*, long, void*) + 368
    32.     frame #867: 0x00000001a1a20988 GraphicsServices`GSEventRunModal + 160
    33.     frame #868: 0x00000001838d5a94 UIKitCore`-[UIApplication _run] + 1080
    34.     frame #869: 0x000000018366efd4 UIKitCore`UIApplicationMain + 336
    35.     frame #870: 0x000000010e79f518 UnityFramework`-[UnityFramework
    36.  
    In UnityAppController+Rendering.mm in the XCode project, the recursion happens here:

    Code (CSharp):
    1. - (void)processTouchEvents
    2. {
    3.     // Sometimes some touch event delivery isn't properly interleaved with graphical frames.
    4.     // Running additional run loop here improves event handling in those cases.
    5.     // We pass an NSDate from the past to invoke the run loop only once.
    6.     if (self.unityView.userInteractionEnabled)
    7.     {
    8.         static NSDate* past = [NSDate dateWithTimeIntervalSince1970: 0]; // the oldest date we can get
    9.         [[NSRunLoop currentRunLoop] acceptInputForMode: NSDefaultRunLoopMode beforeDate: past];
    10.     }
    11. }
    12. - (void)repaintDisplayLink
    13. {
    14.     if (!_didResignActive)
    15.     {
    16.         UnityDisplayLinkCallback(_displayLink.timestamp);
    17.         [self repaint];
    18.         [self processTouchEvents];
    19.     }
    20. }
    The processTouchEvents function is literally starting another run loop from within another run loop, which triggers another processTouchEvents, which triggers another run loop, until the stack is exhausted. The comment even seems aware of it, but seems to be relying on undocumented side-effects of particular combinations of iOS SDK, XCode and iOS version, and in our combination of facts, it doesn't hold true.

    The workaround is to stop the display link while processing touch events, so that it doesn't trigger another run loop from within the run loop:

    Code (CSharp):
    1. - (void)repaintDisplayLink
    2. {
    3.     if (!_didResignActive)
    4.     {
    5.         UnityDisplayLinkCallback(_displayLink.timestamp);
    6.         [self repaint];
    7.         _displayLink.paused = YES;  // <----- ADD
    8.         [self processTouchEvents];
    9.         _displayLink.paused = NO;   // <----- ADD
    10.     }
    11. }
    The question is, what the heck is going on here? What's the deal with trigger a run loop from within the run loop? That's, like, a crime, even on the face of it. What is correct here?

    Nevertheless, for anyone else encountering this, we've made a post-build step to add this code to the XCode project for an iOS build automatically. Make the following class:

    Code (CSharp):
    1. using UnityEditor;
    2. using UnityEditor.Callbacks;
    3. using System.IO;
    4. public class PostBuild
    5. {
    6.     [PostProcessBuildAttribute(1)]
    7.     public static void IOSBuildPostProcess(BuildTarget target, string pathToBuiltProject)
    8.     {
    9.         if (target != BuildTarget.iOS) {
    10.             return;
    11.         }
    12.         // Fix processTouchEvent stack overflow in UnityAppController
    13.         BuildLog.Log("Fixing processTouchEvent stack overflow in UnityAppController");
    14.         {
    15.             var filename = pathToBuiltProject + "/Classes/UnityAppController+Rendering.mm";
    16.             string contents = File.ReadAllText(filename);
    17.             contents = contents.Replace(
    18.                 "[self processTouchEvents];",
    19.                 "_displayLink.paused = YES; [self processTouchEvents]; _displayLink.paused = NO;"
    20.             );
    21.             File.WriteAllText(filename, contents);
    22.         }
    23.     }
    24. }
    25.  
     
    all_iver likes this.
  2. kin4n

    kin4n

    Joined:
    Jan 17, 2023
    Posts:
    9
    Hello, I m encountering exactly the same weird loop, but I don t understand how this post build class worksm would you mind giving me a hint on how it works ? It s also weird you are the only one mentionning this loop. On my side it seems to happen only on older devices (Iphone 11 on my side)
     
  3. perholmes

    perholmes

    Joined:
    Dec 29, 2017
    Posts:
    295
    From what we gather, the Unity render loop is trying to process touch events while rendering. When events are received, they restart the render loop from within the render loop, and for some magical reason, this doesn't cause an obvious infinite loop on whatever devices they tested on. Presumably, they're trying to adapt to slow devices and capture touch event timing even if the frame rate is low.

    We have indeed been running on an older iOS device, where whatever implementation-assumptions they've made about iOS isn't true. And there it seems that the processing touch events from within the render loop actually causes some new touch event, maybe some kind of idle event, to be re-inserted into the queue, causing the render loop to trigger another render loop ad nauseum, until the stack overflows at about 830 frames.

    As I remember it, the hack is to disable the queue while the queue is being processed, so that no new events can be added to the queue while we're processing existing events. There would be other ways to stop the infinite loop, like a static variable to count nesting, so that we refuse to recurse.

    I think that Unity is making some wrong assumptions, and even on the face of it, you shouldn't be triggering a render loop from within the render loop. It's an accident waiting to happen, even if you feel sure about the conditions it happens under. And here we are.

    The post build step rewrites the line that processes touch events during the render loop in the XCode project to disable the queue while processing touch events.
     
  4. kin4n

    kin4n

    Joined:
    Jan 17, 2023
    Posts:
    9
    I totally agree. Fixed it with your code, but noticed that the bug isnt there anymore in the 2022.3 LTS that just came out. Thank you for the analysis, I thought exactly the same. Peace
     
  5. perholmes

    perholmes

    Joined:
    Dec 29, 2017
    Posts:
    295
    We haven't tested in any of the 2022 LTS versions. This happens for us on the 2023 alpha we're on. We'll move to a newer alpha soon, and it's a good point to keep checking if the change is needed.
     
  6. udubaso

    udubaso

    Joined:
    Dec 30, 2014
    Posts:
    16
    Thanks, with this we no longer experience these crashes.
     
  7. unity_key47

    unity_key47

    Joined:
    Jan 30, 2022
    Posts:
    5
    2023.1.7f1
    Xcode 14.3.1 (14E300c)
    iPhone 11
    iOS 16.3
    Encouneted exactly the same issue.
    Fix from @perholmes worked like a charm.

    Code (CSharp):
    1. 0   libsystem_pthread.dylib           0x00000001f4d48be4 thread_chkstk_darwin + 60
    2. 1   UnityFramework                    0x000000010a6913c0 Camera::RenderSkybox() + 40 (Camera.cpp:1160)
    3. 2   UnityFramework                    0x000000010a15f20c ExecuteDrawSkyboxRendererCommand(LegacyListData&) + 364 (ScriptableDrawRenderers.cpp:2043)
    4. 3   UnityFramework                    0x000000010a14d1e0 RenderingCommandBuffer::ExecuteCommandBufferWithState(ShaderPassContext&, RenderNodeQueue&, RenderingCommandBufferState*, unsigned int, ComputeQueueType) const + 864 (RenderingCommandBuffer.cpp:2615)
    5. 4   UnityFramework                    0x000000010a14c74c RenderingCommandBuffer::ExecuteCommandBuffer(ShaderPassContext&, RenderNodeQueue&, unsigned int, dynamic_array<std::__1::pair<ShaderLab::FastPropertyName, RenderTexture*>, 0ul>*, ComputeQueueType, ... + 88 (RenderingCommandBuffer.cpp:2301)
    6. 5   UnityFramework                    0x000000010a164184 ScriptableRenderContext::ExecuteScriptableRenderLoop() + 2656 (ScriptableRenderContext.cpp:726)
    7. 6   UnityFramework                    0x000000010ea6e928 UniversalRenderPipeline_RenderSingleCamera_mAED46E947EC1009B5D51EE35B156C551A2901A18 + 2980 (Unity.RenderPipelines.Universal.Runtime__5.cpp:8671)
    8. 7   UnityFramework                    0x000000010ea6bed0 UniversalRenderPipeline_RenderCameraStack_m47BBC0B4111D83BB6EE3275C9572BFBF2F5451B9 + 3164 (Unity.RenderPipelines.Universal.Runtime__5.cpp:9405)
    9. 8   UnityFramework                    0x000000010ea6a2d8 UniversalRenderPipeline_Render_m15A42AB44C14AB4DCA7EF0B915964D46B643D50E + 1060 (Unity.RenderPipelines.Universal.Runtime__5.cpp:7834)
    10. 9   UnityFramework                    0x000000010ec85404 RenderPipelineManager_DoRenderLoop_Internal_mB646C8738F4A9859101F3BE94809E2E10BBDB1FB + 700 (UnityEngine.CoreModule__5.cpp:14332)
    11. 10  UnityFramework                    0x0000000109fcd5a4 il2cpp::vm::Runtime::InvokeWithThrow(MethodInfo const*, void*, void**) + 100 (Runtime.cpp:637)
    12. 11  UnityFramework                    0x0000000109fcd4e0 il2cpp::vm::Runtime::Invoke(MethodInfo const*, void*, void**, Il2CppException**) + 84 (Runtime.cpp:623)
    13. 12  UnityFramework                    0x000000010a29a65c 0x109f24000 + 3630684 (ScriptingApi_Il2Cpp.cpp:297)
    14. 13  UnityFramework                    0x000000010a2a7dc4 ScriptingInvocation::Invoke(ScriptingExceptionPtr*, bool) + 120 (ScriptingInvocation.cpp:298)
    15. 14  UnityFramework                    0x000000010a164aa4 Invoke + 20 (ScriptingInvocation.h:71)
    16. 15  UnityFramework                    0x000000010a164aa4 ScriptableRenderContext::ExtractAndExecuteRenderPipeline(dynamic_array<Camera*, 0ul> const&, void (*)(SceneNode const*, AABB const*, IndexList&, SceneCullingParameters const*), void*, ScriptingObje... + 296 (ScriptableRenderContext.cpp:1038)
    17. 16  UnityFramework                    0x000000010a6ac82c RenderManager::RenderCamerasWithScriptableRenderLoop(int) + 728 (RenderManager.cpp:280)
    18. 17  UnityFramework                    0x000000010a6acee8 RenderManager::RenderCameras(int, void (*)(), void (*)()) + 72 (RenderManager.cpp:357)
    19. 18  UnityFramework                    0x000000010a19673c PlayerRender(bool) + 700 (Player.cpp:1069)
    20. 19  UnityFramework                    0x000000010a18d08c ExecutePlayerLoop(NativePlayerLoopSystem*) + 100 (PlayerLoop.cpp:389)
    21. 20  UnityFramework                    0x000000010a18d0cc ExecutePlayerLoop(NativePlayerLoopSystem*) + 164 (PlayerLoop.cpp:410)
    22. 21  UnityFramework                    0x000000010a18d384 PlayerLoop() + 280 (PlayerLoop.cpp:517)
    23. 22  UnityFramework                    0x000000010a95fa7c UnityPlayerLoopImpl(bool) + 112 (LibEntryPoint.mm:339)
    24. 23  UnityFramework                    0x0000000109f39b18 UnityRepaint + 12 (UnityAppController+Rendering.mm:194)
    25. 24  UnityFramework                    0x0000000109f39b18 -[UnityAppController(Rendering) repaint] + 84 (UnityAppController+Rendering.mm:69)
    26. 25  UnityFramework                    0x0000000109f39ab4 -[UnityAppController(Rendering) repaintDisplayLink] + 76 (UnityAppController+Rendering.mm:54)
    27. 26  QuartzCore                        0x00000001a8192ca4 CA::Display::DisplayLink::dispatch_items(unsigned long long, unsigned long long, unsigned long long) + 820 (CADisplay.mm:4507)
    28. 27  QuartzCore                        0x00000001a81a46f8 display_timer_callback(__CFMachPort*, void*, long, void*) + 372 (CADisplayTimer.cpp:219)
    29. 28  CoreFoundation                    0x00000001a6b427f4 __CFMachPortPerform + 176 (CFMachPort.c:586)
    30. 29  CoreFoundation                    0x00000001a6b5fcd0 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 60 (CFRunLoop.c:1981)
    31. 30  CoreFoundation                    0x00000001a6b618d8 __CFRunLoopDoSource1 + 520 (CFRunLoop.c:2120)
    32. 31  CoreFoundation                    0x00000001a6b43110 __CFRunLoopRun + 2264 (CFRunLoop.c:3175)
    33. 32  CoreFoundation                    0x00000001a6b47eb0 CFRunLoopRunSpecific + 612 (CFRunLoop.c:3418)
    34. 33  UnityFramework                    0x0000000109f399e0 -[UnityAppController(Rendering) processTouchEvents] + 112 (UnityAppController+Rendering.mm:45)
    35. 34  QuartzCore                        0x00000001a8192ca4 CA::Display::DisplayLink::dispatch_items(unsigned long long, unsigned long long, unsigned long long) + 820 (CADisplay.mm:4507)
    36. 35  QuartzCore                        0x00000001a81a46f8 display_timer_callback(__CFMachPort*, void*, long, void*) + 372 (CADisplayTimer.cpp:219)
    37. 36  CoreFoundation                    0x00000001a6b427f4 __CFMachPortPerform + 176 (CFMachPort.c:586)
    38. 37  CoreFoundation                    0x00000001a6b5fcd0 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 60 (CFRunLoop.c:1981)
    39. 38  CoreFoundation                    0x00000001a6b618d8 __CFRunLoopDoSource1 + 520 (CFRunLoop.c:2120)
    40. 39  CoreFoundation                    0x00000001a6b43110 __CFRunLoopRun + 2264 (CFRunLoop.c:3175)
    41. 40  CoreFoundation                    0x00000001a6b47eb0 CFRunLoopRunSpecific + 612 (CFRunLoop.c:3418)
    42. 41  UnityFramework                    0x0000000109f399e0 -[UnityAppController(Rendering) processTouchEvents] + 112 (UnityAppController+Rendering.mm:45)
    43. 42  QuartzCore                        0x00000001a8192ca4 CA::Display::DisplayLink::dispatch_items(unsigned long long, unsigned long long, unsigned long long) + 820 (CADisplay.mm:4507)
    44. 43  QuartzCore                        0x00000001a81a46f8 display_timer_callback(__CFMachPort*, void*, long, void*) + 372 (CADisplayTimer.cpp:219)
    45. 44  CoreFoundation                    0x00000001a6b427f4 __CFMachPortPerform + 176 (CFMachPort.c:586)
    46. 45  CoreFoundation                    0x00000001a6b5fcd0 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 60 (CFRunLoop.c:1981)
    47. 46  CoreFoundation                    0x00000001a6b618d8 __CFRunLoopDoSource1 + 520 (CFRunLoop.c:2120)
    48. 47  CoreFoundation                    0x00000001a6b43110 __CFRunLoopRun + 2264 (CFRunLoop.c:3175)
    49. 48  CoreFoundation                    0x00000001a6b47eb0 CFRunLoopRunSpecific + 612 (CFRunLoop.c:3418)
    50. 49  UnityFramework                    0x0000000109f399e0 -[UnityAppController(Rendering) processTouchEvents] + 112 (UnityAppController+Rendering.mm:45)
    51. 50  QuartzCore                        0x00000001a8192ca4 CA::Display::DisplayLink::dispatch_items(unsigned long long, unsigned long long, unsigned long long) + 820 (CADisplay.mm:4507)
    52. 51  QuartzCore                        0x00000001a81a46f8 display_timer_callback(__CFMachPort*, void*, long, void*) + 372 (CADisplayTimer.cpp:219)