From ad589ceb60717d4314214001cf69a9d32cd2f193 Mon Sep 17 00:00:00 2001 From: justincarlson Date: Thu, 20 Apr 2017 10:26:07 -0700 Subject: [PATCH] Port threading design doc to in-tree docs, start a README for design docs in the tree. This is a straight up port of https://www.chromium.org/developers/design-documents/threading, with minimal editing only for style consistency and some grammar errors. Review-Url: https://codereview.chromium.org/2822353002 Cr-Commit-Position: refs/heads/master@{#466049} --- docs/README.md | 3 + docs/design/README.md | 13 + docs/design/threading.md | 496 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 512 insertions(+) create mode 100644 docs/design/README.md create mode 100644 docs/design/threading.md diff --git a/docs/README.md b/docs/README.md index b0d26a2965b707..0ef06ca52309b3 100644 --- a/docs/README.md +++ b/docs/README.md @@ -57,6 +57,9 @@ git cl patch hide some of the tools used for working on Chromium behind an abstraction layer +### Design Docs +* See [design/README.md](design/README.md) + ### Integrated Development Environment (IDE) Set Up Guides * [Android Studio](android_studio.md) - Android Studio for Android builds * [Eclipse for Android](eclipse.md) - Eclipse for Android diff --git a/docs/design/README.md b/docs/design/README.md new file mode 100644 index 00000000000000..e9aeef29d1c52a --- /dev/null +++ b/docs/design/README.md @@ -0,0 +1,13 @@ +# Chromium Design Docs + +This directory contains chromium project documentation in +[Gitiles-flavored Markdown](https://gerrit.googlesource.com/gitiles/+/master/Documentation/markdown.md). +It is automatically +[rendered by Gitiles](https://chromium.googlesource.com/chromium/src/+/master/docs/). + +Documents here have been imported +from [the Project site](https://www.chromium.org/developers/design-documents). +As of this writing, the vast majority of docs have not been imported yet. + +* [Threading](threading.md) - Preferred ways to use threading, and library + support for concurrency. diff --git a/docs/design/threading.md b/docs/design/threading.md new file mode 100644 index 00000000000000..105891cfab01b4 --- /dev/null +++ b/docs/design/threading.md @@ -0,0 +1,496 @@ +# Threading + +[TOC] + +## Overview + +Chromium is a very multithreaded product. We try to keep the UI as responsive as +possible, and this means not blocking the UI thread with any blocking I/O or +other expensive operations. Our approach is to use message passing as the way of +communicating between threads. We discourage locking and threadsafe +objects. Instead, objects live on only one thread, we pass messages between +threads for communication, and we use callback interfaces (implemented by +message passing) for most cross-thread requests. + +The `Thread` object is defined in +[`base/threading/thread.h`](https://cs.chromium.org/chromium/src/base/threading/thread.h). +In general you should probably use one of the existing threads described below +rather than make new ones. We already have a lot of threads that are difficult +to keep track of. Each thread has a `MessageLoop` (see +[`base/message_loop/message_loop.h`](https://cs.chromium.org/chromium/src/base/message_loop/message_loop.h) +that processes messages for that thread. You can get the message loop for a +thread using the `Thread.message_loop()` function. More details about +`MessageLoop` can be found in +[Anatomy of Chromium MessageLoop](https://docs.google.com/document/d/1_pJUHO3f3VyRSQjEhKVvUU7NzCyuTCQshZvbWeQiCXU/view#). + +## Existing threads + +Most threads are managed by the BrowserProcess object, which acts as the service +manager for the main "browser" process. By default, everything happens on the UI +thread. We have pushed certain classes of processing into these other +threads. It has getters for the following threads: + +* **ui_thread**: Main thread where the application starts up. +* **io_thread**: This thread is somewhat mis-named. It is the dispatcher thread + that handles communication between the browser process and all the + sub-processes. It is also where all resource requests (web page loads) are + dispatched from (see + [Multi-process Architecture](https://www.chromium.org/developers/design-documents/multi-process-architecture)). +* **file_thread**: A general process thread for file operations. When you want to + do blocking filesystem operations (for example, requesting an icon for a file + type, or writing downloaded files to disk), dispatch to this thread. +* **db_thread**: A thread for database operations. For example, the cookie + service does sqlite operations on this thread. Note that the history database + doesn't use this thread yet. +* **safe_browsing_thread** + +Several components have their own threads: + +* **History**: The history service object has its own thread. This might be + merged with the db_thread above. However, we need to be sure that things + happen in the correct order -- for example, that cookies are loaded before + history since cookies are needed for the first load, and history + initialization is long and will block it. +* **Proxy service**: See + [`net/http/http_proxy_service.cc`](https://cs.chromium.org/chromium/src/net/http/http_proxy_service.cc). +* **Automation proxy**: This thread is used to communicate with the UI test + program driving the app. + +## Keeping the browser responsive + +As hinted in the overview, we avoid doing any blocking I/O on the UI thread to +keep the UI responsive. Less apparent is that we also need to avoid blocking +I/O on the IO thread. The reason is that if we block it for an expensive +operation, say disk access, then IPC messages don't get processed. The effect +is that the user can't interact with a page. Note that asynchronous/overlapped +I/O are fine. + +Another thing to watch out for is to not block threads on one another. Locks +should only be used to swap in a shared data structure that can be accessed on +multiple threads. If one thread updates it based on expensive computation or +through disk access, then that slow work should be done without holding on to +the lock. Only when the result is available should the lock be used to swap in +the new data. An example of this is in PluginList::LoadPlugins +([`content/common/plugin_list.cc`](https://cs.chromium.org/chromium/src/content/common/plugin_list.cc). If +you must use locks, +[here](https://www.chromium.org/developers/lock-and-condition-variable) +are some best practices and pitfalls to avoid. + +In order to write non-blocking code, many APIs in Chromium are +asynchronous. Usually this means that they either need to be executed on a +particular thread and will return results via a custom delegate interface, or +they take a `base::Callback<>` object that is called when the requested +operation is completed. Executing work on a specific thread is covered in the +PostTask section below. + +## Getting stuff to other threads + +### `base::Callback<>`, Async APIs and Currying + + +A `base::Callback<>` (see the docs in +[`base/callback.h`](https://cs.chromium.org/chromium/src/base/callback.h) is +a templated class with a `Run()` method. It is a generalization of a function +pointer and is created by a call to `base::Bind`. Async APIs often will take a +`base::Callback<>` as a means to asynchronously return the results of an +operation. Here is an example of a hypothetical FileRead API. + + void ReadToString(const std::string& filename, const base::Callback& on_read); + + void DisplayString(const std::string& result) { + LOG(INFO) << result; + } + + void SomeFunc(const std::string& file) { + ReadToString(file, base::Bind(&DisplayString)); + }; + +In the example above, `base::Bind` takes the function pointer `&DisplayString` +and turns it into a `base::Callback`. The type +of the generated `base::Callback<>` is inferred from the arguments. Why not +just pass the function pointer directly? The reason is `base::Bind` allows the +caller to adapt function interfaces and/or attach extra context +via [Currying](http://en.wikipedia.org/wiki/Currying). For instance, if we had +a utility function `DisplayStringWithPrefix` that took an extra argument with +the prefix, we use `base::Bind` to adapt the interface as follows. + + void DisplayStringWithPrefix(const std::string& prefix, const std::string& result) { + LOG(INFO) << prefix << result; + } + + void AnotherFunc(const std::string& file) { + ReadToString(file, base::Bind(&DisplayStringWithPrefix, "MyPrefix: ")); + }; + +This can be used in lieu of creating an adapter functions a small classes that +holds prefix as a member variable. Notice also that the `"MyPrefix: "` argument +is actually a `const char*`, while `DisplayStringWithPrefix` actually wants a +`const std::string&`. Like normal function dispatch, `base::Bind`, will coerce +parameters types if possible. + +See [How arguments are handled by base::Bind()](#how_arguments_are_handled) +below for more details about argument storage, copying, and special handling of +references. + +### PostTask + +The lowest level of dispatching to another thread is to use the +`MessageLoop.PostTask` and `MessageLoop.PostDelayedTask` +(see +[`base/message_loop/message_loop.h`](https://cs.chromium.org/chromium/src/base/message_loop/message_loop.h)). +PostTask schedules a task to be run on a particular thread. A task is defined +as a `base::Closure`, which is a typedef for a +`base::Callback`. `PostDelayedTask` schedules a task to be run after +a delay on a particular thread. A task is represented by the `base::Closure` +typedef, which contains a `Run()` function, and is created by calling +`base::Bind()`. To process a task, the message loop eventually calls +`base::Closure`'s `Run` function, and then drops the reference to the task +object. Both `PostTask` and `PostDelayedTask` take a `tracked_objects::Location` +parameter, which is used for lightweight debugging purposes (counts and +primitive profiling of pending and completed tasks can be monitored in a debug +build via the url about:objects). Generally the macro value `FROM_HERE` is the +appropriate value to use in this parameter. + +Note that new tasks go on the message loop's queue, and any delay that is +specified is subject to the operating system's timer resolutions. This means +that under Windows, very small timeouts (under 10ms) will likely not be honored +(and will be longer). Using a timeout of 0 in `PostDelayedTask` is equivalent to +calling `PostTask`, and adds no delay beyond queuing delay. `PostTask` is also +used to do something on the current thread "sometime after the current +processing returns to the message loop." Such a continuation on the current +thread can be used to assure that other time critical tasks are not starved on +this thread. + +The following is an example of a creating a task for a function and posting it +to another thread (in this example, the file thread): + + void WriteToFile(const std::string& filename, const std::string& data); + BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, + base::Bind(&WriteToFile, "foo.txt", "hello world!")); + +You should always use `BrowserThread` to post tasks between threads. Never +cache `MessageLoop` pointers as it can cause bugs such as the pointers being +deleted while you're still holding on to them. More information can be +found +[here](https://www.chromium.org/developers/design-documents/threading/suble-threading-bugs-and-patterns-to-avoid-them). + + +### base::Bind() and class methods. + +The `base::Bind()` API also supports invoking class methods as well. The syntax +is very similar to calling `base::Bind()` on a function, except the first +argument should be the object the method belongs to. By default, the object that +`PostTask` uses must be a thread-safe reference-counted object. Reference +counting ensures that the object invoked on another thread will stay alive until +the task completes. + + class MyObject : public base::RefCountedThreadSafe { + public: + void DoSomething(const std::string16& name) { + thread_->message_loop()->PostTask( + FROM_HERE, base::Bind(&MyObject::DoSomethingOnAnotherThread, this, name)); + } + + void DoSomethingOnAnotherThread(const std::string16& name) { + ... + } + private: + // Always good form to make the destructor private so that only RefCounted + // ThreadSafe can access it. + // This avoids bugs with double deletes. + friend class base::RefCountedThreadSafe; + + ~MyObject(); + Thread* thread_; + }; + +If you have external synchronization structures that can completely ensure that +an object will always be alive while the task is waiting to execute, you can +wrap the object pointer with `base::Unretained()` when calling `base::Bind()` to +disable the refcounting. This will also allow using `base::Bind()` on classes +that are not refcounted. Be careful when doing this! + + + +### How arguments are handled by `base::Bind()` + + +The arguments given to `base::Bind()` are copied into an internal +`InvokerStorage` structure object (defined in +[`base/bind_internal.h`](http://cs.chromium.org/chromium/src/base/bind_internal.h). +When the function is finally executed, it will see copies of the arguments. This is important if your target function or method takes a const reference; the +reference will be to a copy of the argument. If you need a reference to the +original argument, you can wrap the argument with `base::ConstRef()`. Use this +carefully as it is likely dangerous if target of the reference cannot be +guaranteed to live past when the task is executed. In particular, it is almost +never safe to use `base::ConstRef()` to a variable on the stack unless you can +guarantee the stack frame will not be invalidated until the asynchronous task +finishes. + +Sometimes, you will want to pass reference-counted objects as parameters (be +sure to use `RefCountedThreadSafe` and not plain `RefCounted` as the base class +for these objects). To ensure that the object lives throughout the entire +request, the Closure generated by `base::Bind` must keep a reference to it. This +can be done by passing scoped_refptr as the parameter type, or by wrapping the +raw pointer with `make_scoped_refptr()`: + + class SomeParamObject : public base::RefCountedThreadSafe { + ... + }; + + class MyObject : public base::RefCountedThreadSafe { + public: + void DoSomething() { + scoped_refptr param(new SomeParamObject); + thread_->message_loop()->PostTask(FROM_HERE + base::Bind(&MyObject::DoSomethingOnAnotherThread, this, param)); + } + void DoSomething2() { + SomeParamObject* param = new SomeParamObject; + thread_->message_loop()->PostTask(FROM_HERE + base::Bind(&MyObject::DoSomethingOnAnotherThread, this, + make_scoped_refptr(param))); + } + // Note how this takes a raw pointer. The important part is that + // base::Bind() was passed a scoped_refptr; using a scoped_refptr + // here would result in an extra AddRef()/Release() pair. + void DoSomethingOnAnotherThread(SomeParamObject* param) { + ... + } + }; + +If you want to pass the object without taking a reference on it, wrap the +argument with `base::Unretained()`. Again, using this means there are external +guarantees on the lifetime of the object, so tread carefully! + +If your object has a non-trivial destructor that needs to run on a specific +thread, you can use the following trait. This is needed since timing races could +lead to a task completing execution before the code that posted it has unwound +the stack. + + class MyObject : public base::RefCountedThreadSafe { + +## Callback cancellation + +There are 2 major reasons to cancel a task (in the form of a Callback): +* You want to do something later on your object, but at the time your callback + runs, your object may have been destroyed. +* When input changes (e.g. user input), old tasks become unnecessary. For + performance considerations, you should cancel them. +See following about different approaches for cancellation. + +### Important notes about cancellation + +It's dangerous to cancel a task with owned parameters. See the following +example. (The example uses `base::WeakPtr` for cancellation, but the problem +applies to all approaches). + + class MyClass { + public: + // Owns |p|. + void DoSomething(AnotherClass* p) { + ... + } + WeakPtr AsWeakPtr() { + return weak_factory_.GetWeakPtr(); + } + private: + base::WeakPtrFactory weak_factory_; + }; + + ... + Closure cancelable_closure = Bind(&MyClass::DoSomething, object->AsWeakPtr(), p); + Callback cancelable_callback = Bind(&MyClass::DoSomething, object->AsWeakPtr()); + ... + + void FunctionRunLater(const Closure& cancelable_closure, + const Callback& cancelable_callback) { + ... + // Leak memory! + cancelable_closure.Run(); + cancelable_callback.Run(p); + } + +In `FunctionRunLater`, both `Run()` calls will leak `p` when object is already +destructed. Using `scoped_ptr` can fix the bug: + + class MyClass { + public: + void DoSomething(scoped_ptr p) { + ... + } + ... + }; + +### base::WeakPtr and Cancellation __[NOT THREAD SAFE]__ + +You can use a `base::WeakPtr` and `base::WeakPtrFactory` +(in +[base/memory/weak_ptr.h](https://cs.chromium.org/chromium/src/base/memory/weak_ptr.h)) +to ensure that any invokes can not outlive the object they are being invoked on, +without using reference counting. The `base::Bind` mechanism has special +understanding for `base::WeakPtr` that will disable the task's execution if the +`base::WeakPtr` has been invalidated. The `base::WeakPtrFactory` object can be +used to generate `base::WeakPtr` instances that know about the factory +object. When the factory is destroyed, all the `base::WeakPtr` will have their +internal "invalidated" flag set, which will make any tasks bound to them to not +dispatch. By putting the factory as a member of the object being dispatched to, +you can get automatic cancellation. + +__NOTE__: This only works when the task is posted to the same thread. Currently +there is not a general solution that works for tasks posted to other +threads. See +the [next section about CancelableTaskTracker](#cancelable_task_tracker) for an +alternative solution. + + class MyObject { + public: + MyObject() : weak_factory_(this) {} + + void DoSomething() { + const int kDelayMS = 100; + MessageLoop::current()->PostDelayedTask(FROM_HERE, + base::Bind(&MyObject::DoSomethingLater, weak_factory_.GetWeakPtr()), + kDelayMS); + } + + void DoSomethingLater() { + ... + } + + private: + base::WeakPtrFactory weak_factory_; + }; + +### CancelableTaskTracker + + +While `base::WeakPtr` is very helpful to cancel a task, it is not thread safe so +can not be used to cancel tasks running on another thread. This is sometimes a +performance critical requirement. E.g. We need to cancel database lookup task on +DB thread when user changes inputed text. In this kind of situation +`CancelableTaskTracker` is appropriate. + +With `CancelableTaskTracker` you can cancel a single task with returned +`TaskId`. This is another reason to use `CancelableTaskTracker` instead of +`base::WeakPtr`, even in a single thread context. + +`CancelableTaskTracker` has 2 `Post` methods doing the same thing as the ones in +`base::TaskRunner`, with additional cancellation support. + + class UserInputHandler : public base::RefCountedThreadSafe { + // Runs on UI thread. + void OnUserInput(Input input) { + CancelPreviousTask(); + DBResult* result = new DBResult(); + task_id_ = tracker_->PostTaskAndReply( + BrowserThread::GetMessageLoopProxyForThread(BrowserThread::DB).get(), + FROM_HERE, + base::Bind(&LookupHistoryOnDBThread, this, input, result), + base::Bind(&ShowHistoryOnUIThread, this, base::Owned(result))); + } + + void CancelPreviousTask() { + tracker_->TryCancel(task_id_); + } + + ... + + private: + CancelableTaskTracker tracker_; // Cancels all pending tasks while destruction. + CancelableTaskTracker::TaskId task_id_; + ... + }; + +Since task runs on other threads, there's no guarantee it can be successfully +canceled. + +When `TryCancel()` is called: + +* If neither task nor reply has started running, both will be canceled. +* If task is already running or has finished running, reply will be canceled. +* If reply is running or has finished running, cancelation is a noop. + +Like `base::WeakPtrFactory`, `CancelableTaskTracker` will cancel all tasks on +destruction. + +### Cancelable request __(DEPRECATED)__ + +Note. Cancelable request is deprecated. Please do not use it in new code. For +canceling tasks running on the same thread, use WeakPtr. For canceling tasks +running on a different thread, use `CancelableTaskTracker`. + +A cancelable request makes it easier to make requests to another thread with +that thread returning some data to you asynchronously. Like the revokable store +system, it uses objects that track whether the originating object is alive. When +the calling object is deleted, the request will be canceled to prevent invalid +callbacks. + +Like the revokable store system, a user of a cancelable request has +an object (here, called a _Consumer_) that tracks whether it is alive and will +auto-cancel any outstanding requests on deleting. + + class MyClass { + void MakeRequest() { + frontend_service->StartRequest(some_input1, some_input2, this, + // Use base::Unretained(this) if this may cause a refcount cycle. + base::Bind(&MyClass:RequestComplete, this)); + } + void RequestComplete(int status) { + ... + } + + private: + CancelableRequestConsumer consumer_; + }; + +Note that the `MyClass::RequestComplete`, is bounded with +`base::Unretained(this)` here. + +The consumer also allows you to associate extra data with a request. Use +`CancelableRequestConsumer` which will allow you to associate arbitrary data +with the handle returned by the provider service when you invoke the +request. The data will be automatically destroyed when the request is canceled. + +A service handling requests inherits from `CancelableRequestProvider`. This +object provides methods for canceling in-flight requests, and will work with the +consumers to make sure everything is cleaned up properly on cancel. This +frontend service just tracks the request and sends it to a backend service on +another thread for actual processing. It would look like this: + + class FrontendService : public CancelableRequestProvider { + typedef base::Callback RequestCallbackType; + + Handle StartRequest(int some_input1, int some_input2, + CallbackConsumer* consumer, + const RequestCallbackType& callback) { + scoped_refptr> + request(new CancelableRequest(callback)); + AddRequest(request, consumer); + + // Send the parameters and the request to the backend thread. + backend_thread_->PostTask(FROM_HERE, + base::Bind(&BackendService::DoRequest, backend_, request, + some_input1, some_input2), 0); + // The handle will have been set by AddRequest. + return request->handle(); + } + }; + +The backend service runs on another thread. It does processing and forwards the +result back to the original caller. It would look like this: + + class BackendService : public base::RefCountedThreadSafe { + void DoRequest( + scoped_refptr> + request, + int some_input1, int some_input2) { + if (request->canceled()) + return; + + ... do your processing ... + + // Execute ForwardResult() like you would do Run() on the base::Callback<>. + request->ForwardResult(return_value); + } + };