Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Crash in iOS when swiping up for the control center #1705

Open
MichaelHills opened this issue Sep 15, 2020 · 22 comments
Open

Crash in iOS when swiping up for the control center #1705

MichaelHills opened this issue Sep 15, 2020 · 22 comments
Labels
B - bug Dang, that shouldn't have happened C - needs investigation Issue must be confirmed and researched DS - ios

Comments

@MichaelHills
Copy link
Contributor

On iOS 12.4.8 on my iPhone 6, using Bevy on winit, if I swipe up for the control center immediately after app load I get a crash. If I first touch the screen to fire some touch events before trying to open the control center, then there is no crash and everything works as normal.

I'm trying to reproduce this directly on winit, but haven't been able to yet. Thought I'd post first anyway to see if this sounds familiar. I've googled various parts of the stack trace with no success.

It's really bizarre because the crash is inside iOS libraries. Perhaps the gesture recogniser is registered on the winit-defined UIView / UIViewController and some interaction there is the problem. Unfortunately without any source to the stack trace I don't know what this NSArray is. I suspect it's just accumulating touch events as part of the "delay touch near edge of screen" logic.

Exception	NSException *	"*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil"	0x0000000282d89110

* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x00000001bd3239c0 libobjc.A.dylib`objc_exception_throw
    frame #1: 0x00000001be0c4bec CoreFoundation`_CFThrowFormattedException + 112
    frame #2: 0x00000001be0358ac CoreFoundation`-[__NSArrayM insertObject:atIndex:] + 1212
    frame #3: 0x00000001ea565868 UIKitCore`-[UIGestureRecognizer _delayTouch:forEvent:] + 216
    frame #4: 0x00000001ea5813c0 UIKitCore`-[_UISystemGestureGateGestureRecognizer _delayTouch:forEvent:] + 140
    frame #5: 0x00000001ea565a68 UIKitCore`-[UIGestureRecognizer _delayTouchesForEvent:inPhase:] + 220
    frame #6: 0x00000001ea565c98 UIKitCore`-[UIGestureRecognizer _delayTouchesForEventIfNeeded:] + 100
    frame #7: 0x00000001ea566a9c UIKitCore`-[UIGestureRecognizer _updateGestureWithEvent:buttonEvent:] + 504
    frame #8: 0x00000001ea55ac78 UIKitCore`_UIGestureEnvironmentUpdate + 2180
    frame #9: 0x00000001ea55a3a8 UIKitCore`-[UIGestureEnvironment _deliverEvent:toGestureRecognizers:usingBlock:] + 384
    frame #10: 0x00000001ea55a188 UIKitCore`-[UIGestureEnvironment _updateForEvent:window:] + 204
    frame #11: 0x00000001ea9727d0 UIKitCore`-[UIWindow sendEvent:] + 3112
    frame #12: 0x00000001ea95285c UIKitCore`-[UIApplication sendEvent:] + 340
    frame #13: 0x00000001eaa189d4 UIKitCore`__dispatchPreprocessedEventFromEventQueue + 1768
    frame #14: 0x00000001eaa1b100 UIKitCore`__handleEventQueueInternal + 4828
    frame #15: 0x00000001eaa14330 UIKitCore`__handleHIDEventFetcherDrain + 152
    frame #16: 0x00000001be0dcf1c CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 24
    frame #17: 0x00000001be0dce9c CoreFoundation`__CFRunLoopDoSource0 + 88
    frame #18: 0x00000001be0dc784 CoreFoundation`__CFRunLoopDoSources0 + 176
    frame #19: 0x00000001be0d76c0 CoreFoundation`__CFRunLoopRun + 1004
    frame #20: 0x00000001be0d6fb4 CoreFoundation`CFRunLoopRunSpecific + 436
    frame #21: 0x00000001c02d879c GraphicsServices`GSEventRunModal + 104
    frame #22: 0x00000001ea938c38 UIKitCore`UIApplicationMain + 212
  * frame #23: 0x0000000101dc0910 Callisto`cart_tmp_winit::platform_impl::platform::event_loop::EventLoop$LT$T$GT$::run::h2a3bed959aaaa659(self=EventLoop<()> @ 0x000000016ef50bb0, event_handler=closure-1 @ 0x000000016ef50bd0) at event_loop.rs:116:13
    frame #24: 0x0000000101de5b4c Callisto`cart_tmp_winit::event_loop::EventLoop$LT$T$GT$::run::h8c8ef82fc7ef7463(self=<unavailable>, event_handler=<unavailable>) at event_loop.rs:149:9
    frame #25: 0x0000000101de1724 Callisto`bevy_winit::run::h215a617795758f71(event_loop=<unavailable>, event_handler=<unavailable>) at lib.rs:43:5
    frame #26: 0x0000000101dd1d28 Callisto`bevy_winit::winit_runner::h90a304e554acabdc(app=App @ 0x000000016ef51c28) at lib.rs:244:9
    frame #27: 0x0000000101dda810 Callisto`core::ops::function::Fn::call::ha1caefc47bb26836((null)=0x0000000000000001, (null)=(bevy_app::app::App) @ 0x000000016ef51c28) at function.rs:70:5
    frame #28: 0x0000000102de6698 Callisto`_$LT$alloc..boxed..Box$LT$F$GT$$u20$as$u20$core..ops..function..Fn$LT$A$GT$$GT$::call::h88bacb7a918cef10(self=0x000000016ef52628, args=(bevy_app::app::App) @ 0x000000016ef51f58) at boxed.rs:1056:9
    frame #29: 0x0000000102de8ca8 Callisto`bevy_app::app::App::run::h0e84ac1eed74c29a(self=App @ 0x000000016ef52ee8) at app.rs:80:9
    frame #30: 0x0000000102dcf734 Callisto`bevy_app::app_builder::AppBuilder::run::hed1780d15d7a0af0(self=0x000000016ef53558) at app_builder.rs:45:9
    frame #31: 0x0000000100f06808 Callisto`bevy_main at lib.rs:48:5
    frame #32: 0x0000000100eb3d5c Callisto`main at main.swift:17:1
    frame #33: 0x00000001bdb9a8e0 libdyld.dylib`start + 4
@francesca64 francesca64 added DS - ios C - needs investigation Issue must be confirmed and researched B - bug Dang, that shouldn't have happened labels Sep 15, 2020
@MichaelHills
Copy link
Contributor Author

I was able to reproduce this using just the wgpu triangle example on winit. By just having this call to swap_chain.get_current_frame() in RedrawRequested I can get it to crash. Commenting out get_current_frame() such that RedrawRequested is empty will make the crash go away.

            Event::RedrawRequested(_) => {
                let frame = swap_chain
                    .get_current_frame()
                    .expect("Failed to acquire next swap chain texture")
                    .output;

So seemingly wgpu somehow triggers the crash (and bevy uses wgpu so that probably explains my original crash). I don't really see what wgpu has to do with this though.

@MichaelHills
Copy link
Contributor Author

MichaelHills commented Dec 5, 2020

On Bevy and iPhone 11 I can reproduce this crash by swiping in from any side of the screen. Specifically need to come in from the outside edge. It kind of sounds like this might be related to the preferredScreenEdgesDeferringSystemGestures functionality. Haven't had a chance to go back to the wgpu/winit example though and try swiping from any edge.

@bigmstone
Copy link

@MichaelHills I am looking into this as well. I've noticed if you touch the screen prior to dragging in from the edges it will prevent the crash. I have put a bunch of traces into winit side of the UIApplication/View Controller, but haven't been able to determine much that route. None of my traces in the winit view class or the delegate are triggered before the crash (I was assuming I would see something on the touch handler). My best guess right now, and I'll reiterate it's only a guess, is there's some uninitialized array in the UIGestureRecognizer that somehow gets initialized when you provide a valid touch inside the app. I need to figure out some better way of troubleshooting this though. It all feels pretty opaque right now. I.E. I am not sure where exactly to put a break to begin stepping through because everything on the rust side is abstracted by run of the event loop.

@bigmstone
Copy link

This also seems related to #1613 — backtrace at a glance is the same.

@bigmstone
Copy link

#1843 Resolved this on my end. Would enjoy some other 👀

@MichaelHills
Copy link
Contributor Author

Wow nice work. I'll try it when I get a chance. :)

@shuoli84
Copy link
Contributor

shuoli84 commented Sep 4, 2022

Bug and trouble shooting

I've been debugging the crash bug for hours. It was reported by others and had a quick fix by setting ScreenEdge::ALL. Since the crash is reproducible, so I tried to tackle it down or at least find the root cause, I think I got something.
Currently, we create UIWindow first, then run UIApplicationMain, which is problematic, since UIApplicationMain actually setup some global state that UIKit requires, like following

Stack:
[UIGestureEnvironment init]
[UIApplication init]
UIApplicationInstantiateSingleton
_UIApplicationMainPreparations
UIApplicationMain

UIGestureEnvironment is also a singleton which is inited and stored inside UIApplication. Also, it is stored in each UIGestureRecognizer instance if it is already created. And UIGestureRecognizer is created when we create UIWindow, which means at that time, the global didn't created, so all the recognizer with _gestureEnvironment property as nil.

When ScreenEdge preference is 0, it triggers some logic of delay touch handling, and in that code path, it read the property as nil, which crashed when add into a nsarray.

How to fix:

UIWindow/UIView should be created by method on ApplicationDelegate, since UIApplicationMain will never return.

Other

Some evidence (crashed is the bevy_ios_example. good is a normal created ios app):

crashed:
(lldb) po ((_UISystemGestureGateGestureRecognizer*)0x15fe115f0) -> _gestureEnvironment
 nil

Good
(lldb) po ((_UISystemGestureGateGestureRecognizer *)0x101808750) -> _gestureEnvironment
<UIGestureEnvironment: 0x281ee4af0>

Crashed
Screen Shot 2022-09-04 at 22 25 53

Good call stack
Screen Shot 2022-09-04 at 22 24 46

@shuoli84
Copy link
Contributor

shuoli84 commented Sep 4, 2022

Good full stack, we can see the window is created inside UIApplicationMain.

Screen Shot 2022-09-04 at 22 37 27

@madsmtm
Copy link
Member

madsmtm commented Sep 4, 2022

we create UIWindow first, then run UIApplicationMain, which is problematic, since UIApplicationMain actually setup some global state that UIKit requires, like following

This sounds a lot like the same issue we have on macOS, which is documented in the bottom of the readme for now; can you try the suggested workaround (creating your window inside StartCause(Init)?

@shuoli84
Copy link
Contributor

shuoli84 commented Sep 4, 2022

@madsmtm the window is created by winit, before it calls uiapplicationmain. I don’t think user has the chance to modify where to initialize the window in ios platform.

@madsmtm
Copy link
Member

madsmtm commented Sep 5, 2022

Hmm, are you sure? I'm fairly certain you should be able to do something like this?

use winit::{
    event::{Event, StartCause, WindowEvent},
    event_loop::EventLoop,
    window::Window,
};

fn main() {
    let event_loop = EventLoop::new();

    let mut window = None;

    event_loop.run(move |event, event_loop, control_flow| {
        control_flow.set_wait();

        match event {
            Event::NewEvents(StartCause::Init) => {
                window = Some(Window::new(&event_loop).unwrap());
            }
            Event::WindowEvent {
                event: WindowEvent::CloseRequested,
                window_id,
            } if Some(window_id) == window.as_ref().map(|w| w.id()) => control_flow.set_exit(),
            _ => (),
        }
    });
}

@shuoli84
Copy link
Contributor

shuoli84 commented Sep 5, 2022

@madsmtm I'm not sure, I just started read winit code the moment hit by the crash. But I see code in app_state, which implements will_launch_transition or did_finish_launching_transition, they are taking window from predefined state, which I think is created and managed by winit itself?

AppStateImpl::NotLaunched {
                queued_windows,
                queued_events,
                queued_gpu_redraws,
            } => (queued_windows, queued_events, queued_gpu_redraws),

EDIT: typo

@shuoli84
Copy link
Contributor

shuoli84 commented Sep 5, 2022

@madsmtm Thanks for the advice. From code here, seems the crate supports user create a new window instance and replace the one winit created. Seems a bit hacky in my mind... Do you know why it designed like this?

let (windows, events) = AppState::get_mut().did_finish_launching_transition();
let events = std::iter::once(EventWrapper::StaticEvent(Event::NewEvents(
StartCause::Init,
)))
.chain(events);
handle_nonuser_events(events);
// the above window dance hack, could possibly trigger new windows to be created.
// we can just set those windows up normally, as they were created after didFinishLaunching
for window in windows {
let count: NSUInteger = msg_send![window, retainCount];
// make sure the window is still referenced
if count > 1 {
let _: () = msg_send![window, makeKeyAndVisible];
}
let _: () = msg_send![window, release];
}

@madsmtm
Copy link
Member

madsmtm commented Sep 5, 2022

Because it's possible to create windows before StartCause::Init on other platforms, so there hasn't been much incentive to change the API to something less ergonomic (but more correct) - it is something I'm quite annoyed with though, so I'll probably write an issue and discuss a few solutions with the other maintainers at some point.

@kchibisov
Copy link
Member

We now suggest to create a window from inside Resume and it's documented. The main issue that we shouldn't prevent window creation on other platforms since ios is a bit special here.

The same applies for android, since it also should create from the Resume event.

Probably the right way is to fail window creation on ios from the wrong place? Same for Android?

@shuoli84
Copy link
Contributor

shuoli84 commented Sep 5, 2022

@kchibisov Thanks, any link for the documentation? I scanned the code but didn't find anything. In readme, the window is created outside of the event loop.

@madsmtm
Copy link
Member

madsmtm commented Sep 5, 2022

Resume event.

Oh right, yeah, that one.

Probably the right way is to fail window creation on ios from the wrong place? Same for Android?

I'm not sure it's technically possible to know if the main event loop is running on iOS, but if it is, we should definitely do this!

@kchibisov
Copy link
Member

I'm not sure it's technically possible to know if the main event loop is running on iOS, but if it is, we should definitely do this!

You can run event_loop in winit only once, so you sort of know. Just set a global AtomicBool from the run invokation.

bors bot pushed a commit to bevyengine/bevy that referenced this issue Sep 6, 2022
# Objective

Fixes #5882 

## Solution

Per rust-windowing/winit#1705, the root cause is "UIWindow should be created inside UIApplicationMain". Currently, there are two places to create UIWindow, one is Plugin's build function, which is not inside UIApplicationMain. Just comment it out, and it works.
nicopap pushed a commit to nicopap/bevy that referenced this issue Sep 12, 2022
# Objective

Fixes bevyengine#5882 

## Solution

Per rust-windowing/winit#1705, the root cause is "UIWindow should be created inside UIApplicationMain". Currently, there are two places to create UIWindow, one is Plugin's build function, which is not inside UIApplicationMain. Just comment it out, and it works.
james7132 pushed a commit to james7132/bevy that referenced this issue Oct 28, 2022
# Objective

Fixes bevyengine#5882 

## Solution

Per rust-windowing/winit#1705, the root cause is "UIWindow should be created inside UIApplicationMain". Currently, there are two places to create UIWindow, one is Plugin's build function, which is not inside UIApplicationMain. Just comment it out, and it works.
ItsDoot pushed a commit to ItsDoot/bevy that referenced this issue Feb 1, 2023
# Objective

Fixes bevyengine#5882 

## Solution

Per rust-windowing/winit#1705, the root cause is "UIWindow should be created inside UIApplicationMain". Currently, there are two places to create UIWindow, one is Plugin's build function, which is not inside UIApplicationMain. Just comment it out, and it works.
@dmoore764
Copy link

Sorry if this is not appropriate to ask here, but does anyone have a sort of minimal example of an iOS winit app? I'm not getting any touch events firing at all. I do see window resize events/resume events/etc, so I assume everything else is functioning.

@sekoyo
Copy link

sekoyo commented Oct 8, 2023

Sorry if this is not appropriate to ask here, but does anyone have a sort of minimal example of an iOS winit app? I'm not getting any touch events firing at all. I do see window resize events/resume events/etc, so I assume everything else is functioning.

Did you get any further with this? I'd also appreciate an example or guide (and for Android)

@kchibisov
Copy link
Member

@dominictobias the android is dead simple though. You could look at the https://github.com/rust-windowing/glutin/blob/master/glutin_examples/examples/android.rs . And that's the only thing you'd need, the rest is the same as on any other OS.

Not familiar with how to setup iOS example.

@sekoyo
Copy link

sekoyo commented Oct 8, 2023

@dominictobias the android is dead simple though. You could look at the https://github.com/rust-windowing/glutin/blob/master/glutin_examples/examples/android.rs . And that's the only thing you'd need, the rest is the same as on any other OS.

Not familiar with how to setup iOS example.

Ah nice thanks. I didn't try yet I can prob figure iOS out then

Subserial pushed a commit to Subserial/bevy_winit_hook that referenced this issue Jan 24, 2024
# Objective

Fixes #5882 

## Solution

Per rust-windowing/winit#1705, the root cause is "UIWindow should be created inside UIApplicationMain". Currently, there are two places to create UIWindow, one is Plugin's build function, which is not inside UIApplicationMain. Just comment it out, and it works.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
B - bug Dang, that shouldn't have happened C - needs investigation Issue must be confirmed and researched DS - ios
Development

Successfully merging a pull request may close this issue.

8 participants