Bug 148538

Summary: Web Inspector: InspectorController should support multiple frontend channels
Product: WebKit Reporter: BJ Burg <bburg>
Component: Web InspectorAssignee: BJ Burg <bburg>
Status: RESOLVED FIXED    
Severity: Normal CC: bburg, commit-queue, graouts, joepeck, mattbaker, nvasilyev, timothy, webkit-bug-importer
Priority: P2 Keywords: InRadar
Version: WebKit Nightly Build   
Hardware: All   
OS: All   
Bug Depends on: 148785    
Bug Blocks: 148481, 148492    
Attachments:
Description Flags
Proposed Fix
none
Proposed Fix (EWS)
none
Proposed Fix (EWS)
none
Proposed Fix (for EWS)
none
Proposed Fix (for Win EWS)
none
Proposed Fix (for Win EWS)
none
Proposed Fix (Fix Crash) none

Description BJ Burg 2015-08-27 14:02:09 PDT
First step.
Comment 1 Radar WebKit Bug Importer 2015-08-27 14:03:02 PDT
<rdar://problem/22462778>
Comment 2 BJ Burg 2015-08-28 16:02:31 PDT
Created attachment 260191 [details]
Proposed Fix

Here we go.. expecting a few rounds with EWS.
Comment 3 Joseph Pecoraro 2015-08-28 16:50:16 PDT
Comment on attachment 260191 [details]
Proposed Fix

View in context: https://bugs.webkit.org/attachment.cgi?id=260191&action=review

Leaving the review flag for my questions about first frontend connection, last frontend disconnection.

> Source/JavaScriptCore/CMakeLists.txt:326
>      inspector/InspectorAgentRegistry.cpp
> +    inspector/InspectorFrontendRouter.cpp
>      inspector/InspectorBackendDispatcher.cpp

Nit: `sort` order.

> Source/JavaScriptCore/inspector/InspectorFrontendRouter.h:40
> +    bool hasFrontends() const { return m_connections.size() > 0; }

Would !Vector::isEmpty be clearer?

> Source/JavaScriptCore/inspector/InspectorFrontendRouter.h:55
> +    Vector<FrontendChannel*, 2> m_connections;

Nice use of size hint!

> Source/JavaScriptCore/inspector/JSGlobalObjectInspectorController.cpp:116
> +    m_agents.didCreateFrontendAndBackend(frontendChannel, &m_backendDispatcher.get());

Is the plan that this should happen when adding the first frontend?

> Source/JavaScriptCore/inspector/JSGlobalObjectInspectorController.cpp:130
> +    m_agents.willDestroyFrontendAndBackend(DisconnectReason::InspectorDestroyed);

Is the plan that this should happen when removing the last frontend?

> Source/JavaScriptCore/inspector/JSGlobalObjectInspectorController.cpp:-154
> -    if (!m_frontendChannel)
> -        return;

I think the equivalent of this would be:

    if (!m_frontendRouter.hasFrontends())
        return;

I'm not quite sure of the consequences here.

> Source/JavaScriptCore/inspector/JSGlobalObjectInspectorController.cpp:276
> -    if (m_frontendChannel)
> -        agent->didCreateFrontendAndBackend(m_frontendChannel, m_backendDispatcher.get());
> +    agent->didCreateFrontendAndBackend(m_frontendRouter->leakChannel(), &m_backendDispatcher.get());
>  
>      m_agents.appendExtraAgent(WTF::move(agent));
>  
> -    if (m_frontendChannel)
> -        m_inspectorAgent->activateExtraDomain(domainName);
> +    m_inspectorAgent->activateExtraDomain(domainName);

Same here. The consequences here seem to just be that you might be doing work in an agent that gets repeated when connectFrontend happens.

> Source/WebCore/WebCore.vcxproj/WebCore.vcxproj:20398
>      <ClInclude Include="..\ForwardingHeaders\inspector\InspectorFrontendChannel.h" />
> +    <ClInclude Include="..\ForwardingHeaders\inspector\InspectorFrontendRouter.h" />
>      <ClInclude Include="..\ForwardingHeaders\inspector\InspectorBackendDispatchers.h" />
>      <ClInclude Include="..\ForwardingHeaders\inspector\InspectorFrontendDispatchers.h" />

You could correct the sort order while you are here!

> Source/WebCore/inspector/InspectorController.cpp:266
>  #if ENABLE(REMOTE_INSPECTOR)
> -    if (!hasRemoteFrontend())
> +    if (!m_frontendRouter->hasRemoteFrontend() && didHaveRemoteFrontend)
>          m_page.remoteInspectorInformationDidChange();
>  #endif

The purpose of this here is to inform any remote debuggers if this debuggable has a debugger or not. So I think this may have been flawed originally, and it probably should be the same as first/last disconnect conditions. Or for now:

    if (m_frontendRouter->hasFrontends() && didHaveFrontends())

> Source/WebCore/testing/Internals.cpp:1736
> -    bool isAutomaticInspection = false;
>      m_frontendChannel = std::make_unique<InspectorFrontendChannelDummy>(frontendPage);
> -    page->inspectorController().connectFrontend(m_frontendChannel.get(), isAutomaticInspection);
> +    page->inspectorController().connectFrontend(m_frontendChannel.get());

Arg, I hate default parameters. It is easy to make mistakes. But this seems reasonable =)
Comment 4 BJ Burg 2015-08-28 19:48:15 PDT
Created attachment 260204 [details]
Proposed Fix (EWS)
Comment 5 BJ Burg 2015-08-28 19:56:18 PDT
Comment on attachment 260191 [details]
Proposed Fix

View in context: https://bugs.webkit.org/attachment.cgi?id=260191&action=review

>> Source/JavaScriptCore/inspector/JSGlobalObjectInspectorController.cpp:116
>> +    m_agents.didCreateFrontendAndBackend(frontendChannel, &m_backendDispatcher.get());
> 
> Is the plan that this should happen when adding the first frontend?

Correct. I tried doing that earlier, but was hitting other issues so I reverted it to help isolate the problem. I will try altering the condition to what you said again, now that everything else is working.

>> Source/JavaScriptCore/inspector/JSGlobalObjectInspectorController.cpp:130
>> +    m_agents.willDestroyFrontendAndBackend(DisconnectReason::InspectorDestroyed);
> 
> Is the plan that this should happen when removing the last frontend?

Yup, see above.

>> Source/JavaScriptCore/inspector/JSGlobalObjectInspectorController.cpp:-154
>> -        return;
> 
> I think the equivalent of this would be:
> 
>     if (!m_frontendRouter.hasFrontends())
>         return;
> 
> I'm not quite sure of the consequences here.

Is this code related to automatic inspection and auto-resuming? Will investigate.

>> Source/JavaScriptCore/inspector/JSGlobalObjectInspectorController.cpp:276
>> +    m_inspectorAgent->activateExtraDomain(domainName);
> 
> Same here. The consequences here seem to just be that you might be doing work in an agent that gets repeated when connectFrontend happens.

How will we know to activate the domain once a frontend connects? Or are these always done together?

>> Source/WebCore/inspector/InspectorController.cpp:266
>>  #endif
> 
> The purpose of this here is to inform any remote debuggers if this debuggable has a debugger or not. So I think this may have been flawed originally, and it probably should be the same as first/last disconnect conditions. Or for now:
> 
>     if (m_frontendRouter->hasFrontends() && didHaveFrontends())

yeah, this condition will change once we want to allow multiple remotes to actually connect. But for now, when should it notify: when a frontend has become detached or when it has become attached?

>> Source/WebCore/testing/Internals.cpp:1736
>> +    page->inspectorController().connectFrontend(m_frontendChannel.get());
> 
> Arg, I hate default parameters. It is easy to make mistakes. But this seems reasonable =)

It is used so infrequently I thought it was appropriate here.
Comment 6 BJ Burg 2015-08-28 21:48:06 PDT
Created attachment 260214 [details]
Proposed Fix (EWS)
Comment 7 Joseph Pecoraro 2015-08-29 01:15:34 PDT
(In reply to comment #5)
> Comment on attachment 260191 [details]
> Proposed Fix
> 
> View in context:
> https://bugs.webkit.org/attachment.cgi?id=260191&action=review
> 
> >> Source/JavaScriptCore/inspector/JSGlobalObjectInspectorController.cpp:116
> >> +    m_agents.didCreateFrontendAndBackend(frontendChannel, &m_backendDispatcher.get());
> > 
> > Is the plan that this should happen when adding the first frontend?
> 
> Correct. I tried doing that earlier, but was hitting other issues so I
> reverted it to help isolate the problem. I will try altering the condition
> to what you said again, now that everything else is working.
> 
> >> Source/JavaScriptCore/inspector/JSGlobalObjectInspectorController.cpp:130
> >> +    m_agents.willDestroyFrontendAndBackend(DisconnectReason::InspectorDestroyed);
> > 
> > Is the plan that this should happen when removing the last frontend?
> 
> Yup, see above.
> 
> >> Source/JavaScriptCore/inspector/JSGlobalObjectInspectorController.cpp:-154
> >> -        return;
> > 
> > I think the equivalent of this would be:
> > 
> >     if (!m_frontendRouter.hasFrontends())
> >         return;
> > 
> > I'm not quite sure of the consequences here.
> 
> Is this code related to automatic inspection and auto-resuming? Will
> investigate.

Yes, it is. Ping me on IRC if needed and I can show you how to test this / explain how it works.

> >> Source/JavaScriptCore/inspector/JSGlobalObjectInspectorController.cpp:276
> >> +    m_inspectorAgent->activateExtraDomain(domainName);
> > 
> > Same here. The consequences here seem to just be that you might be doing work in an agent that gets repeated when connectFrontend happens.
> 
> How will we know to activate the domain once a frontend connects? Or are
> these always done together?

The normal path is inside JSGlobalObjectInspectorController::connectFrontend (line 123). When a frontend connects we tell it about all the extra domains:

    m_inspectorAgent->activateExtraDomains(m_agents.extraDomains());

This path here (line 276) is handling when an extra domain is activated and a frontend is already connected, it does the normal agent setup and informs the frontend about this new extra domain.


> >> Source/WebCore/inspector/InspectorController.cpp:266
> >>  #endif
> > 
> > The purpose of this here is to inform any remote debuggers if this debuggable has a debugger or not. So I think this may have been flawed originally, and it probably should be the same as first/last disconnect conditions. Or for now:
> > 
> >     if (m_frontendRouter->hasFrontends() && didHaveFrontends())
> 
> yeah, this condition will change once we want to allow multiple remotes to
> actually connect. But for now, when should it notify: when a frontend has
> become detached or when it has become attached?

Given how uncommon a frontend connecting/disconnecting is, and how likely it is in practice to actually change some information about the remote debuggable it wouldn't be unreasonable to just always do it when a frontend connects/disconnects.
Comment 8 BJ Burg 2015-08-29 09:02:57 PDT
The new patch addresses these comments, except the vcxproj reorderings. I hope we nuke those files soon anyway.
Comment 9 Joseph Pecoraro 2015-09-02 18:24:37 PDT
Comment on attachment 260214 [details]
Proposed Fix (EWS)

View in context: https://bugs.webkit.org/attachment.cgi?id=260214&action=review

r=me.

I think it might be nicer for clarity to have distinct methods for the code that gets run in the distinct cases.

    Controller::connectFrontend
    Controller::disconnectFrontend

    Controller::firstFrontendConnected (setup?)
    Controller::lastFrontendDisconnected (teardown?)

But it may just be diff ugliness that makes it harder to follow this.

> Source/JavaScriptCore/inspector/JSGlobalObjectInspectorController.cpp:-154
> -    if (!m_frontendChannel)
> -        return;

I still think this can be if (!hasFrontends).

> Source/JavaScriptCore/inspector/JSGlobalObjectInspectorController.cpp:-266
> -    if (m_frontendChannel)
> -        agent->didCreateFrontendAndBackend(m_frontendChannel, m_backendDispatcher.get());

Likewise. if (hasFrontend)

> Source/JavaScriptCore/inspector/JSGlobalObjectInspectorController.cpp:-271
> -    if (m_frontendChannel)
> -        m_inspectorAgent->activateExtraDomain(domainName);

Likewise. if (hasFrontend)

> Source/WebCore/inspector/InspectorController.cpp:272
>  #if ENABLE(REMOTE_INSPECTOR)
> -    if (!hasRemoteFrontend())
> +    if (!m_frontendRouter->hasFrontends() && didHaveRemoteFrontend)
>          m_page.remoteInspectorInformationDidChange();
> +#else
> +    UNUSED_PARAM(didHaveRemoteFrontend);
>  #endif
> +}

Lets simplify and just always call m_page.remoteInspectorInformationDidChange if we are removing the last frontend.
Comment 10 BJ Burg 2015-09-03 15:31:13 PDT
Created attachment 260530 [details]
Proposed Fix (for EWS)
Comment 11 BJ Burg 2015-09-03 16:12:10 PDT
Created attachment 260534 [details]
Proposed Fix (for Win EWS)
Comment 12 BJ Burg 2015-09-03 17:13:31 PDT
Created attachment 260541 [details]
Proposed Fix (for Win EWS)
Comment 13 WebKit Commit Bot 2015-09-03 20:58:50 PDT
Comment on attachment 260541 [details]
Proposed Fix (for Win EWS)

Clearing flags on attachment: 260541

Committed r189338: <http://trac.webkit.org/changeset/189338>
Comment 14 WebKit Commit Bot 2015-09-03 20:58:55 PDT
All reviewed patches have been landed.  Closing bug.
Comment 15 WebKit Commit Bot 2015-09-03 21:52:09 PDT
Re-opened since this is blocked by bug 148785
Comment 16 Joseph Pecoraro 2015-09-03 21:55:48 PDT
Bots are seeing ASSERTs:

ASSERTION FAILED: m_frontendRouter->hasFrontends()
/Volumes/Data/slave/yosemite-debug/build/Source/WebCore/inspector/WorkerInspectorController.cpp(130) : void WebCore::WorkerInspectorController::disconnectFrontend(Inspector::DisconnectReason)
1   0x114fb7190 WTFCrash
2   0x118b6c8eb WebCore::WorkerInspectorController::disconnectFrontend(Inspector::DisconnectReason)
3   0x118b6c627 WebCore::WorkerInspectorController::~WorkerInspectorController()
4   0x118b6caa5 WebCore::WorkerInspectorController::~WorkerInspectorController()
5   0x118b6cac9 WebCore::WorkerInspectorController::~WorkerInspectorController()
6   0x118b61844 WebCore::WorkerGlobalScope::~WorkerGlobalScope()
7   0x116e6e825 WebCore::DedicatedWorkerGlobalScope::~DedicatedWorkerGlobalScope()
8   0x116e6e845 WebCore::DedicatedWorkerGlobalScope::~DedicatedWorkerGlobalScope()
9   0x116e6e899 WebCore::DedicatedWorkerGlobalScope::~DedicatedWorkerGlobalScope()
10  0x116e70103 WTF::RefCounted<WebCore::WorkerGlobalScope>::deref()
11  0x117dc0f11 void WTF::derefIfNotNull<WebCore::WorkerGlobalScope>(WebCore::WorkerGlobalScope*)
12  0x118b91757 WTF::RefPtr<WebCore::WorkerGlobalScope>::operator=(std::nullptr_t)
13  0x118b8dc10 WebCore::WorkerThread::workerThread()
14  0x118b8d6e5 WebCore::WorkerThread::workerThreadStart(void*)
15  0x11501fd89 WTF::createThread(void (*)(void*), void*, char const*)::$_0::operator()() const
16  0x11501fd5c std::__1::__function::__func<WTF::createThread(void (*)(void*), void*, char const*)::$_0, std::__1::allocator<WTF::createThread(void (*)(void*), void*, char const*)::$_0>, void ()>::operator()()
17  0x114aa4e2a std::__1::function<void ()>::operator()() const
18  0x11501ebae WTF::threadEntryPoint(void*)
19  0x115020321 WTF::wtfThreadEntryPoint(void*)
20  0x7fff82a6a05a _pthread_body
21  0x7fff82a69fd7 _pthread_body
22  0x7fff82a673ed thread_start
Comment 17 BJ Burg 2015-09-04 11:41:14 PDT
Created attachment 260605 [details]
Proposed Fix (Fix Crash)

Turns out we were unconditionally disconnecting worker controller's frontend channel, when this work was already being done when the workers were spun down.
Comment 18 WebKit Commit Bot 2015-09-04 13:00:30 PDT
Comment on attachment 260605 [details]
Proposed Fix (Fix Crash)

Clearing flags on attachment: 260605

Committed r189375: <http://trac.webkit.org/changeset/189375>
Comment 19 WebKit Commit Bot 2015-09-04 13:00:34 PDT
All reviewed patches have been landed.  Closing bug.