# Protected Sequence Synchronizer [ProtectedSequenceSynchronizer](https://source.chromium.org/chromium/chromium/src/+/main:cc/base/protected_sequence_synchronizer.h;l=19;drc=3f7a9d89de4c434583d520384b01ce87178ce888) is a class in cc that enforces the thread safety of data. This is used in multiple cc codes such as cc::Layer, cc::Animation ... This is making the codes a bit confusing, so let's get used to it. ## What it's for and How to use [ProtectedSequenceSynchronizer](https://source.chromium.org/chromium/chromium/src/+/main:cc/base/protected_sequence_synchronizer.h;l=19;drc=3f7a9d89de4c434583d520384b01ce87178ce888) is a template class that can take struct or class and help to write/read data in the type safely across the threads. Its parent class owns ProtectedSequenceSynchronizer, produces it, and then passed by reference to another thread to use for a limited duration as a protected sequence. For example, assuming you have `Inputs` class which contains various data and the data may be changed/referred from multiple threads, you can do like this: ```cpp= class CC_EXPORT Layer : public base::RefCounted<Layer>, public ProtectedSequenceSynchronizer { ...; // ProtectedSequenceSynchronizer implementation bool IsOwnerThread() const override; bool InProtectedSequence() const override; void WaitForProtectedSequenceCompletion() const override; ...; // Readable ProtectedSequenceReadable<Inputs> inputs_; } inputs_.Write(*this).bounds = size; scroll_node->bounds = inputs_.Read(*this).bounds; ``` ## Readable / Writable There are several types of sequence. - [ProtectedSequenceForbidden](https://source.chromium.org/chromium/chromium/src/+/main:cc/base/protected_sequence_synchronizer.h;l=46;drc=d3d4ff28768842dd1ce94f408f89d1e2d31dd4fd): Non-owner cannot read nor write - [ProtectedSequenceReadble](https://source.chromium.org/chromium/chromium/src/+/main:cc/base/protected_sequence_synchronizer.h;l=76;drc=d3d4ff28768842dd1ce94f408f89d1e2d31dd4fd): Owner can read and write, but cannot write during protected sequence. Non-owner can read during protected sequence but cannot write. - [ProtectedSequenceWritable](https://source.chromium.org/chromium/chromium/src/+/main:cc/base/protected_sequence_synchronizer.h;l=110;drc=d3d4ff28768842dd1ce94f408f89d1e2d31dd4fd): Owner can read and write only when it's not during protected sequence. Non-owner can read and write only when it's during protected sequence. ## Implementation ProtectedSequenceSynchronizer interface has 3 APIs. [IsOwnerThread](https://source.chromium.org/chromium/chromium/src/+/main:cc/base/protected_sequence_synchronizer.h;l=28;drc=d3d4ff28768842dd1ce94f408f89d1e2d31dd4fd) which returns whether the current thread is owner or not. [IsProtectedSequence](https://source.chromium.org/chromium/chromium/src/+/main:cc/base/protected_sequence_synchronizer.h;l=35;drc=d3d4ff28768842dd1ce94f408f89d1e2d31dd4fd) which returns non-owner thread is running a protected sequence. [WaitForProtectedSequenceCompletion](https://source.chromium.org/chromium/chromium/src/+/main:cc/base/protected_sequence_synchronizer.h;l=40;drc=d3d4ff28768842dd1ce94f408f89d1e2d31dd4fd) to block execution of the owner until protected sequence duration ends. The class must implement 3 APIs above which will be used from ProtectedSequence implementation. Let's see [ProtectedSequenceForbidden](https://source.chromium.org/chromium/chromium/src/+/main:cc/base/protected_sequence_synchronizer.h;l=46;drc=d3d4ff28768842dd1ce94f408f89d1e2d31dd4fd) first. ```cpp= template <typename T> class ProtectedSequenceForbidden { public: ...; const T& Read(const ProtectedSequenceSynchronizer& synchronizer) const { DCHECK(synchronizer.IsOwnerThread()); return value_; } T& Write(const ProtectedSequenceSynchronizer& synchronizer) { DCHECK(synchronizer.IsOwnerThread()); return value_; } private: T value_; }; ``` The implementation is quite simple. If it's owner thread, you can read it. If it's owner thread, you can write it. The data is storerd as `value_`. The only API we used is `IsOwnerThread` in this case because we don't have to pay attention to Protected sequence in this condition. Readable class has a bit more intersting impl. ```cpp= class ProtectedSequenceReadable { public: ...; const T& Read(const ProtectedSequenceSynchronizer& synchronizer) const { DCHECK(synchronizer.IsOwnerThread() || synchronizer.InProtectedSequence()); return value_; } T& Write(const ProtectedSequenceSynchronizer& synchronizer) { DCHECK(synchronizer.IsOwnerThread()); synchronizer.WaitForProtectedSequenceCompletion(); return value_; } private: T value_; }; ``` Now we can see other 2 APIs. On read, it checks IsOwnerTHread or IsProtectedSequence because non-owner thread can still read if it's during protected sequence. On the other hand, owner thread are now blocked to write when it's in protected sequence. Instead, we wait until it's finish. This implies that `Write` can be a blocking call. Writable is similar, so let's skip it. We also have template for unique_ptr container. ```cpp= template <typename T> class ProtectedSequenceReadable<std::unique_ptr<T>> { ``` ## ProtectedSequenceSychronizer implementation example As discussed above, ProtectedSeuqnceSynchronizer has 3 abstract APIs and the derived class must implement it. Here, let's see the example: For example on [cc::LayerTreeHost](https://source.chromium.org/chromium/chromium/src/+/main:cc/trees/layer_tree_host.h;l=120;drc=ca067f2604f9bf0ff2fa070eafeed664698d819a), ```cpp= bool LayerTreeHost::IsOwnerThread() const { return task_runner_provider_->MainThreadTaskRunner() ->RunsTasksInCurrentSequence(); } bool LayerTreeHost::InProtectedSequence() const { return in_commit(); } ``` TO check whether it's a owner thread, [RunsTasksInCurrentSequence](https://source.chromium.org/chromium/chromium/src/+/main:base/task/sequenced_task_runner.h;l=309;drc=d3d4ff28768842dd1ce94f408f89d1e2d31dd4fd) is called. This API returns true if and only if tasks posted to this TaskRunner are sequenced with this call. This is a same condition as it's owner. On commiting frame, it seems to go in to Protected sequence. For LayerTreeHost, commit runs on impl thread so that it blocks until the commit complete. Note: Impl is not a physical thread on somce platforms, but we handle them as if it's a thtread. ## Note It was relatively straight forward. Now we can read cc codes easier~