Description: Move WorkerThreadableLoader internal classes to Oilpan heap
This CL splits WorkerThreadableLoader::MainThreadBridgeBase (and subclasses) into the following on-heap classes.
- TaskForwarder and subclasses Lives in the main thread and forwards tasks to the worker thread. - Bridge Lives in the worker thread - Peer Lives in the worker thread and forwards notifications from the loader living in the main thread to ThreadableLoaderClientWrapper living in the worker thread with a TaskForwarder.
Pointers to an object living in the other thread are held as CrossThreadPersistents (e.g., Bridge::m_peer).
Affected files (+403, -275 lines): M third_party/WebKit/Source/core/loader/WorkerThreadableLoader.h M third_party/WebKit/Source/core/loader/WorkerThreadableLoader.cpp
har...@chromium.org
unread,
Jul 21, 2016, 6:22:29 AM7/21/16
Reply to author
Sign in to reply to author
Forward
Sign in to forward
Delete
You do not have permission to delete messages in this group
Copy link
Report message
Sign in to report message
Show original message
Either email addresses are anonymous for this group or you need the view member email addresses permission to view the original message
Is it guaranteed that WorkerThreadableLoader gets destroyed outside Oilpan's GC? Otherwise, it would be problematic to touch on-heap objects in m_bridge->destroy().
On 2016/07/21 10:22:29, haraken wrote: > > Is it guaranteed that WorkerThreadableLoader gets destroyed outside Oilpan's GC? > Otherwise, it would be problematic to touch on-heap objects in > m_bridge->destroy().
No, it's not guaranteed. I thought that calling destroy is safe because m_bridge is held as Persistent and hence is never unreachable here. Is my understanding wrong?
On 2016/07/21 10:22:29, haraken wrote: > > Don't we need to call: > > m_forwarder->abort(); > m_clientWrapper = nullptr; > > in cancel()?
We don't need to call them when this function is called from Bridge::cancel. Peer::contextDestroyed also calls this function but it calls them by itself.
We might want to have a figure (as a comment in code) that illustrates the lifetime relationship between Peer, Bridge, MainThredableLoader, ThreadableLoaderClient, ThreadableLoaderClientWrapper and WorkerThreadableLoader.
On 2016/07/21 11:44:55, yhirano wrote: > On 2016/07/21 10:22:29, haraken wrote: > >
> > Shall we use wrapCrossThreadPersistent()? > > > > The same comment for other parts in this file. > > > > Let me confirm: Are you suggesting > > (1) { > DCHECK(isMainThread()); > CrossThreadPersistent<ThreadableLoaderClientWrapper> clientWrapper = > wrapCrossThreadPersistent(m_clientWrapper.get()); > if (!clientWrapper) > return; > forwardTask(createCrossThreadTask( > ..., clientWrapper, ...)); > } > > or > > (2) { > DCHECK(isMainThread()); > forwardTask(createCrossThreadTask( > ..., wrapCrossThreadPersistent(m_clientWrapper.get()), ...)); > } > > ? > > (1) doens't have a merit, and (2) may bind nullptr for the task as > m_clientWrapper is a weak pointer.
I was suggesting 2).
if (!m_clientWrapper) return; createCrossThreadTask(..., wrapCrossThreadPersistent(m_clientWrapper.get())
On 2016/07/21 10:56:09, yhirano wrote: > On 2016/07/21 10:22:29, haraken wrote: > >
> > Does this need to be a weak pointer? > > Having this as a strong persistent may form a reference cycle (if/when we hold > ThreadableLoaderClientWrapper::m_client as a Member). > > I made worker->main cross thread persistent pointers strong and main->worker > pointers weak, but I'm not 100% sure it's the right pattern.
I want to confirm two things:
- Even if we use a strong reference and create a cycle, it won't be a problem because the main->worker strong reference is explicitly broken when WorkerContextLifecycleObserver::contextDestroyed is called, right?
- Who guarantees that ThreadableLoaderClientWrapper is kept alive while Peer is holding a weak reference to the ThreadableLoaderClientWrapper?
> We might want to have a figure (as a comment in code) that illustrates the > lifetime relationship between Peer, Bridge, MainThredableLoader, > ThreadableLoaderClient, ThreadableLoaderClientWrapper and > WorkerThreadableLoader.
That's a good idea, but can you wait until the series of CLs are landed? The picture will change when I make ThreadableLoader GCed. I added a TODO comment.
On 2016/07/21 15:56:14, haraken wrote: > On 2016/07/21 10:56:09, yhirano wrote: > > On 2016/07/21 10:22:29, haraken wrote: > > > > > > Does this need to be a weak pointer? > > > > Having this as a strong persistent may form a reference cycle (if/when we hold > > ThreadableLoaderClientWrapper::m_client as a Member). > > > > I made worker->main cross thread persistent pointers strong and main->worker > > pointers weak, but I'm not 100% sure it's the right pattern. > > I want to confirm two things: > > - Even if we use a strong reference and create a cycle, it won't be a problem > because the main->worker strong reference is explicitly broken when > WorkerContextLifecycleObserver::contextDestroyed is called, right? >
contextDestroyed is called at the worker thread termination, so the loop will eventually break.
> - Who guarantees that ThreadableLoaderClientWrapper is kept alive while Peer is > holding a weak reference to the ThreadableLoaderClientWrapper? >
It's WorkerThreadableLoader. A WorkerThreadableLoader has a ThreadableLoaderClientWrapper as Persistent. I will replace the Persistent with Member in a later CL.
When ThreadableLoader is moved to Oilpan heap, users will expect there is a strong member reference or weak reference from a ThreadableLoader to its client (I'm not sure which is correct right now). Users won't expect there is a strong persistent reference from a ThreadableLoader to its client, I think.
Once we move ThreadableLoader to Oilpan's heap, we can just use strong references (Member<>s). Cycles between Member<>s is not problematic at all.
In this CL, strong or weak wouldn't matter in practice. I'd slightly prefer using a strong reference because then we can get rid of if(!clientWrapper) check from a bunch of methods. We anyway need to get rid of the checks when we change the reference to Member<> in the next CL. It looks simpler to use a strong reference from the beginning.
Just help me understand: Do we need to observe contextDestroyed()? When the worker thread is shut down, Bridge::destroy() should have already been called. Bridge::destroy() calls Peer:cancel(), so we won't need to call cancel() here.
Also the worker thread can abort things himself without relying on the main thread call m_forwarder->abort().
On 2016/07/22 08:57:30, haraken wrote: > > Just help me understand: Do we need to observe contextDestroyed()? When the > worker thread is shut down, Bridge::destroy() should have already been called. > Bridge::destroy() calls Peer:cancel(), so we won't need to call cancel() here. > > Also the worker thread can abort things himself without relying on the main > thread call m_forwarder->abort(). > > >
Peer needs to inherit WorkerThreadableLifecycleObserver from two reasons:
1) It should know if the thread is being terminated in Peer::createAndStart. As the Peer is not yet associated with a Bridge, I would like to cancel the main thread loader here.
2) The abort call in contextDestroyed interrupts a worker-side sync loading that may block the thread termination (Even the worker thread is blocked, the termination process can be started from the main thread).
On 2016/07/22 08:57:30, haraken wrote: > > Also I'd suggest renaming classes in a follow-up CL to clarify what is on the > main thread and what is on the worker thread. > > e.g.: > > Peer => PeerOnMainThread > Bridge => PeerOnWorker > ThreadableLoaderClient => ThreadableLoaderClientOnMainThread > ThreadableLoaderClientWrapper => ThreadableLoaderClientOnWorker
Added a TODO.
Note that ThreadableLoaderClient is not bound to the main thread. For example, XHR is a ThreadableLoaderClient and running on both threads.
> > Makes sense. > > Once we move ThreadableLoader to Oilpan's heap, we can just use strong > references (Member<>s). Cycles between Member<>s is not problematic at all. > > In this CL, strong or weak wouldn't matter in practice. I'd slightly prefer > using a strong reference because then we can get rid of if(!clientWrapper) check > from a bunch of methods. We anyway need to get rid of the checks when we change > the reference to Member<> in the next CL. It looks simpler to use a strong > reference from the beginning. >
Sorry I don't understand. IIUC those checks won't go away even when we replace WorkerThreadableLoader::m_workerClientWrapper's Persistent with Member. The ThreadableLoaderClientWrapper is held as Persistent/Member in the worker thread, but in Peer functions, we are in the main thread, so we cannot rely on that Persistent/Member (i.e., the protection on the worker thread may go away completely asynchronously with the per-thread heap mechanism).
On 2016/07/25 02:20:59, yhirano wrote: > On 2016/07/22 08:57:30, haraken wrote: > >
> > Just help me understand: Do we need to observe contextDestroyed()? When the > > worker thread is shut down, Bridge::destroy() should have already been called. > > Bridge::destroy() calls Peer:cancel(), so we won't need to call cancel() here. > > > > Also the worker thread can abort things himself without relying on the main > > thread call m_forwarder->abort(). > > > > > > > > Peer needs to inherit WorkerThreadableLifecycleObserver from two reasons: > > > 1) It should know if the thread is being terminated in Peer::createAndStart. As > the Peer is not yet associated with a Bridge, I would like to cancel the main > thread loader here. > > 2) The abort call in contextDestroyed interrupts a worker-side sync loading that > may block the thread termination (Even the worker thread is blocked, the > termination process can be started from the main thread).
Makes sense.
I now understand that Peer needs to observe contextDestroyed(), but do we need to call Peer::cancel()? I guess Peer::cancel() will anyway be called when Bridge::destroy() is called (when the worker thread terminates), so we don't need to call it here.
I'm concerned about the process shutdown scenario. On that case it's possible that Bridge::destroy runs on the worker thread but the posted Peer::cancel won't run on the main thread, I think.
What do you think about this scenario? If that's impossible, I think we can remove this statement.
Are you concerned that the main thread shuts down before finishing processing all tasks posted by the worker thread? Or are you concerned that there may be a scenario where the worker thread doesn't post a task that triggers Peer::cancel()?
1. A dedicated worker is running, a WorkerThreadableLoader is running on the thread, and a DocumentThreadableLoader associated with it is running on the main thread. 2. blink::shutdown is called. 3. WorkerThread::terminateAndWaitForAllWorkers() is called. 4. WorkerThreadLifecycleNotifier::notifyContextDestroyed() is called. 5. WorkerGlobalScope::dispose is called [on the worker thread] 6. WorkerThreadableLoader::destroy is called and Peer::cancel is posted on the main thread [on the worker thread]. 7. (end of WorkerThread::terminateAndWaitForAllWorkers()) 8. Platform::shutdown is called.
As everything in blink::shutdown is executed synchronously, I think there is no chance to run the Peer::cancel task posted in 6.
Should we probably call messageloop.RunUntilIdle() before 8? (tzik@ was trying something like this.) I think it should be guaranteed that all tasks posted to the main thread are processed before the main thread shuts down. Otherwise, it will cause similar threading issues in other places...
Maybe, but I'm not sure. For this class, it's enough to cancel the loader at the step 4 (i.e., Peer::contextDestroyed). It's not a so bad solution, I believe. Personally I hope other classes can do the same thing, but I'm don't know if it's possible right now...
Anyway I now understand the situation well. I think it should be fixed somehow (=guarantee that the main thread processes all tasks before shutting down) but it won't need to block this CL.
On 2016/07/26 07:32:52, hiroshige wrote: > Violations to DCHECKs on |m_isSignalCalled| and |m_isWaitDone| cause race > conditions and use-after-free, so it might be better to use CHECK() for them.
On 2016/07/27 02:17:49, kinuko wrote: > nit: declaration of TaskForwarder subclasses and WaitableEventWithTasks could be > possibly moved into .cpp? Having so many inner class declarations in a header > slightly hurts readability.
On 2016/08/03 19:23:10, Nate Chapin wrote: > On 2016/08/01 07:57:43, yhirano wrote: > > On 2016/07/28 07:25:48, yhirano wrote: > > > +japhet@ > > > > Nate, Hiroshige: Do you have further comments? > > I don't know WorkerThreadableLoader well enough to feel comfortable giving a > real approval, but this seems reasonable to me.