wiki:ThreadCommunication
Last modified 3 years ago Last modified on 04/24/11 21:22:44

Introduction

There are multiple places in WebKit where we do communication between threads. Each of these has a slightly different model, but all are pretty similar. The problems we have with our current scheme are:

  1. Every time someone need to do this, they need to write their own version of it, which leads to many typical problems with duplicate code.
  1. Common operations are need to be hand written each time requiring careful review of tricky lifetime code which is easy to get wrong.

To fix these, it would be good to have some common data structures/api which handle common operations without requiring much boilerplate code. Let's start by enumerating out common cases.

Use cases

  1. An object exist on one thread. It wants to create an object on another thread and be able to call methods on it.

This is a superset of other potential cases including fire and forget.

  1. An object wants to be able to allow another thread to be able to call its methods.
  1. A function wants to call a method on another thread and wait until it is done and allow callbacks resulting from that to run in the originating thread.

This looks like a nested message loop with a filter which is generally regarded as bad. The only reason to allow this capability is for synchronous calls from javascript to call to the main thread to get some work done and act like a synchronous call but there may be callbacks (e.g. when doing a sync xhr call.) Theoretically, the javascript engine could roll up its state into a closure and we could avoid the nested message loop but that capability doesn't exist in the engines that we deal with and would be very expensive to add to them.

The framework should track the dependencies to check for possible deadlocks.

Constraints:

  • The api should be simple to use and handle things like ensuring objects are appropriately copied or ref counted and make it easier to deal with lifetime issues.
  • Either object should be able to be deleted at any time and the other side shouldn't cause any memory corruption issues by sending more messages.
  • Allow for a thread pool scenario. (This isn't about implementing the thread pool but ensuring that the implementation suggested doesn't preclude one being done in the future).

TBD: There are likely more use cases to be listed here.

Proposed code samples for use cases

Case 1: Create an object on another thread:

    // Creates a proxy and initializes a target object on a given message loop.
    RefPtr<TypeName::Proxy> proxy = TypeName::createProxy(otherMessageLoop, constructorArg1, constructorArg2, ...);

    // Calls method on the object on the other message loop. This does not need to wait for some signal that the object has been created.
    proxy->invoke(&TypeName::method, arg1, arg2, ...);

Case 2: Allow an object on another thread to do callbacks:

    // getProxy is a method in the current object which returns its proxy. The proxy may be used with invoke like these examples.
    proxy->invokeWithCallback(this, &TypeName::method, arg1, arg2, arg3, ...);

The resulting call on TypeName::method should look identical to that done with invokeSync. (The other side doesn't care and should indicate when it is "done" so it may be used in either way.)

Case 3: A thread wants to call a function on another thread and wait until it is done.

    OwnPtr<SyncWaiter> syncCall = proxy->invokeWithCallback(this, &TypeName::method,  arg1, arg2, arg3, ...);
    syncCall.wait();

Case study: File API from Workers

Here's an example of converting the move method from WebKit/chromium/src/WorkerFileSystemCallbacksBridge.cpp (http://trac.webkit.org/browser/trunk/Source/WebKit/chromium/src/WorkerFileSystemCallbacksBridge.cpp) to using this.

The main changes are less code at the calling sites and less mechanics around carefully tracking the lifetime of each side.

class MainThreadFileSystemCallbacks : public WebFileSystemCallbacks {
public:
    // Callbacks are self-destructed and we always return leaked pointer here.
    static MainThreadFileSystemCallbacks* createLeakedPtr(PassOwnPtr<ClassProxy> callbackProxy)
    {
        return adoptPtr(new MainThreadFileSystemCallbacks(callbackProxy)).leakPtr();
    }

    virtual void didSucceed()
    {
        m_callbackProxy->invoke(&didSucceedOnMainThread);
        m_callbackProxy->done();
        delete this;
    }

private:
    MainThreadFileSystemCallbacks(PassOwnPtr<ClassProxy> callbackProxy)
        : m_callbackProxy(callbackProxy)
    {
    }

    OwnPtr<ClassProxy> m_callbackProxy;
};

void moveOnMainThread(PassOwnPtr<ClassProxy> callbackProxy, WebFileSystem* fileSystem, const String& sourcePath, const String& destinationPath, const String& mode)
{
    fileSystem->move(sourcePath, destinationPath, MainThreadFileSystemCallbacks::createLeakedPtr(callbackProxy));
}

void WorkerFileSystemCallbacks::stop()
{
    if (m_callbacksOnWorkerThread) {
        m_callbacksOnWorkerThread->didFail(WebFileErrorAbort);
        m_callbacksOnWorkerThread = 0;
    }
}

void WorkerFileSystemCallbacks::postMoveToMainThread(WebFileSystem* fileSystem, const String& sourcePath, const String& destinationPath, const String& mode)
{
    OwnPtr<SyncWaiter> syncCall = mainThreadMessageLoop->invokeSync(this, &moveOnMainThread, fileSystem, sourcePath, destinationPath);
    syncCall.wait();
    stop();
}

This introduced a way to to call a function on the other thread (Messageloop::invokeSync).

Detailed api breakdown

TBD