Skip to content
This repository has been archived by the owner on Dec 5, 2019. It is now read-only.

Crash recovery sets recovered server port to 0, resulting in about:blank page load #248

Open
kristfal opened this issue Jun 6, 2016 · 14 comments

Comments

@kristfal
Copy link

kristfal commented Jun 6, 2016

Hey,

running version 0.6.9 of this plugin, and the performance improvements are great over the regular UIWebView.

However, crash recovery seems to be spotty as it occasionally sets the wrong recovery URL.

So, when to forcing a memory warning crash, this happens:

Native Xcode console:

2016-06-06 11:26:17.766 Embark[1969:433599] Received memory warning.
2016-06-06 11:27:19.478 Embark[1969:433599] THREAD WARNING: ['Compass'] took '32.695068' ms. Plugin should use a background thread.
2016-06-06 11:27:45.175 Embark[1969:433599] heading STOPPED
2016-06-06 11:27:46.855 Embark[1969:433599] WkWebView crash detected, recovering... 
2016-06-06 11:27:46.895 Embark[1969:433599] Apache Cordova native platform version 3.9.2 is starting.
2016-06-06 11:27:46.895 Embark[1969:433599] Multi-tasking -> Device: YES, App: YES
2016-06-06 11:27:47.958 Embark[1969:433599] Using a WKWebView
2016-06-06 11:27:48.292 Embark[1969:433599] Starting Facebook Connect plugin
2016-06-06 11:27:48.294 Embark[1969:433599] [CDVTimer][facebookconnectplugin] 3.773987ms
2016-06-06 11:27:48.303 Embark[1969:433599] [CDVTimer][file] 6.096005ms
2016-06-06 11:27:48.317 Embark[1969:433599] [CDVTimer][intercom] 8.639038ms
2016-06-06 11:27:48.369 Embark[1969:433599] [CDVTimer][splashscreen] 51.909983ms
2016-06-06 11:27:48.378 Embark[1969:433599] [CDVTimer][crashlytics] 7.184029ms
2016-06-06 11:27:48.388 Embark[1969:433599] [CDVTimer][keyboard] 8.794963ms
2016-06-06 11:27:48.516 Embark[1969:433599] [CDVTimer][statusbar] 126.800001ms
2016-06-06 11:27:48.517 Embark[1969:433599] [CDVTimer][TotalPluginStartup] 226.468980ms
2016-06-06 11:28:02.057 Embark[1969:433599] - CDVBackgroundGeoLocation resume

So the crash is detected, the webview is reloading and Cordova is restarting. Great. However, the web-page URL opened after recovery is:

> window.location.href 
 "about:blank" = $1

A restart of the app fixes the situation and gives the regular APP url:

> window.location.href
http://localhost:12344/index.html

Now, the interesting thing is that while being in the about:blank state, calling location.href = 'http://localhost:12344/index.html'; from the safari javascript console loads the correct page and the app continues running after the recovery as expected.

I an by no means a comfortable with ObjC, so would really appreciate if someone could look into this.

Related: #223

@kristfal
Copy link
Author

kristfal commented Jun 6, 2016

Update: It seems like the server port isn't passed along to the recovered view:

Added logging:

- (void)loadURL:(NSURL*)URL
{
    self.alreadyLoaded = true;
    // /////////////////
    [CDVUserAgentUtil acquireLock:^(NSInteger lockToken) {
      _userAgentLockToken = lockToken;
      [CDVUserAgentUtil setUserAgent:self.userAgent lockToken:lockToken];
      NSURLRequest* appReq = [NSURLRequest requestWithURL:URL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20.0];
      [self.wkWebView loadRequest:appReq];

      NSLog(@"url = %@", URL);
    }];

}

Returns:

url = http://localhost:0/index.html

@kristfal kristfal changed the title Crash recovery sets recovered page URL to about:blank Crash recovery sets recovered server port to 0, resulting in about:blank page load Jun 6, 2016
@kristfal
Copy link
Author

kristfal commented Jun 6, 2016

This fix to createWindowAndStartWebServer seems to solve the issue, but I haven't tested very much:

    // don't restart the webserver if we don't have to (fi. after a crash, see #223)
    if (_webServer != nil && [_webServer isRunning]) {
        int port = [[_webServerOptions objectForKey:@"Port"] intValue]; // Get web server port from options
        [myMainViewController setServerPort:port];
        return;
    }

Someone with objC skills should probably review and create a PR.

@kristfal
Copy link
Author

kristfal commented Jun 6, 2016

Update: Despite setting the correct port number, I'm still seeing blank pages on recovery occasionally. The fix above is not fully functional.

@matrixreal
Copy link

great ... i'm looking on it

@gregavola
Copy link

@kristfal How can you get the WKWebview to crash?

@kristfal
Copy link
Author

kristfal commented Jul 26, 2016

@gregavola I've found two reliable options. They only work on the device:

The bad way: Open a lot of other heavy apps after opening your app, then open your app. This usually takes around 20 apps on my iPhone 6. Open too many apps and you will get terminated while running in the background. Too few and your webview won't crash.

The less bad way: Open Safari on your Mac and inspect your webview in the debugger. Select the 'Timelines' tab and start recording. Quickly tap around on a few buttons or other places in the app that trigger methods. The app should be non-responsive while recording.

Stop recording after around 5-10 seconds. The webview should now crash. If you stop recording too soon, the webview will not crash. If you stop recording too late, the entire app will crash, so try different durations as the duration varies depending on device and number methods called during recording.

I have found a very crude workaround for the white screen of death / infinite loading screens.

I haven't made a PR because the solution is rather bad as it falls back to crashing the application entirely if it fails to recover properly. Here is the changed code in MyMainViewController.m:

- (void) applicationDidBecomeActive: (NSNotification*)notification
{
    [self performSelector:@selector(recoverFromCrash) withObject:nil afterDelay:0.5];
    [self performSelector:@selector(recoverFromCrashFallback) withObject:nil afterDelay:3.5];
}

- (void)recoverFromCrash
{
    NSString* title = self.wkWebView.title;
    NSLog(@"webview title: %@", title);
    if ((title == nil) || [title isEqualToString:@""]) {
        (void)self.wkWebView.reload;
    }
}

- (void) recoverFromCrashFallback
{
    NSString* title = self.wkWebView.title;
    NSLog(@"webview title: %@", title);
    if ((title == nil) || [title isEqualToString:@""]) {
        @throw NSInternalInconsistencyException;
    }
}

- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView
{
    (void)self.wkWebView.reload;
}

And in the viewDidLoad method, change the crash recovery timer to:

  // Start timer which periodically checks whether the app is alive
  if (![self settingForKey:@"DisableCrashRecovery"] || ![[self settingForKey:@"DisableCrashRecovery"] boolValue]) {
     _crashRecoveryTimer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(recoverFromCrashFallback) userInfo:nil repeats:YES];
  }

In short, this is happening:

If the app is running in the foreground, crash recovery is working very well via webViewWebContentProcessDidTerminate. It simply reloads the wkwebview. No new instance of the webview is created, so no more memory issues (the current implementation is leaking).

When the app is returning from the background, it is prone to crashing the webview without calling webViewWebContentProcessDidTerminate. The app will attempt to recover with a reload of the webview via applicationDidBecomeActive, but if it fails (which seems to happen around 1/10 times), it will crash the entire app to prevent getting stuck in an infinite loading loop.

The periodic timer is the final fallback as we have some times experienced that applicationDidBecomeActive is not called when quickly exiting and entering an application. It might be skipable, but I'd rather have the app crash than getting stuck.

This solution is acceptable for our production needs, but I wouldn't call it good. Use with caution, and please improve on it if you are able.

@gregavola
Copy link

@kristfal Thanks for this detail! My biggest concern is that the WKWebview seems to only crash when a ViewControllers is presented above the WKWebView (like Camera view), and the WKWebView Crashes behind the screens, due to poor memory - have you seen this?

@kristfal
Copy link
Author

@gregavola Not quite sure if I am following. Do you mean that the app is crashing after taking a photo with the Cordova camera plugin?

@gregavola
Copy link

@kristfal In situations where there is memory pressure - when calling the Camera Plugin, the WKWebview get's placed in the background. Because memory is low, iOS will automatically kill processed in the background (thus killing the WKWebview Pid). This happens whenever the WkWebview is placed in the BG and memory is low.

@kristfal
Copy link
Author

@gregavola Ah, now I follow. I've seen that happening as well. In that case, with my fix, the webView reloads and recovers without crashing the app. However, the captured photo will not be available.

For photo captures, I really recommend to upload the photo to a remote server, and resize it on the server before showing it in the app (do not render the photo directly from a local fileURI).

I used to see a lot of crashes with the cordova photo plugin when I attempted to show the photo directly from the captured fileURI. These crashes were resolved when I uploaded the photo first.

@gregavola
Copy link

@kristfal We don't do any resizing on the app, it's just open Camera, then back to the app and wait for the callback. It seems to kill the PID everytime on some devices.

@gregavola
Copy link

@kristfal Your last comment - was this an actual crash of camera, or the just restart of the app?

@kristfal
Copy link
Author

@gregavola That is the webview crashing as it attempts to render the image which was captured by the camera

@gregavola
Copy link

@kristfal How did you determine that it's the image that crashes the wkwebview?

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants