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

Engine v1.12.13-hotfixes has bug when using AddToApp #52455

Closed
xujim opened this issue Mar 12, 2020 · 20 comments
Closed

Engine v1.12.13-hotfixes has bug when using AddToApp #52455

xujim opened this issue Mar 12, 2020 · 20 comments
Assignees
Labels
a: existing-apps Integration with existing apps via the add-to-app flow c: crash Stack traces logged to the console customer: alibaba engine flutter/engine repository. See also e: labels. platform-ios iOS applications specifically
Milestone

Comments

@xujim
Copy link

xujim commented Mar 12, 2020

hi, I found engine v1.12.13-hotfixes is not ok with the add to app. When I pushed multiple FlutterViewController and pop back, the app will crash due to accessing the nulled surface in Rasterizer::Setup. Here is my mini example to reproduce the problem: https://github.com/xujim/testaccessibility

The steps to reproduce the crash

Here is my add to app example to reproduce the crash bug:
https://github.com/xujim/testaccessibility
With this example installed, you just need to "Open Flutter Page", and again and again, then pop back the VC, finally, it crashed.

The Root Cause Analysis

It is caused by the sequence of Setup and Teardown of Rasterizer in the addToApps circumstance.
In the addToApps, we need to push and pop multiple VC, it will lead to setup and teardown of surface more than one time. And the setup and teardown tasks are queued by GPU thread.
However, note that the surface is held by on Engine, and the underlying memory is shared by the tasks(setup and teardown task) in the GPU thread queue. This will cause a situation, that a setup task need to access surface which has just been teardown in the last teardown task. Just as following diagram:
image

Change the Rasterizer::Setup function this way can avoid the crash, even though it might be not the best fix:
image

My Flutter Version

 testaccessibility git:(master) ✗ flutter doctor -v
[✓] Flutter (Channel v1.12.13-hotfixes, v1.12.13+hotfix.8, on Mac OS X 10.14.6
    18G95, locale zh-Hans-CN)
    • Flutter version 1.12.13+hotfix.8 at
      /Users/yujie/Desktop/FlutterWork/flutter_Github
    • Framework revision 0b8abb4724 (4 周前), 2020-02-11 11:44:36 -0800
    • Engine revision e1e6ced81d
    • Dart version 2.7.0
@xujim
Copy link
Author

xujim commented Mar 12, 2020

@xster please take a look

@kangwang1988 kangwang1988 added a: existing-apps Integration with existing apps via the add-to-app flow customer: alibaba engine flutter/engine repository. See also e: labels. c: crash Stack traces logged to the console labels Mar 12, 2020
@kangwang1988
Copy link
Contributor

CC @dnfield

@xster xster added this to the March 2020 milestone Mar 12, 2020
@xster xster added the platform-ios iOS applications specifically label Mar 12, 2020
@xster
Copy link
Member

xster commented Mar 12, 2020

@gaaclarke can you take a look?

@dnfield
Copy link
Contributor

dnfield commented Mar 12, 2020

Does this bug still exist on master?

@xujim
Copy link
Author

xujim commented Mar 12, 2020

I am afraid it still exist

@dnfield
Copy link
Contributor

dnfield commented Mar 12, 2020

Hm. Both the Android and the iOS embedding believe they can return nullptr in CreateRenderingSurface if they have no surface to work with, which can happen if they get that call without having a view to work with (or, before having a view to work with).

That should only be valid if there really is no View to work with. But in this case, there should be.

I haven't traced this all the way through, but it seems like we might not be calling NotifyCreated properly in this case - it also seems like we should not try to setup the rasterizer if there is in fact no surface to work with.

/cc @chinmaygarde @amirh @cyanglaz

@gaaclarke probably has more recent context on this than I do as well but those people might be interested in the platform view/rasterizer aspects.

@xujim xujim changed the title Engine v1.12.13-hotfixes have bug when using AddToApp Engine v1.12.13-hotfixes has bug when using AddToApp Mar 12, 2020
xujim pushed a commit to xujim/engine that referenced this issue Mar 12, 2020
We need to check if surface is nullptr before invoke the setup as well
@gaaclarke
Copy link
Member

stacktrace:

#0	0x00000001092346e8 in flutter::Rasterizer::Setup(std::__1::unique_ptr<flutter::Surface, std::__1::default_delete<flutter::Surface> >) at /Users/aaclarke/dev/engine/src/flutter/shell/common/rasterizer.cc:69
#1	0x0000000109254b4a in flutter::Shell::OnPlatformViewCreated(std::__1::unique_ptr<flutter::Surface, std::__1::default_delete<flutter::Surface> >)::$_8::operator()() at /Users/aaclarke/dev/engine/src/flutter/shell/common/shell.cc:609
#2	0x0000000109248ed1 in auto fml::internal::CopyableLambda<flutter::Shell::OnPlatformViewCreated(std::__1::unique_ptr<flutter::Surface, std::__1::default_delete<flutter::Surface> >)::$_8>::operator()<>() const at /Users/aaclarke/dev/engine/src/flutter/fml/make_copyable.h:24
#3	0x000000010927a10d in decltype(std::__1::forward<fml::internal::CopyableLambda<flutter::Shell::OnPlatformViewCreated(std::__1::unique_ptr<flutter::Surface, std::__1::default_delete<flutter::Surface> >)::$_8>&>(fp)()) std::__1::__invoke<fml::internal::CopyableLambda<flutter::Shell::OnPlatformViewCreated(std::__1::unique_ptr<flutter::Surface, std::__1::default_delete<flutter::Surface> >)::$_8>&>(fml::internal::CopyableLambda<flutter::Shell::OnPlatformViewCreated(std::__1::unique_ptr<flutter::Surface, std::__1::default_delete<flutter::Surface> >)::$_8>&) at /Users/aaclarke/dev/engine/src/buildtools/mac-x64/clang/include/c++/v1/type_traits:4350
#4	0x000000010927a0bd in void std::__1::__invoke_void_return_wrapper<void>::__call<fml::internal::CopyableLambda<flutter::Shell::OnPlatformViewCreated(std::__1::unique_ptr<flutter::Surface, std::__1::default_delete<flutter::Surface> >)::$_8>&>(fml::internal::CopyableLambda<flutter::Shell::OnPlatformViewCreated(std::__1::unique_ptr<flutter::Surface, std::__1::default_delete<flutter::Surface> >)::$_8>&) at /Users/aaclarke/dev/engine/src/buildtools/mac-x64/clang/include/c++/v1/__functional_base:349
#5	0x0000000109279261 in std::__1::__function::__func<fml::internal::CopyableLambda<flutter::Shell::OnPlatformViewCreated(std::__1::unique_ptr<flutter::Surface, std::__1::default_delete<flutter::Surface> >)::$_8>, std::__1::allocator<fml::internal::CopyableLambda<flutter::Shell::OnPlatformViewCreated(std::__1::unique_ptr<flutter::Surface, std::__1::default_delete<flutter::Surface> >)::$_8> >, void ()>::operator()() at /Users/aaclarke/dev/engine/src/buildtools/mac-x64/clang/include/c++/v1/functional:1572
#6	0x00000001090eb595 in std::__1::function<void ()>::operator()() const at /Users/aaclarke/dev/engine/src/buildtools/mac-x64/clang/include/c++/v1/functional:1923
#7	0x00000001090ff483 in fml::MessageLoopImpl::FlushTasks(fml::FlushType) at /Users/aaclarke/dev/engine/src/flutter/fml/message_loop_impl.cc:127
#8	0x00000001090ff36a in fml::MessageLoopImpl::RunExpiredTasksNow() at /Users/aaclarke/dev/engine/src/flutter/fml/message_loop_impl.cc:137
#9	0x0000000109112885 in fml::MessageLoopDarwin::OnTimerFire(__CFRunLoopTimer*, fml::MessageLoopDarwin*) at /Users/aaclarke/dev/engine/src/flutter/fml/platform/darwin/message_loop_darwin.mm:75

@gaaclarke
Copy link
Member

The following assert can fail because platform_view->CreateRenderingSurface() is returning null (as dan pointed out)

--- a/shell/common/platform_view.cc
+++ b/shell/common/platform_view.cc
@@ -74,6 +74,7 @@ void PlatformView::NotifyCreated() {
         latch.Signal();
       });
   latch.Wait();
+  assert(surface);
   delegate_.OnPlatformViewCreated(std::move(surface));
 }

Here is the code where PlatformViewIOS returns nullptr:

std::unique_ptr<Surface> PlatformViewIOS::CreateRenderingSurface() {
  FML_DCHECK(task_runners_.GetGPUTaskRunner()->RunsTasksOnCurrentThread());
  std::lock_guard<std::mutex> guard(ios_surface_mutex_);
  if (!ios_surface_) {
    FML_DLOG(INFO) << "Could not CreateRenderingSurface, this PlatformViewIOS "
                      "has no ViewController.";
    return nullptr;
  }
  return ios_surface_->CreateGPUSurface();
}

callstack:

#4	0x0000000102d30cac in flutter::PlatformView::NotifyCreated() at /Users/aaclarke/dev/engine/src/flutter/shell/common/platform_view.cc:77
#5	0x0000000102b78cd9 in ::-[FlutterViewController surfaceUpdated:](BOOL) at /Users/aaclarke/dev/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm:544
#6	0x0000000102b791a8 in ::-[FlutterViewController viewWillAppear:](BOOL) at /Users/aaclarke/dev/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm:580

If you put a breakpoint on PlatformViewIOS::SetOwnerViewController you'll see this if you push twice, then hit back twice:

(lldb) p this
(flutter::PlatformViewIOS *) $4 = 0x00007fdd73d1ed50
(lldb) po owner_controller.ptr_
<FlutterViewController: 0x7fdd74850800>

(lldb) c
Process 40705 resuming
2020-03-12 09:50:07.218508-0700 Runner[40705:16759644] flutter: Observatory listening on http://127.0.0.1:60959/K4uqwl6Dp7Y=/
(lldb) p this
(flutter::PlatformViewIOS *) $6 = 0x00007fdd73d1ed50
(lldb) po owner_controller.ptr_
<FlutterViewController: 0x7fdd7486d600>

(lldb) c
Process 40705 resuming
(lldb) p this
(flutter::PlatformViewIOS *) $8 = 0x00007fdd73d1ed50
(lldb) po owner_controller.ptr_
<FlutterViewController: 0x7fdd7402e800>

(lldb) c
Process 40705 resuming
(lldb) p this
(flutter::PlatformViewIOS *) $10 = 0x00007fdd73d1ed50
(lldb) po owner_controller.ptr_
 nil
Assertion failed: (surface), function NotifyCreated, file ../../flutter/shell/common/platform_view.cc, line 77.
(lldb) p this
(flutter::PlatformViewIOS *) $12 = 0x00007fdd73d1ed50

So this means the FlutterViewController for the PlatformView is getting set to nil just before it is getting shown on screen and viewWillAppear: is getting called on the FlutterViewController (0x7fdd74850800).

@gaaclarke
Copy link
Member

--- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm
+++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm
@@ -570,6 +570,8 @@ static void sendFakeTouchEvent(FlutterEngine* engine,
 
 - (void)viewWillAppear:(BOOL)animated {
   TRACE_EVENT0("flutter", "viewWillAppear");
+  fml::WeakPtr<flutter::PlatformViewIOS> weakPlatformView = [_engine.get() platformView];
+  assert(weakPlatformView->GetOwnerViewController());
 
   // Send platform settings to Flutter, e.g., platform brightness.
   [self onUserSettingsChanged:nil];

The following assert will trip before the crash happens. This seems to be a problem with presenting a FlutterViewController that has been previously covered up, like in the case of a UINavigationController stack pop.

@jmagman jmagman added this to Awaiting triage in Mobile - iOS engine review via automation Mar 13, 2020
@gaaclarke gaaclarke moved this from Awaiting triage to Engineer reviewed in Mobile - iOS engine review Mar 16, 2020
@gaaclarke
Copy link
Member

Ok, I've looked at the code that reproduces this more closely. testaccessibility is sharing an engine between multiple live FlutterViewControllers. I'd say this isn't a supported flow. I'm not even sure it makes sense. I can make it not crash, but I don't think this is a flow we really want to support. We plan on releasing better guidance around multiple view controllers / engines.

The following change fixes the crash:

--- a/ios/Runner/AppDelegate.m
+++ b/ios/Runner/AppDelegate.m
@@ -54,7 +54,11 @@
     }else if([@"openPage" isEqualToString:call.method]){
 //        FlutterViewController *oldVC = (FlutterViewController *)self.flutterEngine.viewController;
 //        ((void(*)(id, SEL, BOOL))objc_msgSend)(oldVC, NSSelectorFromString(@"surfaceUpdated:"), NO);
-        FlutterViewController *vc = [[FlutterViewController alloc]initWithEngine:self.flutterEngine nibName:nil bundle:nil];
+        FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"io.flutter" project:nil];
+        [engine runWithEntrypoint:nil];
+        [self.class registerWithRegistrar:[engine registrarForPlugin:@"testaccessibility"]];
+        [GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine];
+        FlutterViewController *vc = [[FlutterViewController alloc]initWithEngine:engine nibName:nil bundle:nil];
         UINavigationController *nav = (id)self.window.rootViewController;
         [nav pushViewController:vc animated:YES];
     }else{

@gaaclarke
Copy link
Member

For the record, here is the patch that can make this not crash (but it behaves in a weird way because of the shared engine still):

--- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm
+++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm
@@ -570,6 +570,12 @@ static void sendFakeTouchEvent(FlutterEngine* engine,
 
 - (void)viewWillAppear:(BOOL)animated {
   TRACE_EVENT0("flutter", "viewWillAppear");
+  // If we are presenting a previously viewed FlutterViewController, setup the platform
+  // view again.
+  fml::WeakPtr<flutter::PlatformViewIOS> weakPlatformView = [_engine.get() platformView];
+  if (!weakPlatformView->GetOwnerViewController()) {
+    weakPlatformView->SetOwnerViewController([self getWeakPtr]);
+  }
 
   // Send platform settings to Flutter, e.g., platform brightness.
   [self onUserSettingsChanged:nil];

@xster
Copy link
Member

xster commented Mar 17, 2020

@xujim @kangwang1988, was there a reason why flutter_boost needed to have multiple FlutterViewControllers attached to the same FlutterEngine at the same time rather than detaching the bottom one first before attaching the top one?

Also, please add a flutter_boost test to our testing repository at https://github.com/flutter/tests which runs with each framework PR. This way, we'll make sure we don't break you guys via something in the engine like this (or even by naming another widget Action).

See https://cs.opensource.google/flutter/engine/+/master:testing/scenario_app/ios/Scenarios/ScenariosTests for examples of driving tests from the platform side.

@xujim
Copy link
Author

xujim commented Mar 18, 2020

@gaaclarke @xster Thanks. Currently, concerns on the memory consumption, flutter boost create only one engine and have multiple view controller attach it in turn。However, they are NOT attached to same engine at the same time. It is in turn, that is mean, we only attach the top VC to engine, and the previous VC(covered up) will be detached as soon as the new one is set as OwnerViewController to engine. When the VC is covered, we will try to call surfaceUpdated:NO to tear down the surface, and call surfaceUpdated:YES to setup the new surface for the top VC vice versa.
I think it is reasonable way, because, Flutter VC is just a kind of UI representation layer, and Engine is data layer managing the flutter data, we can use multiple representation layers for one data layer.
Furthermore, sharing engine for VCs can help them share data like message handling, etc

@foxsofter
Copy link

fix_crash

@xster
Copy link
Member

xster commented Mar 18, 2020

@xujim what you're describing sounds reasonable to me. Does it work if you manually set the engine.viewController to nil or to the top vc before the bottom vc gets covered? Seems like that's what https://github.com/xujim/testaccessibility/compare/master...foxsofter:fix_crash?expand=1#diff-a6425004106bb459a3f91283fe144fd2 is doing right?

This bug is essentially asking us to make initWithEngine more ergonomic by having the FlutterViewController remove the previous FlutterViewController from the engine automatically?

@xster
Copy link
Member

xster commented Mar 18, 2020

Posterity: from chatting with @gaaclarke documentation and ergonomics around this may all end up bundled in the #37644 work as well.

@xujim
Copy link
Author

xujim commented Mar 21, 2020

@xster , thanks, fix_crash works for my example, but it needs to hook the UINavigationController, which is not so nice. I finally figured out a workaround, which is better for me——try not call surfaceUpdated:NO for my FlutterViewController. This way, we can make sure the flutter engine holding an ios_surface_ until it is reset by SetOwnerViewController as soon as another FlutterVC is attached to FlutterEngine.

@xujim
Copy link
Author

xujim commented Mar 21, 2020

The cons of my solution is that need to override the viewDidDisappear of FlutterViewController, which will lose the TRACE_EVENT0. So, I do believe @gaaclarke 's words that it is not supported flow by flutter:(
Hopefully, you guys can support this flow, I think it is reasonable.
My code as following:

//in this way, we can call FlutterViewController's parent in FLBFlutterViewContainer
@interface FlutterViewController (bridgeToviewDidDisappear)
- (void)flushOngoingTouches;
- (void)override_viewDidDisappear:(BOOL)animated;
@end

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wincomplete-implementation"
@implementation FlutterViewController (bridgeToviewDidDisappear)
- (void)bridge_viewDidDisappear:(BOOL)animated{
//    TRACE_EVENT0("flutter", "viewDidDisappear");
    [self flushOngoingTouches];
    [super viewDidDisappear:animated];
}
@end

//my VC inherited from FlutterViewController
@implementation FLBFlutterViewContainer 
- (void)viewDidDisappear:(BOOL)animated
{
    [BoostMessageChannel didDisappearPageContainer:^(NSNumber *result) {}
                                                pageName:_name
                                                  params:_params
                                                uniqueId:self.uniqueIDString];

    if (FLUTTER_VC.beingPresented || self.beingDismissed /*|| ![self.uniqueIDString isEqualToString:[(FLBFlutterViewContainer*)FLUTTER_VC uniqueIDString]]*/)
    {
        [FLUTTER_APP resume];
        [(FLBFlutterViewContainer*)FLUTTER_VC surfaceUpdated:YES];
    }

    //call super's super implementation
    [self bridge_viewDidDisappear:animated];
}
@end 

xujim pushed a commit to alibaba/flutter_boost that referenced this issue Mar 21, 2020
xujim pushed a commit to alibaba/flutter_boost that referenced this issue Mar 21, 2020
xujim pushed a commit to alibaba/flutter_boost that referenced this issue Mar 21, 2020
xujim pushed a commit to alibaba/flutter_boost that referenced this issue Mar 21, 2020
kejinlu pushed a commit to alibaba/flutter_boost that referenced this issue Mar 27, 2020
kejinlu pushed a commit to alibaba/flutter_boost that referenced this issue Mar 27, 2020
@lock
Copy link

lock bot commented Apr 4, 2020

This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

@lock lock bot locked and limited conversation to collaborators Apr 4, 2020
@flutter flutter unlocked this conversation Apr 10, 2020
@lock
Copy link

lock bot commented Apr 25, 2020

This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

@lock lock bot locked and limited conversation to collaborators Apr 25, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
a: existing-apps Integration with existing apps via the add-to-app flow c: crash Stack traces logged to the console customer: alibaba engine flutter/engine repository. See also e: labels. platform-ios iOS applications specifically
Projects
Mobile - iOS engine review
  
Engineer reviewed
Development

No branches or pull requests

6 participants