|  | # Advanced scenarios | 
|  |  | 
|  | By default, most of the behavior of intrusive containers in `fbl::` are designed | 
|  | to provide as much safety as possible at compile time, typically by disallowing | 
|  | use patterns that might easily lead to mistakes at compile time. | 
|  |  | 
|  | That said, there are certain advanced scenarios where a user may choose to | 
|  | bypass these compile time safeties in order to deliberately permit certain | 
|  | behaviors.  Whenever you choose to use one of these options, make sure that you | 
|  | have thought carefully about the implications of taking the safety off, and be | 
|  | sure that you are using the functionality in a safe way. | 
|  |  | 
|  | This section of the guide will show you how to: | 
|  |  | 
|  | 1. [Use `fbl::NodeOptions` to opt into advanced behaviors](#node-options) | 
|  | 2. [Control the copy/move-ability of objects while they are in a container.](#copy-move-behavior) | 
|  | 3. [Permit an object tracked by `unique_ptr` to be contained in multiple container types](#multiple-unique) | 
|  | 4. [Clear a container of raw pointers in O(1) time](#clear-unsafe) | 
|  | 5. [Remove an object from a container without a reference to the container](#direct-remove) | 
|  |  | 
|  | ## Controlling advanced options with `fbl::NodeOptions` {#node-options} | 
|  |  | 
|  | In order to control some advanced options at compile time, node state objects | 
|  | (as well as their associated mix-ins) can take a bit-flag style constant, which | 
|  | can be used to change specific behaviors. Options may be combined using the `|` | 
|  | operator. By default, options are the second template parameter of either a | 
|  | `NodeState` or `Listable`/`Containable` type, and the third template parameter | 
|  | of a `TaggedListable`/`TaggedContainable` mix-in. These options always default | 
|  | to `fbl::NodeOptions::None`. The syntax looks like this: | 
|  |  | 
|  | ```cpp | 
|  | class SimpleObject : | 
|  | public fbl::DoublyLinkedListable<SimpleObject*, fbl::NodeOption::OptionFoo> { /* ... */ }; | 
|  |  | 
|  | class MoreComplexObject : | 
|  | public fbl::ContainableBaseClasses< | 
|  | fbl::TaggedSinglyLinkedListable<MoreComplex*, Tag1, fbl::NodeOption::OptionBar>, | 
|  | fbl::TaggedWAVLTreeContainable <MoreComplex*, Tag2, | 
|  | fbl::NodeOption::OptionA | fbl::NodeOption::OptionB>> { | 
|  | // ... | 
|  | }; | 
|  |  | 
|  | class ExplicitNodesObject { | 
|  | public: | 
|  | // ... | 
|  |  | 
|  | private: | 
|  | // ... | 
|  | static constexpr fbl::NodeOptions kOptions = fbl::NodeOption::OptionX | | 
|  | fbl::NodeOption::OptionY | | 
|  | fbl::NodeOption::OptionZ; | 
|  | fbl::WAVLTreeNodeState<ExplicitNodesObject*, kOptions> wavl_node_state_; | 
|  | }; | 
|  | ``` | 
|  |  | 
|  | ## Controlling the copy/move behavior of objects while they are in a container {#copy-move-behavior} | 
|  |  | 
|  | Copying or moving node state while its object is in a container is not a legal | 
|  | operation can cannot be permitted. Consider the following: | 
|  |  | 
|  | ```cpp | 
|  | fbl::DoublyLinkedList<Obj*> the_list; | 
|  | ASSERT(!the_list.is_empty()); | 
|  |  | 
|  | Obj the_obj; | 
|  | the_list.insert_after(the_list.begin()); | 
|  |  | 
|  | Obj another_obj{the_obj}; | 
|  |  | 
|  | Obj yet_another_object; | 
|  | the_obj = yet_another_object; | 
|  | ``` | 
|  |  | 
|  | `the_obj` exists in the list after the first node. If you were to allow the node | 
|  | state to be copied into `another_obj` via the default copy constructor, you would | 
|  | have two objects with two copies of the bookkeeping. `another_obj` would | 
|  | incorrectly think that it was in the container, and will now attempt to assert | 
|  | when destroyed. | 
|  |  | 
|  | Worse, if you attempt to remove the object by calling | 
|  | `the_list.erase(another_object)`, you are attempting to erase an object from a | 
|  | container in an incoherent state. In this case, the prev and next pointers of | 
|  | another object point to the first object in the list, the object that used to | 
|  | be after `begin()` at the start of the example, but the next pointer of | 
|  | `*begin()` is pointing to `the_obj`, and likewise for the prev pointer of the | 
|  | next object in the sequence. While the specific behavior will vary on the type | 
|  | of container and the specific implementation of erase, what is going to happen | 
|  | is undefined behavior and cannot end well. | 
|  |  | 
|  | Finally, when the example code assigns the newly stack constructed | 
|  | `yet_another_object` to `the_obj`, if the node state data were to be copied from | 
|  | `yet_another_object`, `the_obj` would suddenly think it is not in the list even | 
|  | though the objects on either side of it held pointers to it. | 
|  |  | 
|  | Any way you look at it, permitting the node state data to be copied will corrupt | 
|  | the container's structure and cannot be allowed to happen, and the same goes for | 
|  | the definition of most move operations. | 
|  |  | 
|  | To prevent mistakes like this, the default behavior of `fbl::` node state | 
|  | objects is to disallow copy construction/assignment as well as move | 
|  | construction/assignment. Any attempt to invoke the copy/move | 
|  | constructor/assignment operator will result in a `static_assert` and a failure | 
|  | to compile. | 
|  |  | 
|  | What about when objects are not in a container? Shouldn't copy/move be | 
|  | permitted then? The short answer is sure, but in the interest of safety, it is | 
|  | considered to be allowed only if the code author has opted into the behavior. | 
|  | In order to opt in, you may use the following `NodeOptions` with their mix-in | 
|  | or node storage types. | 
|  |  | 
|  | * `fbl::NodeOptions::AllowCopy` | 
|  | * `fbl::NodeOptions::AllowMove` | 
|  | * `fbl::NodeOptions::AllowCopyAndMove` | 
|  |  | 
|  | Setting `AllowCopy` will permit copy (l-value) construction and assignment, | 
|  | while setting `AllowMove` will permit move (r-value) construction and | 
|  | assignment. `AllowCopyAndMove` is simply shorthand for the two of them | 
|  | combined. | 
|  |  | 
|  | During the operation itself, the node state object will `ZX_DEBUG_ASSERT` that | 
|  | neither the source object is not in a container for construction and that | 
|  | neither the source nor the destination object exist in containers during | 
|  | assignment. Regardless of whether or not `ZX_DEBUG_ASERT`s are enabled, the | 
|  | source and destination objects' states will *never* be modified. | 
|  |  | 
|  | For example: | 
|  |  | 
|  | ```cpp | 
|  | struct Point : fbl::DoublyLinkedListable<std::unique_ptr<Point>, | 
|  | fbl::NodeOptions::AllowCopy> { | 
|  | float x, y; | 
|  | }; | 
|  |  | 
|  | fbl::DoublyLinkedList<std::unique_ptr<Point>> all_points; | 
|  |  | 
|  | void AddCopy(const Point& pt) { | 
|  | // If pt is in a list, this will assert. If asserts are off, pt will remain | 
|  | // where it is and new_pt will not start life in a container. | 
|  | auto new_pt = std::make_unique<Point>(pt); | 
|  | all_points.push_back(std::move(new_pt)); | 
|  | } | 
|  | ``` | 
|  |  | 
|  | So, what if you want to permit copying or moving of an object even while it is | 
|  | _in_ a container? For example, what if you wanted to clone your list of points | 
|  | making use of your copy constructor in order to do so? Users may opt into this | 
|  | behavior as well by passing appropriate combinations of the following: | 
|  |  | 
|  | * `fbl::NodeOptions::AllowCopyFromContainer` | 
|  | * `fbl::NodeOptions::AllowMoveFromContainer` | 
|  | * `fbl::NodeOptions::AllowCopyAndMoveFromContainer` | 
|  |  | 
|  | The behavior described above remains the same; node states will never be | 
|  | changed. New objects will start outside of any container and source objects will | 
|  | remain wherever they are. The only difference between the `FromContainer` | 
|  | version of this option vs the non-`FromContainer` version is that the | 
|  | `FromContainer` version will never assert. So, you could clone your list of | 
|  | points with the following. | 
|  |  | 
|  | ```cpp | 
|  | struct Point : fbl::DoublyLinkedListable<std::unique_ptr<Point>, | 
|  | fbl::NodeOptions::AllowCopyFromContainer> { | 
|  | float x, y; | 
|  | }; | 
|  |  | 
|  | using PointList = fbl::DoublyLinkedList<std::unique_ptr<Point>>; | 
|  |  | 
|  | PointList CloneList(const PointList& list) { | 
|  | PointList ret; | 
|  | for (const auto& point : list) { | 
|  | ret.push_back(std::make_unique<Point>(point)); | 
|  | } | 
|  | return ret; | 
|  | } | 
|  | ``` | 
|  |  | 
|  | ## Allowing objects tracked by `unique_ptr` to exist in multiple containers {#multiple-unique} | 
|  |  | 
|  | Usually, it would be a mistake to define an object that can exist in | 
|  | multiple containers concurrently, while tracking those objects in their | 
|  | containers using `unique_ptr` semantics. In theory, it should be impossible | 
|  | for two different containers to track the same object at the same time, | 
|  | each using something like a `unique_ptr` as this would violate the uniqueness of | 
|  | the pointer. | 
|  |  | 
|  | To assist in preventing any mistakes here, `ContainableBaseClasses` will not | 
|  | permit the use of a `std::unique_ptr` pointer type for any of the specified | 
|  | mix-ins, unless the length of the list of containable base classes is exactly 1. | 
|  |  | 
|  | ```cpp | 
|  | struct Tag1 {}; | 
|  | struct Tag2 {}; | 
|  |  | 
|  | // This is legal | 
|  | class Obj : public fbl::ContainableBaseClasses< | 
|  | fbl::TaggedSinglyLinkedListable<std::unique_ptr<Obj>, Tag1>> { /* ... */ }; | 
|  |  | 
|  | // This is not | 
|  | class Obj : public fbl::ContainableBaseClasses< | 
|  | fbl::TaggedSinglyLinkedListable<std::unique_ptr<Obj>, Tag1>, | 
|  | fbl::TaggedSinglyLinkedListable<std::unique_ptr<Obj>, Tag2>> { /* ... */ }; | 
|  |  | 
|  | // Neither is this | 
|  | class Obj : public fbl::ContainableBaseClasses< | 
|  | fbl::TaggedSinglyLinkedListable<std::unique_ptr<Obj>, Tag1>, | 
|  | fbl::TaggedSinglyLinkedListable<Obj*, Tag2>> { /* ... */ }; | 
|  | ``` | 
|  |  | 
|  | There are, however, legitimate uses for types that can exist in multiple | 
|  | containers, which are managed using `std::unique_ptr`s. | 
|  |  | 
|  | First, you may have a situation where an object can exist in two different types | 
|  | of data structure (perhaps a list and a tree), but never the same data structure | 
|  | at the same time. If the uses of the structure are completely disjoint, you may | 
|  | wish to relax the default restriction. | 
|  |  | 
|  | A second reason you might want to allow this is because you have an object whose | 
|  | life is tracked by a container using `std::unique_ptr`, but for which you want | 
|  | to allow objects to exist in containers on a temporary basis in order to more | 
|  | easily implement some sort of algorithm. Perhaps a set of objects needs to be | 
|  | filtered into a temporary list and then passed to a function that will operate | 
|  | on the filtered set.  Or, perhaps they need to be placed into a temporary | 
|  | WAVLTree with a custom sorting/keys in order to check for duplicates. | 
|  |  | 
|  | Whatever the reason, you can permit this behavior by passing the | 
|  | `AllowMultiContainerUptr` option to the node state types that you use. Here is | 
|  | an example for the disjoint container use case: | 
|  |  | 
|  | ```cpp | 
|  | struct FreeObjTag {}; | 
|  | struct ActiveObjTag {}; | 
|  | class Obj : public fbl::ContainableBaseClasses< | 
|  | fbl::TaggedSinglyLinkedListable<std::unique_ptr<Obj>, FreeObjTag, | 
|  | fbl::NodeOptions::AllowMultiContainerUptr>, | 
|  | fbl::TaggedWAVLTreeContainable<std::unique_ptr<Obj>, ActiveObjTag, | 
|  | fbl::NodeOptions::AllowMultiContainerUptr>> { | 
|  | public: | 
|  | using FreeStack = fbl::TaggedSinglyLinkedList<std::unique_ptr<Obj>, FreeObjTag>; | 
|  | using ActiveSet = fbl::TaggedWAVLTree<UniqueId, std::unique_ptr<Obj>, ActiveObjTag>; | 
|  |  | 
|  | // ... | 
|  | UniqueId GetKey() const { return unique_id_; } | 
|  | void AssignId(UniqueId id) { | 
|  | ZX_DEBUG_ASSERT(!fbl::InContainer<ActiveObjTag>(*this)); | 
|  | unique_id_ = id; | 
|  | } | 
|  | // ... | 
|  |  | 
|  | private: | 
|  | // ... | 
|  | }; | 
|  |  | 
|  | fbl::Mutex obj_lock_; | 
|  | Obj::FreeStack free_objects_ TA_GUARDED(obj_lock_); | 
|  | Obj::ActiveSet active_objects_ TA_GUARDED(obj_lock_); | 
|  |  | 
|  | zx_status_t ActivateObject(UniqueId id) { | 
|  | fbl::AutoLock lock(&obj_lock_); | 
|  |  | 
|  | if (free_objects_.is_empty()) { | 
|  | return ZX_ERR_NO_MEMORY; | 
|  | } | 
|  |  | 
|  | auto ptr = free_objects_.pop_front(); | 
|  | ptr.AssignId(id); | 
|  | active_objects_.insert(std::move(ptr)); | 
|  | return ZX_OK; | 
|  | } | 
|  | ``` | 
|  |  | 
|  | ## Allowing O(1) clearing of containers with `clear_unsafe()` {#clear-unsafe} | 
|  |  | 
|  | As noted in the [Lifecycle checks](#lifecycle-checks) section, containers of | 
|  | unmanaged pointers may not destruct with objects still in them, and objects | 
|  | cannot destruct while they think that they are still in a container. Either | 
|  | behavior is considered to be an error and will trigger an assert in a debug | 
|  | build. | 
|  |  | 
|  | What if you had a situation where you didn't care that your objects still | 
|  | thought that they were in a container when they destructed? Perhaps you | 
|  | allocated a contiguous slab of memory and carved it up into object that you | 
|  | then placed onto a free list. If you want to free your slab of memory, and you | 
|  | know that all of the objects have been returned to the free list, then why | 
|  | bother walking the list to zero out all of the linked list bookkeeping? This | 
|  | would just be wasted work. | 
|  |  | 
|  | You can bypass these checks and skip the mandatory O(N) unlinking of the list by | 
|  | using the `AllowClearUnsafe` NodeOption on your objects. When used, the asserts | 
|  | present in the node state object are skipped, and a method on the container | 
|  | called `clear_unsafe()` becomes available for use. `clear_unsafe()` will simply | 
|  | reset the container to its original empty state making no effort to clean up | 
|  | the objects' node states. This is a simple O(1) operation. Attempting to call | 
|  | `clear_unsafe()` on a container that uses a node state object without this flag | 
|  | will trigger a `static_assert`. Here is a simple example of what this would | 
|  | look like: | 
|  |  | 
|  | ```cpp | 
|  | class SlabObject : | 
|  | public fbl::SinglyLinkedListable<SlabObject*, | 
|  | fbl::NodeOptions::AllowClearUnsafe> { /* ... */ }; | 
|  |  | 
|  | static fbl::Mutex slab_lock; | 
|  | static SlabObject* slab_memory TA_GUARDED(slab_lock) = nullptr; | 
|  | static fbl::SizedSinglyLinkedList<SlabObject*> TA_GUARDED(slab_lock) free_objects; | 
|  |  | 
|  | static constexpr size_t kSlabSize = (1 << 20);   // One MB of objects | 
|  | static constexpr size_t kSlabCount = kSlabSize / sizeof(SlabObject); | 
|  |  | 
|  | zx_status_t InitSlab() { | 
|  | fbl::AutoLock lock(&slab_lock); | 
|  | if ((slab_memory != nullptr) || !free_objects.is_empty()) { | 
|  | return ZX_ERR_BAD_STATE; | 
|  | } | 
|  |  | 
|  | fbl::AllocChecker ac; | 
|  | slab_memory = new (&ac) SlabObject[kSlabCount]; | 
|  | if (!ac.check()) { | 
|  | return ZX_ERR_NO_MEMORY; | 
|  | } | 
|  |  | 
|  | for (size_t i = 0; i < kSlabCount; ++i) { | 
|  | free_objects.push_front(slab_memory + i); | 
|  | } | 
|  |  | 
|  | return ZX_OK; | 
|  | } | 
|  |  | 
|  | SlabObject* GetFreeObj() { | 
|  | fbl::AutoLock lock(&slab_lock); | 
|  | return !free_objects.is_empty() ? free_objects.pop_front() : nullptr; | 
|  | } | 
|  |  | 
|  | void ReturnObj(SlabObject* obj) { | 
|  | fbl::AutoLock lock(&slab_lock); | 
|  | ZX_DEBUG_ASSERT(obj != nullptr); | 
|  | free_objects.push_front(obj); | 
|  | } | 
|  |  | 
|  | zx_status_t DeinitSlab() { | 
|  | fbl::AutoLock lock(&slab_lock); | 
|  |  | 
|  | // If not all of our objects have returned, or if we don't have any slab | 
|  | // memory allocated, then we cannot de-init our slab. | 
|  | if ((slab_memory == nullptr) || (free_objects.size() != kSlabCount)) { | 
|  | return ZX_ERR_BAD_STATE; | 
|  | } | 
|  |  | 
|  | // Great, reset the free list with clear unsafe. This basically just sets the | 
|  | // head pointer to nullptr. | 
|  | free_objects.clear_unsafe(); | 
|  |  | 
|  | // Now give our memory back. Since our objects are flagged with | 
|  | // AllowClearUnsafe, node state destructors do nothing. Provided that | 
|  | // SlabObject destructors do nothing, this delete should just return memory to | 
|  | // the heap and not need to call N destructors. | 
|  | delete[] free_objects; | 
|  | free_objects = nullptr; | 
|  |  | 
|  | return ZX_OK; | 
|  | } | 
|  | ``` | 
|  |  | 
|  | ## Directly removing objects from whatever container instance they are in. {#direct-remove} | 
|  |  | 
|  | In general, even though it is sometimes possible, it is not considered best | 
|  | practice to design code that needs to remove objects directly from a container | 
|  | without having a reference to the container itself. As a design principle, users | 
|  | of intrusive containers should always be aware of which container types and | 
|  | instances objects exist in at all times. Still, sometimes direct removal might | 
|  | be the easiest and best option available. | 
|  |  | 
|  | Containers that track size by default might require an O(n) traversal of the | 
|  | data structure in order to find the container to update the bookkeeping if nodes | 
|  | are removed from the container without knowledge of the container instance. | 
|  | Therefore, these containers do not support direct removal. Other container | 
|  | types, such as a `SinglyLinkedList`, simply cannot do this as they lack a | 
|  | back-pointer to their previous node. | 
|  |  | 
|  | It is, however,  possible for an unsized doubly linked list to support direct | 
|  | node removal. To enable this, add the `AllowRemoveFromContainer` to the node | 
|  | state's `NodeOption`s. When enabled, node state structures will have a | 
|  | `RemoveFromContainer()` method available. Calling RemoveFromContainer is | 
|  | identical to calling InContainer. It may be called directly from the object if | 
|  | there is no ambiguity, using explicit types to select the container to remove | 
|  | from when inheritance produces ambiguity, or using the top level | 
|  | `fbl::RemoveFromContaier<Tag>(obj_ref)` call when using the | 
|  | `ContainableBaseClasses` helper. See [`InContainer()`](membership_tests.md#single-container) | 
|  |  | 
|  | Consider the following use case. You have a bunch of jobs that need to be | 
|  | processed by several stages of a pipeline. The pipeline stages each have a | 
|  | queue of pending work that threads take jobs from, process, and then queue | 
|  | to the next stage of a pipeline. | 
|  |  | 
|  | If you want to cancel a job while it is in flight, how do you easily know which | 
|  | pipeline stage it is in? One answer might be that you don't need to, as long as | 
|  | you can directly remove it from the processing stage it is currently in. This | 
|  | might end up looking like the following: | 
|  |  | 
|  | ```cpp | 
|  | struct PipelineTag {}; | 
|  | struct ActiveTag {}; | 
|  |  | 
|  | fbl::Mutex pipeline_lock; | 
|  |  | 
|  | class Job : public fbl::RefCounted<Job>, | 
|  | public fbl::ContainableBaseClasses< | 
|  | fbl::TaggedDoublyLinkedListable<fbl::RefPtr<Job>, PipelineTag, | 
|  | fbl::NodeOptions::AllowRemoveFromContainer>, | 
|  | fbl::TaggedWAVLTreeContainable<fbl::RefPtr<Job>, ActiveTag>> { | 
|  | public: | 
|  | // ... | 
|  | UniqueId GetKey() const { return unique_id_; } | 
|  | bool is_canceled() const TA_REQ(pipeline_lock) { return cancel_flag_; } | 
|  | void set_canceled() TA_REQ(pipeline_lock) { cancel_flag_ = true; } | 
|  | // ... | 
|  | private: | 
|  | bool cancel_flag_ TA_GUARDED(pipeline_lock) = false; | 
|  | }; | 
|  |  | 
|  | using PipelineQueue = fbl::TaggedDoublyLinkedList<fbl::RefPtr<Job>, PipelineTag>; | 
|  | std::array<PipelineQueue, 10> pipeline_stages TA_GUARDED(pipeline_lock); | 
|  | fbl::TaggedWAVLTree<fbl::RefPtr<Job>, ActiveTag> active_jobs TA_GUARDED(pipeline_lock); | 
|  |  | 
|  | zx_status_t QueueJob(fbl::RefPtr<Job> job) { | 
|  | ZX_DEBUG_ASSERT(job != nullptr); | 
|  | { | 
|  | fbl::AutoLock lock(&pipeline_lock); | 
|  |  | 
|  | // Can't queue a job for processing if it is already being processed. | 
|  | if (fbl::InContainer<ActiveTag>(*job)) { | 
|  | return ZX_ERR_BAD_STATE; | 
|  | } | 
|  |  | 
|  | // If we are not in the active set, then we had better not be in any of the | 
|  | // pipeline stages. | 
|  | ZX_DEBUG_ASSERT(!fbl::InContainer<PipelineTag>(*job)); | 
|  |  | 
|  | // Put the job into the active set and into the first pipeline stage. | 
|  | active_jobs.insert(job); | 
|  | pipeline_stages[0].push_back(std::move(job)); | 
|  | } | 
|  |  | 
|  | SignalPipelineStage(0); | 
|  | } | 
|  |  | 
|  | void WorkThread(size_t pipeline_stage) { | 
|  | ZX_DEBUG_ASSERT(pipeline_stage < pipeline_stages.size()); | 
|  | PipelineQueue& our_stage = pipeline_stages[pipeline_stage]; | 
|  | PipelineQueue* next_stage = ((pipeline_stage + 1) < pipeline_stages.size()) | 
|  | ? (pipeline_stages + pipeline_stage + 1) | 
|  | : nullptr; | 
|  |  | 
|  | while (!QuitTime()) { | 
|  | fbl::RefPtr<Job> job; | 
|  | { | 
|  | // If there is work in our stage, take it out and get to work. | 
|  | fbl::AutoLock lock(&pipeline_lock); | 
|  | if (!our_stage.is_empty()) { | 
|  | job = our_stage.pop_front(); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Did we not find a job? Just wait for something to do then. | 
|  | if (job == nullptr) { | 
|  | WaitForPipelineStageWorkOrQuit(pipeline_stage); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | // Do the job. | 
|  | ProcessJob(job, pipeline_stage); | 
|  |  | 
|  | // If the job was canceled or reached the end of the pipeline, we will call | 
|  | // a handler to take care of it once we are out of the lock. | 
|  | void(*handler)(fbl::RefPtr<Job>) = nullptr; | 
|  | { | 
|  | fbl::AutoLock lock(&pipeline_lock); | 
|  |  | 
|  | if (job->is_canceled()) { | 
|  | // Handle job cancellation if it was flagged for cancel while we were | 
|  | // working. No need to take it out of the active set, the cancel | 
|  | // operation should have already done that for us. | 
|  | ZX_DEBUG_ASSERT(!fbl::InContainer<ActiveTag>(*job)); | 
|  | handler = HandleCanceledJob; | 
|  | } else if (next_stage != nullptr) { | 
|  | // Queue to the next stage if there is one. | 
|  | next_stage->push_back(std::move(job)); | 
|  | signal_next_stage = true; | 
|  | } else { | 
|  | // End of pipeline. This job is finished, remember to take it out of | 
|  | // the active set. | 
|  | ZX_DEBUG_ASSERT(fbl::InContainer<ActiveTag>(*job)); | 
|  | active_jobs.erase(*job); | 
|  | handler = HandleFinishedJob; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Now that we are out of the lock, either signal the next stage so that it | 
|  | // knows that it might have some work, or call the chosen handler on the job. | 
|  | if (handler) { | 
|  | ZX_DEBUG_ASERT(job != nullptr); | 
|  | handler(std::move(job)); | 
|  | } else { | 
|  | SignalPipelineStage(pipeline_stage + 1); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | zx_status_t CancelJob(UniqueId id) { | 
|  | fbl::RefPtr<Job> canceled_job; | 
|  | { | 
|  | fbl::AutoLock lock(&pipeline_lock); | 
|  |  | 
|  | // Is there an active job with the provided ID? | 
|  | auto iter = active_jobs.find(id); | 
|  | if (!iter.IsValid()) { | 
|  | return ZX_ERR_NOT_FOUND; | 
|  | } | 
|  |  | 
|  | // No matter what, the job is no longer active. Take its reference back from | 
|  | // the active job set. | 
|  | fbl::RefPtr<Job> job = active_jobs.erase(iter); | 
|  |  | 
|  | // Flag the job as canceled. | 
|  | job->set_canceled(); | 
|  |  | 
|  | // If the job is in a pipeline stage, then no thread is currently working on | 
|  | // it. We can just pull it out of whatever stage we are in and we are done. | 
|  | if (fbl::InContainer<PipelineTag>(*job)) { | 
|  | canceled_job = fbl::RemoveFromContainer<PipelineTag>(*job); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Now that we are out of the lock, if we were the ones to pull the job out of | 
|  | // the pipeline, we should hand it over to the cancel handler. | 
|  | HandleCanceledJob(std::move(canceled_job)); | 
|  | return ZX_OK; | 
|  | } | 
|  | ``` |