"Remote connection lost." Crush

Issue #35 resolved
Sasha Stavcev
created an issue

After every page load i tried to detect size of document (page) throught JSCallback.

 void loadHandler_OnLoadEnd(object sender, Chromium.Event.CfxOnLoadEndEventArgs e)
        {
            if (e.Frame.IsMain)
            {
                EvaluateJavascript("[document.body.scrollWidth,document.body.scrollHeight]", (value, exception) =>
                {
                       FDocumentWidthOut[0] = value.GetValue(0).IntValue;
                    FDocumentHeightOut[0] = value.GetValue(1).IntValue;
                });
            }
        }

And sometimes it crushed on (sample when rappidily switch url in address bar (LoadUrl) ):

CfrTaskRunner.GetForThread(CfxThreadId.Renderer);

Maybe there is a solution to this? For sample, maybe is it possible to stop the loading previous page and stop OnLoadEnd event of previews page.

I can wrap the problematic code in try/catch block. But then i cannot manipulate with remote process anymore .

Comments (12)

  1. wborgsm

    This is a bug.

    The code line CfrTaskRunner.GetForThread(CfxThreadId.Renderer); runs within a try-catch block. That block should catch any problems with the rpc connection to the render process and return false. The problem: it catches System.IOException which was what would have been thrown a number of commits ago. Now a CfxException is thrown and that will propagate back to your code.

    I'll fix that now.

  2. wborgsm

    Some general thoughts about this: when this bug is fixed, instead of throwing an exception, EvaluateJavascript will return false if the render process goes away before the script can be evaluated. So this does not really solve the problem you mention: instead of try/catch, you will have to check the return value for false. There is really no way around this: if the browser navigates away and the render process gets killed before the script can be evaluated, then the evaluation must fail.

    So you have to implement one of the following solutions:

    1) Check if EvaluateJavascript returns false and/or if the returned CfrV8Value is null (both mean evaluation failed), and have your code acknowledge the fact that it couldn't obtain the document size, or

    2) Make sure in your code that the browser can't navigate away in the time between EvaluateJavascript is called and the response is received. This of course can be tricky, specially if you have to provide for clicks in the page. And as far as I can see, it doesn't solve the problem completely: the render process might still crash while evaluation is running.

  3. Sasha Stavcev reporter

    Thank you!

    It really helped me to partially solve a problem.

    I also found a strange behavior in the code :
    event renderProcessHandler_OnBrowserDestroyed occurs not always
    and renderProcessHandler_OnBrowserCreated occurs always but sometimes in killed process (??).

    Sometimes, i get a new exceptions when creating CfrApp (I think it also occurs in killed process).

      app = new CfrApp ();
    

    Or in next lines when attach handlers to this remote (killed) app.
    Wrapping this lines in try / catch too solve my problem. I hope that the final))

    I also have to say that this happens quite rarely, and only when rapidly change url address (to quickly create / delete the browser).

    Oh.. finally I use CfrOnLoadEndEventArgs (instead of CfxOnLoadEndEventArgs) event to set remote browser and js callback. and don't use renderProcessHandler_OnBrowserDestroyed and renderProcessHandler_OnBrowserCreated.

    void loadHandler_OnLoadEnd(object sender, CfrOnLoadEndEventArgs e)
            {
                if (e.Frame.IsMain ...)
                {
                    SetRemoteBrowser..
                    JSGetSize..
                }
            }
    

    It's seems like a dirty hacks for me - i don't know why and how it works.

  4. Sasha Stavcev reporter

    Oh.. i have news.

    In release version of my app CfrOnLoadStartEvent occurs no always..
    It's compiler's optimization ..

    To prevent that i save every new RenderProcess to static List...

  5. wborgsm

    It's hard to say anything without knowing your code, but I don't think the compiler would optimize something away that belongs to your application logic?

    Using the remote layer is kind of tricky because of the render process getting swapped... usually it should be safe as long as you are within the scope of a callback event from the render process, but if you keep Cfr... objects for later use outside of those callbacks, almost everything must be guarded with try/catch.

    Take a look into RenderProcessHandler, JSProperty and it's derived classes for an example implementation of code working with the remote layer. It uses the CfrRenderProcessHandler.OnContextCreated callback event in order to add js objects and functions to a browser's global object.

  6. wborgsm

    Ok, the line that calls app = new CfrApp() can throw because it is not within the scope of a try/catch. This is the same problem as you mentioned for the "renderProcessHandler_OnBrowserCreated occurs always but sometimes in killed process" - the render process gets killed after making the rpc callback into the browser process but before the callback is finished. I am going to look into it.

  7. wborgsm

    I just added a JSFunction that sleeps 5 seconds on a render process callback. If I load a new page while the callback is sleeping, accessing the callback event args after the sleep throws because the process got killed in the meanwhile. Of course.

    Which means, the way it is working now each and every statement in a callback that accesses the Cfr... proxy objects should be in the scope of an application defined try/catch if the application doesn't prevent navigation. I am going to look into handling this at the library level.

  8. wborgsm

    I think I found a solution:

    • CfrRenderProcessHandler.OnBrowserCreated is called before the old process gets killed. This is an opportunity to suspend all callbacks from the render process into the browser process.

    • So when the browser process receives a CfrRenderProcessHandler.OnBrowserCreated event, it checks if the browser was created on a new render process. If so, the application waits for all running remote callbacks to finish, if any, and then suspends the execution of callbacks.

    • For an embedding application this means: if a remote callback begins, the framework will make sure the process is kept alive for the time of the callback keeping the Cfr... proxy objects valid and accessible without throwing.

    • If a render process is about to be killed, then any pending but not yet initialized callbacks from that process are cancelled.

    • If a page load causes a render process to be killed, then the page load is delayed until no more remote callbacks from that process are running.

    • This is implemented at the level of the ChromiumWebBrowser library, not the ChromiumFX library.

    The relevant commits are #f45f806 and #79ae1d9. It can be tested by triggering a 5 second sleep callback (misc menu) and loading a new page while the callback is running, or trigger the remote layer stress test in the misc menu and load a new page.

  9. Log in to comment