Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 503a9d35 authored by Takaya Saeki's avatar Takaya Saeki
Browse files

ueventd: introduce UeventDependencyGraph class

Uevnt messages can be handled in parallel in a robust way only if we
parallelize events that have no dependency on others.
UeventDependencyGraph class manages the dependencies among Uevents, and
lets callers to take a Uevent which has no dependency on preceding
pending events.

Bug: 400592897
Test: unit tests pass
Change-Id: Ide51058480f6a7e3f630b7ac974d7090b922a2d6
parent 83f735c2
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -81,6 +81,7 @@ init_device_sources = [
    "snapuserd_transition.cpp",
    "switch_root.cpp",
    "uevent_listener.cpp",
    "uevent_dependency_graph.cpp",
    "ueventd.cpp",
    "ueventd_parser.cpp",
]
@@ -514,6 +515,8 @@ cc_test {
        "service_test.cpp",
        "subcontext_test.cpp",
        "tokenizer_test.cpp",
        "uevent_dependency_graph.cpp",
        "uevent_dependency_graph_test.cpp",
        "ueventd_parser_test.cpp",
        "ueventd_test.cpp",
        "util_test.cpp",
+142 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "uevent_dependency_graph.h"

#include <condition_variable>
#include <mutex>
#include <optional>

#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/thread_annotations.h>

namespace android {
namespace init {

/**
 * Finds the sequence number of the latest event that the given uevent depends on.
 * Dependencies arise from:
 * 1. Ancestor devices (e.g., "devices/block/sda" must be processed before
 *    "devices/block/sda/sda1").
 * 2. Descendant devices for "remove" actions (e.g., "devices/block/sda/sda1" must be removed
 *    before "devices/block/sda").
 * 3. Events for the identical device path with a lower sequence number.
 * Note rename events are not processed currently since it's not processed in the main ueventd.
 */
std::optional<UeventDependencyGraph::seqnum_t> UeventDependencyGraph::FindDependency(
        const Uevent& uevent) {
    int max_seqnum = -1;

    // e.g. devices/virtual/mac80211_hwsim/hwsim0 is descendant of devices/virtual/mac80211_hwsim.
    // They immediately follow uevent.path in the sorted event_paths_ map.
    auto descendant = event_paths_.upper_bound({uevent.path, uevent.seqnum});
    while (descendant != event_paths_.end() && descendant->first.starts_with(uevent.path)) {
        if (descendant->second < uevent.seqnum && descendant->second > max_seqnum) {
            max_seqnum = descendant->second;
        }
        descendant++;
    }

    // Find events of ancestor devices and the identical device with lower seqnum.
    // e.g. devices/some_device is descendant of devices/some_device/wakeup
    for (auto ancestor = uevent.path; ancestor != "/" && ancestor != ".";
         ancestor = base::Dirname(ancestor)) {
        auto it = event_paths_.upper_bound({ancestor, uevent.seqnum});
        if (it == event_paths_.begin()) {
            continue;
        }
        it--;
        if (it->first == ancestor && it->second > max_seqnum) {
            max_seqnum = it->second;
        }
    }

    if (max_seqnum == -1) {
        return std::nullopt;
    } else {
        return {max_seqnum};
    }
}

void UeventDependencyGraph::Add(Uevent uevent) {
    bool should_wake_thread = false;
    {
        std::lock_guard<std::mutex> lock(graph_lock_);
        std::optional<UeventDependencyGraph::seqnum_t> dependency = FindDependency(uevent);
        if (dependency) {
            dependencies_.emplace(dependency.value(), uevent.seqnum);
        } else {
            dependency_free_events_.emplace(uevent.seqnum);
            should_wake_thread = true;
        }
        event_paths_.emplace(uevent.path, uevent.seqnum);
        events_.emplace(uevent.seqnum, std::move(uevent));
    }
    if (should_wake_thread) {
        graph_condvar_.notify_one();
    }
}

std::optional<Uevent> UeventDependencyGraph::PopDependencyFreeEventWithoutLock() {
    if (dependency_free_events_.empty()) {
        return std::nullopt;
    }
    auto seqnum = dependency_free_events_.front();
    dependency_free_events_.pop();
    return events_.find(seqnum)->second;
}

std::optional<Uevent> UeventDependencyGraph::PopDependencyFreeEvent() {
    std::lock_guard<std::mutex> lock(graph_lock_);
    return PopDependencyFreeEventWithoutLock();
}

Uevent UeventDependencyGraph::WaitDependencyFreeEvent() {
    std::unique_lock<std::mutex> lock(graph_lock_);
    // Assertion is required to make thread safety annotations work well with a unique_lock
    base::ScopedLockAssertion mutex_lock_assertion(graph_lock_);

    if (dependency_free_events_.empty()) {
        graph_condvar_.wait(lock, [this] {
            base::ScopedLockAssertion mutex_lock_assertion(graph_lock_);
            return !dependency_free_events_.empty();
        });
    }

    return PopDependencyFreeEventWithoutLock().value();
}

void UeventDependencyGraph::MarkEventCompleted(seqnum_t seqnum) {
    bool should_wake_thread = false;
    {
        std::lock_guard<std::mutex> lock(graph_lock_);
        auto dependency = dependencies_.equal_range(seqnum);
        for (auto it = dependency.first; it != dependency.second; ++it) {
            dependency_free_events_.emplace(it->second);
            should_wake_thread = true;
        }
        dependencies_.erase(dependency.first, dependency.second);
        event_paths_.erase({events_.find(seqnum)->second.path, seqnum});
        events_.erase(seqnum);
    }
    if (should_wake_thread) {
        graph_condvar_.notify_one();
    }
}

}  // namespace init
}  // namespace android
+121 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma once

#include <condition_variable>
#include <map>
#include <queue>
#include <set>

#include <android-base/thread_annotations.h>

#include "uevent.h"

namespace android {
namespace init {

/**
 * Manages dependencies between uevents to ensure they are processed in the correct order.
 *
 * Uevents often have dependencies based on their device path. For example, a child device's
 * uevent should typically be processed only after its parent device's uevent has been processed.
 * Similarly, events for the same device should be processed sequentially based on their sequence
 * number.
 *
 * This class builds a dependency graph based on device paths and sequence numbers. It allows
 * adding new uevents and retrieving events that have no outstanding dependencies, ready for
 * processing. Once an event is processed, it should be marked as completed to unblock any
 * dependent events.
 *
 * This class is thread-safe.
 */
class UeventDependencyGraph {
    using seqnum_t = long long;

  public:
    UeventDependencyGraph() = default;

    /**
     * Adds a new uevent to the dependency graph.
     *
     * @param uevent The uevent to add to the graph.
     */
    void Add(Uevent uevent);

    /**
     * Retrieves and removes a uevent that has no outstanding dependencies.
     *
     * This method returns any uevents ready for processing (i.e., all their
     * dependencies have been met).  If no events are ready, it returns std::nullopt immediately.
     *
     * @return An optional containing a dependency-free uevent if one is available, otherwise
     * std::nullopt.
     */
    std::optional<Uevent> PopDependencyFreeEvent();

    /**
     * Waits until a dependency-free uevent is available, then retrieves and removes it.
     *
     * If no dependency-free events are currently available, this method blocks until one becomes
     * available (due to a call to Add() or MarkEventCompleted()).
     *
     * @return The next available dependency-free uevent.
     */
    Uevent WaitDependencyFreeEvent();

    /**
     * Marks a uevent as completed, potentially unblocking dependent events.
     *
     * @param seqnum The sequence number of the uevent that has been completed.
     */
    void MarkEventCompleted(seqnum_t seqnum);

  private:
    /**
     * Finds the sequence number of the latest event that the given uevent depends on.
     *
     * @param uevent The uevent to find dependencies for.
     * @return An optional containing the sequence number of the dependency if found, otherwise
     * std::nullopt.
     */
    std::optional<seqnum_t> FindDependency(const Uevent& uevent)
            EXCLUSIVE_LOCKS_REQUIRED(graph_lock_);

    /**
     * Internal implementation of PopDependencyFreeEvent without locking.
     * Assumes the caller holds the graph_lock_.
     *
     * @return An optional containing a dependency-free uevent if one is available, otherwise
     * std::nullopt.
     */
    std::optional<Uevent> PopDependencyFreeEventWithoutLock() EXCLUSIVE_LOCKS_REQUIRED(graph_lock_);

    std::condition_variable graph_condvar_;
    std::mutex graph_lock_;
    // Stores all uevents currently in the graph, keyed by sequence number.
    std::map<seqnum_t, Uevent> events_ GUARDED_BY(graph_lock_);
    // Queue of events that are ready to be processed.
    std::queue<seqnum_t> dependency_free_events_ GUARDED_BY(graph_lock_);
    // Multimap storing dependencies: key is the sequence number of the prerequisite event,
    // value is the sequence number of the dependent event.
    std::multimap<seqnum_t, seqnum_t> dependencies_ GUARDED_BY(graph_lock_);
    // Set storing pairs of (device path, sequence number) for efficient dependency lookup.
    std::set<std::pair<std::string, seqnum_t>> event_paths_ GUARDED_BY(graph_lock_);
};

}  // namespace init
}  // namespace android
+295 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "uevent_dependency_graph.h"

#include <cstdlib>
#include <mutex>
#include <optional>
#include <thread>

#include <gtest/gtest.h>

#include "uevent.h"

namespace android {
namespace init {

TEST(UeventDependencyGraphTest, NoDependency) {
    UeventDependencyGraph graph;
    Uevent uevent1 = {.action = "add", .path = "devices/block/sda", .seqnum = 1};
    Uevent uevent2 = {.action = "add", .path = "devices/block/sdb", .seqnum = 2};

    graph.Add(uevent1);
    graph.Add(uevent2);

    std::optional<Uevent> result1 = graph.PopDependencyFreeEvent();
    std::optional<Uevent> result2 = graph.PopDependencyFreeEvent();

    EXPECT_TRUE(result1.has_value());
    EXPECT_TRUE(result2.has_value());
    EXPECT_EQ(1, result1->seqnum);
    EXPECT_EQ(2, result2->seqnum);
}

TEST(UeventDependencyGraphTest, AncestorDependencies) {
    UeventDependencyGraph graph;
    Uevent uevent1 = {.action = "add", .path = "devices/block/sda", .seqnum = 1};
    Uevent uevent2 = {.action = "add", .path = "devices/block/sda/child1", .seqnum = 2};
    Uevent uevent3 = {.action = "add", .path = "devices/block/sda/child2", .seqnum = 3};
    Uevent uevent4 = {.action = "add", .path = "devices/block/sda/child1/grandchild", .seqnum = 4};

    graph.Add(uevent1);
    graph.Add(uevent2);
    graph.Add(uevent3);
    graph.Add(uevent4);

    EXPECT_EQ(graph.PopDependencyFreeEvent()->seqnum, uevent1.seqnum);
    EXPECT_FALSE(graph.PopDependencyFreeEvent().has_value());
    graph.MarkEventCompleted(uevent1.seqnum);
    EXPECT_EQ(graph.PopDependencyFreeEvent()->seqnum, uevent2.seqnum);
    EXPECT_EQ(graph.PopDependencyFreeEvent()->seqnum, uevent3.seqnum);
    EXPECT_FALSE(graph.PopDependencyFreeEvent().has_value());
    graph.MarkEventCompleted(uevent2.seqnum);
    EXPECT_EQ(graph.PopDependencyFreeEvent()->seqnum, uevent4.seqnum);
    EXPECT_FALSE(graph.PopDependencyFreeEvent().has_value());
}

TEST(UeventDependencyGraphTest, DescendantDependencies) {
    UeventDependencyGraph graph;
    Uevent uevent1 = {
            .action = "remove", .path = "devices/block/sda/child1/grandchild", .seqnum = 1};
    Uevent uevent2 = {.action = "remove", .path = "devices/block/sda/child1", .seqnum = 2};
    Uevent uevent3 = {.action = "remove", .path = "devices/block/sda", .seqnum = 3};

    graph.Add(uevent1);
    graph.Add(uevent2);
    graph.Add(uevent3);

    EXPECT_EQ(graph.PopDependencyFreeEvent()->seqnum, uevent1.seqnum);
    EXPECT_FALSE(graph.PopDependencyFreeEvent().has_value());
    graph.MarkEventCompleted(uevent1.seqnum);
    EXPECT_EQ(graph.PopDependencyFreeEvent()->seqnum, uevent2.seqnum);
    EXPECT_FALSE(graph.PopDependencyFreeEvent().has_value());
    graph.MarkEventCompleted(uevent2.seqnum);
    EXPECT_EQ(graph.PopDependencyFreeEvent()->seqnum, uevent3.seqnum);
    EXPECT_FALSE(graph.PopDependencyFreeEvent().has_value());
}

TEST(UeventDependencyGraphTest, IdenticalEventDependencies) {
    UeventDependencyGraph graph;
    Uevent uevent1 = {.action = "add", .path = "devices/block/sda", .seqnum = 1};
    Uevent uevent2 = {.action = "change", .path = "devices/block/sda", .seqnum = 2};
    Uevent uevent3 = {.action = "remove", .path = "devices/block/sda", .seqnum = 3};

    graph.Add(uevent1);
    graph.Add(uevent2);
    graph.Add(uevent3);

    EXPECT_EQ(graph.PopDependencyFreeEvent()->seqnum, uevent1.seqnum);
    EXPECT_FALSE(graph.PopDependencyFreeEvent().has_value());
    graph.MarkEventCompleted(uevent1.seqnum);
    EXPECT_EQ(graph.PopDependencyFreeEvent()->seqnum, uevent2.seqnum);
    EXPECT_FALSE(graph.PopDependencyFreeEvent().has_value());
    graph.MarkEventCompleted(uevent2.seqnum);
    EXPECT_EQ(graph.PopDependencyFreeEvent()->seqnum, uevent3.seqnum);
    EXPECT_FALSE(graph.PopDependencyFreeEvent().has_value());
}

TEST(UeventDependencyGraphTest, MixedDependencies) {
    UeventDependencyGraph graph;
    Uevent uevent = {.action = "add", .path = "devices/block/sda", .seqnum = 1};
    Uevent uevent_child_dep = {
            .action = "add", .path = "devices/block/sda/child_dependency", .seqnum = 2};
    Uevent uevent_parent_dep = {.action = "change", .path = "devices/block/sda", .seqnum = 3};
    Uevent uevent_self_dep = {.action = "remove", .path = "devices/block/sda", .seqnum = 4};
    Uevent uevent_no_dependency = {.action = "add", .path = "devices/snd/foo", .seqnum = 5};

    graph.Add(uevent);
    graph.Add(uevent_child_dep);
    graph.Add(uevent_parent_dep);
    graph.Add(uevent_self_dep);
    graph.Add(uevent_no_dependency);

    EXPECT_EQ(graph.PopDependencyFreeEvent()->seqnum, uevent.seqnum);
    EXPECT_EQ(graph.PopDependencyFreeEvent()->seqnum, uevent_no_dependency.seqnum);
    EXPECT_FALSE(graph.PopDependencyFreeEvent().has_value());
    graph.MarkEventCompleted(uevent.seqnum);
    EXPECT_EQ(graph.PopDependencyFreeEvent()->seqnum, uevent_child_dep.seqnum);
    EXPECT_FALSE(graph.PopDependencyFreeEvent().has_value());
    graph.MarkEventCompleted(uevent_child_dep.seqnum);
    EXPECT_EQ(graph.PopDependencyFreeEvent()->seqnum, uevent_parent_dep.seqnum);
    EXPECT_FALSE(graph.PopDependencyFreeEvent().has_value());
    graph.MarkEventCompleted(uevent_parent_dep.seqnum);
    EXPECT_EQ(graph.PopDependencyFreeEvent()->seqnum, uevent_self_dep.seqnum);
    EXPECT_FALSE(graph.PopDependencyFreeEvent().has_value());
    graph.MarkEventCompleted(uevent_self_dep.seqnum);
    EXPECT_FALSE(graph.PopDependencyFreeEvent().has_value());
}

TEST(UeventDependencyGraphTest, DependsOnLaterEventsNotOnEarlier) {
    UeventDependencyGraph graph;
    Uevent uevent = {.action = "add", .path = "devices/block/sda", .seqnum = 1};
    Uevent uevent_child_add = {
            .action = "add", .path = "devices/block/sda/child_dependency", .seqnum = 2};
    Uevent uevent_grandchild_add1 = {
            .action = "add", .path = "devices/block/sda/child_dependency", .seqnum = 3};
    Uevent uevent_removal = {.action = "remove", .path = "devices/block/sda", .seqnum = 4};
    Uevent uevent_grandchild_add2 = {
            .action = "add", .path = "devices/block/sda/child_dependency/grandchild", .seqnum = 5};

    EXPECT_FALSE(graph.PopDependencyFreeEvent().has_value());

    graph.Add(uevent);
    EXPECT_EQ(graph.PopDependencyFreeEvent()->seqnum, uevent.seqnum);
    EXPECT_FALSE(graph.PopDependencyFreeEvent().has_value());

    // New events should not be immediately available due to the dependency.
    graph.Add(uevent_child_add);
    graph.Add(uevent_grandchild_add1);
    graph.Add(uevent_removal);
    graph.Add(uevent_grandchild_add2);
    EXPECT_FALSE(graph.PopDependencyFreeEvent().has_value());

    // Should kick only uevent_child_add
    graph.MarkEventCompleted(uevent.seqnum);
    EXPECT_EQ(graph.PopDependencyFreeEvent()->seqnum, uevent_child_add.seqnum);
    EXPECT_FALSE(graph.PopDependencyFreeEvent().has_value());

    // Should kick only uevent_grandchild_add1
    graph.MarkEventCompleted(uevent_child_add.seqnum);
    EXPECT_EQ(graph.PopDependencyFreeEvent()->seqnum, uevent_grandchild_add1.seqnum);
    EXPECT_FALSE(graph.PopDependencyFreeEvent().has_value());

    // Should kick only uevent_removal
    graph.MarkEventCompleted(uevent_grandchild_add1.seqnum);
    EXPECT_EQ(graph.PopDependencyFreeEvent()->seqnum, uevent_removal.seqnum);
    EXPECT_FALSE(graph.PopDependencyFreeEvent().has_value());

    // Should kick only uevent_grandchild_add2
    graph.MarkEventCompleted(uevent_removal.seqnum);
    EXPECT_EQ(graph.PopDependencyFreeEvent()->seqnum, uevent_grandchild_add2.seqnum);
    EXPECT_FALSE(graph.PopDependencyFreeEvent().has_value());

    // No more events should be available
    graph.MarkEventCompleted(uevent_grandchild_add2.seqnum);
    EXPECT_FALSE(graph.PopDependencyFreeEvent().has_value());
}

TEST(UeventDependencyGraphTest, PushEventsWithDependencyOnPending) {
    UeventDependencyGraph graph;
    Uevent uevent = {.action = "add", .path = "devices/block/sda", .seqnum = 1};
    Uevent uevent_child_dep = {
            .action = "add", .path = "devices/block/sda/child_dependency", .seqnum = 2};
    Uevent uevent_grandchild_dep1 = {
            .action = "add",
            .path = "devices/block/sda/child_dependency/grandchild_dependency1",
            .seqnum = 3};
    Uevent uevent_grandchild_dep2 = {
            .action = "add",
            .path = "devices/block/sda/child_dependency/grandchild_dependency2",
            .seqnum = 4};

    graph.Add(uevent);
    EXPECT_EQ(graph.PopDependencyFreeEvent()->seqnum, uevent.seqnum);
    EXPECT_FALSE(graph.PopDependencyFreeEvent().has_value());

    graph.Add(uevent_child_dep);
    EXPECT_FALSE(graph.PopDependencyFreeEvent().has_value());
    graph.MarkEventCompleted(uevent.seqnum);
    EXPECT_EQ(graph.PopDependencyFreeEvent()->seqnum, uevent_child_dep.seqnum);

    graph.Add(uevent_grandchild_dep1);
    EXPECT_FALSE(graph.PopDependencyFreeEvent().has_value());
    graph.Add(uevent_grandchild_dep2);
    EXPECT_FALSE(graph.PopDependencyFreeEvent().has_value());

    graph.MarkEventCompleted(uevent_child_dep.seqnum);
    EXPECT_EQ(graph.PopDependencyFreeEvent()->seqnum, uevent_grandchild_dep1.seqnum);
    EXPECT_EQ(graph.PopDependencyFreeEvent()->seqnum, uevent_grandchild_dep2.seqnum);
    EXPECT_FALSE(graph.PopDependencyFreeEvent().has_value());
}

TEST(UeventDependencyGraphTest, WaitDependencyFreeEventBlocksUntilDependencyIsMet) {
    UeventDependencyGraph graph;
    bool t_started = false;
    std::mutex m;
    std::condition_variable cv;

    Uevent uevent1 = {.action = "add", .path = "devices/block/sda", .seqnum = 1};
    Uevent uevent2 = {.action = "add", .path = "devices/block/sda/child", .seqnum = 2};
    Uevent uevent3 = {.action = "add", .path = "devices/block/sda/child/grandchild", .seqnum = 3};

    graph.Add(uevent1);
    graph.Add(uevent2);
    graph.Add(uevent3);
    EXPECT_EQ(graph.PopDependencyFreeEvent()->seqnum, uevent1.seqnum);
    EXPECT_FALSE(graph.PopDependencyFreeEvent().has_value());

    std::thread t([&graph, &uevent2, &t_started, &m, &cv]() {
        m.lock();
        t_started = true;
        m.unlock();
        cv.notify_all();
        // Should wait until uevent2 is available
        EXPECT_EQ(graph.WaitDependencyFreeEvent().seqnum, uevent2.seqnum);
        // Unblock uevent3
        graph.MarkEventCompleted(uevent2.seqnum);
    });

    // Wait for the thread to start, which waits for uevent2
    std::unique_lock<std::mutex> lock(m);
    cv.wait(lock, [&t_started] { return t_started; });
    lock.unlock();
    // Kick the uevent2 for the thread
    graph.MarkEventCompleted(uevent1.seqnum);
    t.join();
    EXPECT_EQ(graph.PopDependencyFreeEvent()->seqnum, uevent3.seqnum);
}

TEST(UeventDependencyGraphTest, WaitDependencyFreeEventReturnsIfDependencyFreeOneIsAvailable) {
    UeventDependencyGraph graph;
    bool t_started = false;
    std::mutex m;
    std::condition_variable cv;

    Uevent uevent1 = {.action = "add", .path = "devices/block/sda", .seqnum = 1};
    Uevent uevent2 = {.action = "add", .path = "devices/block/sda/child", .seqnum = 2};
    Uevent uevent3 = {.action = "add", .path = "devices/block/sda/child/grandchild", .seqnum = 3};

    graph.Add(uevent1);
    graph.Add(uevent2);
    graph.Add(uevent3);

    // No dependency free events are available until uevent1 is processed
    EXPECT_EQ(graph.PopDependencyFreeEvent()->seqnum, uevent1.seqnum);
    EXPECT_FALSE(graph.PopDependencyFreeEvent().has_value());

    // Should kick uevent2 and WaitDependencyFreeEvent immediately returns it.
    graph.MarkEventCompleted(uevent1.seqnum);
    EXPECT_EQ(graph.WaitDependencyFreeEvent().seqnum, uevent2.seqnum);

    // Should kick uevent3 and WaitDependencyFreeEvent immediately returns it.
    graph.MarkEventCompleted(uevent2.seqnum);
    EXPECT_EQ(graph.WaitDependencyFreeEvent().seqnum, uevent3.seqnum);

    // No more events should be available
    graph.MarkEventCompleted(uevent3.seqnum);
    EXPECT_FALSE(graph.PopDependencyFreeEvent().has_value());
}

}  // namespace init
}  // namespace android