Automated commit: libchrome r1132882 uprev
Merge with upstream commit eb2d2f5119b1fd920ffbf3c127cbf8fad6ac9a5d
BUG=None
TEST=sudo emerge libchrome
Change-Id: I84a4b00acbe6b15918ba62b7f4ad7fbc157ab351
diff --git a/BASE_VER b/BASE_VER
index b764763..022643a 100644
--- a/BASE_VER
+++ b/BASE_VER
@@ -1 +1 @@
-1132288
+1132882
diff --git a/base/android/java/src/org/chromium/base/ResettersForTesting.java b/base/android/java/src/org/chromium/base/ResettersForTesting.java
new file mode 100644
index 0000000..ecd25b0
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/ResettersForTesting.java
@@ -0,0 +1,120 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+
+/**
+ * ResettersForTesting provides functionality for reset values set for testing. This class is used
+ * directly by test runners, but lives in prod code to simplify usage.
+ *
+ * It is required to invoke {@link #register(Runnable)} whenever a method called `set*ForTesting`,
+ * such `setFooForTesting(Foo foo)` is invoked. Typical usage looks like this:
+ *
+ * <code>
+ * class MyClass {
+ * private static MyClass sInstance;
+ *
+ * public static MyClass getInstance() {
+ * if (sInstance == null) sInstance = new MyClass();
+ * return sInstance;
+ * }
+ *
+ * public static void setMyClassForTesting(MyClass myClassObj) {
+ * sInstance = myClassObj;
+ * ResettersForTesting.register(() -> sInstance = null);
+ * }
+ * }
+ * </code>
+ *
+ * This is not only used for singleton instances, but can also be used for resetting other static
+ * members.
+ *
+ * <code>
+ * class NeedsFoo {
+ * private static Foo sFooForTesting;
+ *
+ * public void doThing() {
+ * Foo foo;
+ * if (sFooForTesting != null) {
+ * foo = sFooForTesting;
+ * } else {* foo = new FooImpl();
+ * }
+ * foo.doItsThing();
+ * }
+ *
+ * public static void setFooForTesting(Foo foo) {
+ * sFooForTesting = foo;
+ * ResettersForTesting.register(() -> sFooForTesting = null);
+ * }
+ * }
+ * </code>
+ *
+ * For cases where it is important that a particular resetter runs only once, even if the
+ * `set*ForTesting` method is invoked multiple times, there is another variation that can be used.
+ * In particular, since a lambda always ends up creating a new instance in Chromium builds, we can
+ * avoid this by having a single static instance of the resetter, like this:
+ *
+ * <code>
+ * private static class NeedsFooSingleDestroy {
+ * private static final class LazyHolder {
+ * private static Foo INSTANCE = new Foo();
+ * }
+ *
+ * private static LazyHolder sFoo;
+ *
+ * private static Runnable sOneShotResetter = () -> {
+ * sFoo.INSTANCE.destroy();
+ * sFoo = new Foo();
+ * };
+ *
+ * public static void setFooForTesting(Foo foo) {
+ * sFoo.INSTANCE = foo;
+ * ResettersForTesting.register(sResetter);
+ * }
+ * }
+ * </code>
+ */
+public class ResettersForTesting {
+ // LinkedHashSet is a set that provides ordering and enables one-shot resetters to only be
+ // invoked once. For example, the following `sResetter` will only be in the set a single time.
+ // <code>
+ // private static final Runnable sResetter = () -> { ... }
+ // ...
+ // ResettersForTesting.register(sResetter);
+ // </code>
+ private static final LinkedHashSet<Runnable> sResetters = new LinkedHashSet<>();
+
+ /**
+ * Register a {@link Runnable} that will automatically execute during test tear down.
+ * @param runnable the {@link Runnable} to execute.
+ */
+ public static void register(Runnable runnable) {
+ synchronized (sResetters) {
+ sResetters.add(runnable);
+ }
+ }
+
+ /**
+ * Execute and clear all the currently registered resetters.
+ *
+ * This is not intended to be invoked manually, but is intended to be invoked by the test
+ * runners automatically during tear down.
+ */
+ public static void executeResetters() {
+ ArrayList<Runnable> resetters;
+ synchronized (sResetters) {
+ resetters = new ArrayList<>(sResetters);
+ sResetters.clear();
+ }
+
+ // Ensure that resetters are run in reverse order, enabling nesting of values as well as
+ // being more similar to C++ destruction order.
+ Collections.reverse(resetters);
+ for (Runnable resetter : resetters) resetter.run();
+ }
+}
diff --git a/base/android/jni_generator/manual_jni_registration_proguard.flags b/base/android/jni_generator/manual_jni_registration_proguard.flags
deleted file mode 100644
index 23d7120..0000000
--- a/base/android/jni_generator/manual_jni_registration_proguard.flags
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright 2023 The Chromium Authors
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# Unused methods must be kept for manual jni registration.
--keepclasseswithmembers,includedescriptorclasses,allowaccessmodification class !org.chromium.base.library_loader.**,** {
- native <methods>;
-}
diff --git a/base/android/junit/src/org/chromium/base/ResettersForTestingTest.java b/base/android/junit/src/org/chromium/base/ResettersForTestingTest.java
new file mode 100644
index 0000000..eb369f6
--- /dev/null
+++ b/base/android/junit/src/org/chromium/base/ResettersForTestingTest.java
@@ -0,0 +1,145 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import static junit.framework.Assert.assertEquals;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+
+/**
+ * Unit tests for {@link ResettersForTesting}.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class ResettersForTestingTest {
+ private static class ResetsToNull {
+ public static String str;
+ public static void setStrForTesting(String newStr) {
+ str = newStr;
+ ResettersForTesting.register(() -> str = null);
+ }
+ }
+
+ private static class ResetsToOldValue {
+ public static String str;
+
+ public static void setStrForTesting(String newStr) {
+ String oldValue = str;
+ str = newStr;
+ ResettersForTesting.register(() -> str = oldValue);
+ }
+ }
+
+ private static class ResetsToNullAndIncrements {
+ public static String str;
+ public static int resetCount;
+
+ public static void setStrForTesting(String newStr) {
+ str = newStr;
+ ResettersForTesting.register(() -> {
+ str = null;
+ resetCount++;
+ });
+ }
+ }
+
+ private static class ResetsToNullAndIncrementsWithOneShotResetter {
+ public static String str;
+ public static int resetCount;
+ private static Runnable sResetter = () -> {
+ str = null;
+ resetCount++;
+ };
+
+ public static void setStrForTesting(String newStr) {
+ str = newStr;
+ ResettersForTesting.register(sResetter);
+ }
+ }
+
+ @Test
+ public void testTypicalUsage() {
+ ResetsToNull.setStrForTesting("foo");
+ assertEquals("foo", ResetsToNull.str);
+ ResettersForTesting.executeResetters();
+ Assert.assertNull(ResetsToNull.str);
+ }
+
+ @Test
+ public void testResetsToPreviousValue() {
+ // Inject a previous value to verify that we can get back to it.
+ ResetsToOldValue.str = "bar";
+
+ ResetsToOldValue.setStrForTesting("foo");
+ assertEquals("foo", ResetsToOldValue.str);
+
+ // After resetting the value, it should be back to the first value.
+ ResettersForTesting.executeResetters();
+ assertEquals("bar", ResetsToOldValue.str);
+ }
+
+ @Test
+ public void testMultipleResets() {
+ // Inject an outer value to verify we can get back to this.
+ ResetsToOldValue.str = "qux";
+
+ // Then set the next value.
+ ResetsToOldValue.setStrForTesting("foo");
+ assertEquals("foo", ResetsToOldValue.str);
+
+ // Now, next that value into another one to ensure the unwinding works.
+ ResetsToOldValue.setStrForTesting("bar");
+ assertEquals("bar", ResetsToOldValue.str);
+
+ // Since we are invoking the resetters in the reverse order, we should now be back to start.
+ ResettersForTesting.executeResetters();
+ assertEquals("qux", ResetsToOldValue.str);
+ }
+
+ @Test
+ public void testResettersExecutedOnlyOnce() {
+ // Force set this to 0 for this particular test.
+ ResetsToNullAndIncrements.resetCount = 0;
+ ResetsToNullAndIncrements.str = null;
+
+ // Set the initial value and register the resetter.
+ ResetsToNullAndIncrements.setStrForTesting("some value");
+ assertEquals("some value", ResetsToNullAndIncrements.str);
+
+ // Now, execute all resetters and ensure it's only executed once.
+ ResettersForTesting.executeResetters();
+ assertEquals(1, ResetsToNullAndIncrements.resetCount);
+
+ // Execute the resetters again, and verify it does not invoke the same resetter again.
+ ResettersForTesting.executeResetters();
+ assertEquals(1, ResetsToNullAndIncrements.resetCount);
+ }
+
+ @Test
+ public void testResettersExecutedOnlyOnceForOneShotResetters() {
+ // Force set this to 0 for this particular test.
+ ResetsToNullAndIncrementsWithOneShotResetter.resetCount = 0;
+ ResetsToNullAndIncrementsWithOneShotResetter.str = null;
+
+ // Set the initial value and register the resetter twice.
+ ResetsToNullAndIncrementsWithOneShotResetter.setStrForTesting("some value");
+ ResetsToNullAndIncrementsWithOneShotResetter.setStrForTesting("some other value");
+ assertEquals("some other value", ResetsToNullAndIncrementsWithOneShotResetter.str);
+
+ // Now, execute all resetters and ensure it's only executed once, since it is a single
+ // instance of the same resetter.
+ ResettersForTesting.executeResetters();
+ assertEquals(1, ResetsToNullAndIncrementsWithOneShotResetter.resetCount);
+
+ // Execute the resetters again, and verify it does not invoke the same resetter again.
+ ResettersForTesting.executeResetters();
+ assertEquals(1, ResetsToNullAndIncrementsWithOneShotResetter.resetCount);
+ }
+}
diff --git a/base/android/proguard/chromium_code.flags b/base/android/proguard/chromium_code.flags
index b8ee9ea..5f5bb55 100644
--- a/base/android/proguard/chromium_code.flags
+++ b/base/android/proguard/chromium_code.flags
@@ -13,8 +13,12 @@
@androidx.annotation.Keep <methods>;
}
-# Allow unused native methods to be removed, but prevent renaming on those that are kept.
--keepclasseswithmembernames,includedescriptorclasses,allowaccessmodification class ** {
+# Even unused methods kept due to explicit jni registration:
+# https://crbug.com/688465.
+-keepclasseswithmembers,includedescriptorclasses,allowaccessmodification class !org.chromium.base.library_loader.**,** {
+ native <methods>;
+}
+-keepclasseswithmembernames,includedescriptorclasses,allowaccessmodification class org.chromium.base.library_loader.** {
native <methods>;
}
diff --git a/base/containers/span.h b/base/containers/span.h
index 65b392b..ce3e017 100644
--- a/base/containers/span.h
+++ b/base/containers/span.h
@@ -6,6 +6,7 @@
#define BASE_CONTAINERS_SPAN_H_
#include <stddef.h>
+#include <stdint.h>
#include <array>
#include <iterator>
diff --git a/base/message_loop/fd_watch_controller_posix_unittest.cc b/base/message_loop/fd_watch_controller_posix_unittest.cc
index 766f392..f937ca9 100644
--- a/base/message_loop/fd_watch_controller_posix_unittest.cc
+++ b/base/message_loop/fd_watch_controller_posix_unittest.cc
@@ -131,10 +131,10 @@
TEST_F(FdWatchControllerPosixTest, FileDescriptorWatcherOutlivesMessageLoop) {
// Simulate a MessageLoop that dies before an FileDescriptorWatcher.
// This could happen when people use the Singleton pattern or atexit.
+ TestHandler handler;
// Arrange for watcher to live longer than message loop.
MessagePumpForIO::FdWatchController watcher(FROM_HERE);
- TestHandler handler;
{
test::TaskEnvironment env(test::TaskEnvironment::MainThreadType::IO);
diff --git a/base/message_loop/message_pump_glib.cc b/base/message_loop/message_pump_glib.cc
index 7c046dc..dfc8a95 100644
--- a/base/message_loop/message_pump_glib.cc
+++ b/base/message_loop/message_pump_glib.cc
@@ -103,6 +103,153 @@
// we run DoWork. That part will also run in the other event pumps.
// - We also run DoWork, and possibly DoIdleWork, in the main loop,
// around event handling.
+//
+// ---------------------------------------------------------------------------
+//
+// An overview on the way that we track work items:
+//
+// ScopedDoWorkItems are used by this pump to track native work. They are
+// stored by value in |state_| and are set/cleared as the pump runs. Their
+// setting and clearing is done in the functions
+// {Set,Clear,EnsureSet,EnsureCleared}ScopedWorkItem. Control flow in GLib is
+// quite non-obvious because chrome is not notified when a nested loop is
+// entered/exited. To detect nested loops, MessagePumpGlib uses
+// |state_->do_work_depth| which is incremented when DoWork is entered, and a
+// GLib library function, g_main_depth(), which indicates the current number of
+// Dispatch() calls on the stack. To react to them, two separate
+// ScopedDoWorkItems are used (a standard one used for all native work, and a
+// second one used exclusively for forcing nesting when there is a native loop
+// spinning). Note that `ThreadController` flags all nesting as
+// `Phase::kNested` so separating native and application work while nested isn't
+// supported nor a goal.
+//
+// It should also be noted that a second GSource has been added to GLib,
+// referred to as the "observer" source. It is used because in the case where
+// native work occurs on wakeup that is higher priority than Chrome (all of
+// GTK), chrome won't even get notified that the pump is awake.
+//
+// There are several cases to consider wrt. nesting level and order. In
+// order, we have:
+// A. [root] -> MessagePump::Run() -> native event -> g_main_context_iteration
+// B. [root] -> MessagePump::Run() -> DoWork -> g_main_context_iteration
+// C. [root] -> native -> DoWork -> MessagePump -> [...]
+// The second two cases are identical for our purposes, and the last one turns
+// out to be handled without any extra headache.
+//
+// Consider nesting case A, where native work is called from
+// |g_main_context_iteration()| from the pump, and that native work spins up a
+// loop. For our purposes, this is a nested loop, because control is not
+// returned to the pump once one iteration of the pump is complete. In this
+// case, the pump needs to enter nesting without DoWork being involved at
+// all. This is accomplished using |MessagePumpGlib::NestIfRequired()|, which is
+// called during the Prepare() phase of GLib. As the pump records state on entry
+// and exit from GLib using |OnEntryToGlib| and |OnExitFromGlib|, we can compare
+// |g_main_depth| at |HandlePrepare| with the one before we entered
+// |g_main_context_iteration|. If it is higher, there is a native loop being
+// spun, and |RegisterNesting| is called, forcing nesting by initializing two
+// work items at once. These are destroyed after the exit from
+// |g_main_context_iteration| using |OnExitFromGlib|.
+//
+// Then, considering nesting case B, |state_->do_work_depth| is incremented
+// during any Chrome work, to allow the pump to detect re-entrancy during a
+// chrome work item. This is required because `g_main_depth` is not incremented
+// in any `DoWork` call not occuring during `Dispatch()` (i.e. during
+// `MessagePumpGlib::Run()`). In this case, a nested loop is recorded, and the
+// pump sets-and-clears scoped work items during Prepare, Check, and Dispatch. A
+// work item can never be active when control flow returns to GLib (i.e. on
+// return) during a nested loop, because the nested loop could exit at any
+// point. This is fine because TimeKeeper is only concerned with the fact that a
+// nested loop is in progress, as opposed to the various phases of the nested
+// loop.
+//
+// Finally, consider nesting case C, where a native loop is spinning
+// entirely outside of Chrome, such as inside a signal handler, the pump might
+// create and destroy DoWorkItems during Prepare() and Check(), but these work
+// items will always get cleared during Dispatch(), before the pump enters a
+// DoWork(), leading to the pump showing non-nested native work without the
+// thread controller being active, the correct situation (which won't occur
+// outside of startup or shutdown). Once Dispatch() is called, the pump's
+// nesting tracking works correctly, as state_->do_work_depth is increased, and
+// upon re-entrancy we detect the nested loop, which is correct, as this is the
+// only point at which the loop actually becomes "nested".
+//
+// -----------------------------------------------------------------------------
+//
+// As an overview of the steps taken by MessagePumpGLib to ensure that nested
+// loops are detected adequately during each phase of the GLib loop:
+//
+// 0: Before entering GLib:
+// 0.1: Record state about current state of GLib (g_main_depth()) for
+// case 1.1.2.
+//
+// 1: Prepare.
+// 1.1: Detection of nested loops
+
+// 1.1.1: If |state_->do_work_depth| > 0, we are in nesting case B detailed
+// above. A work item must be newly created during this function to
+// trigger nesting, and is destroyed to ensure proper destruction order
+// in the case where GLib quits after Prepare().
+//
+// 1.1.2: Otherwise, check if we are in nesting case A above. If yes, trigger
+// nesting using ScopedDoWorkItems. The nesting will be cleared at exit
+// from GLib.
+//
+// This check occurs only in |HandleObserverPrepare|, not in
+// |HandlePrepare|.
+//
+// A third party is running a glib message loop. Since Chrome work is
+// registered with GLib at |G_PRIORITY_DEFAULT_IDLE|, a relatively low
+// priority, sources of default-or-higher priority will be Dispatch()ed
+// first. Since only one source is Dispatched per loop iteration,
+// |HandlePrepare| can get called several times in a row in the case that
+// there are any other events in the queue. A ScopedDoWorkItem is created
+// and destroyed to record this. That work item triggers nesting.
+//
+// 1.2: Other considerations
+// 1.2.1: Sleep occurs between Prepare() and Check(). If Chrome will pass a
+// nonzero poll time to GLib, the inner ScopedDoWorkItem is cleared and
+// BeforeWait() is called. In nesting case A, the nesting work item will
+// not be cleared. A nested loop will typically not block.
+//
+// Since Prepare() is called before Check() in all cases, the bulk of
+// nesting detection is done in Prepare().
+//
+// 2: Check.
+// 2.1: Detection of nested loops:
+// 2.1.1: In nesting case B, |ClearScopedWorkItem()| on exit. A third party is
+// running a glib message loop. It is possible that at any point the
+// nested message loop will quit. In this case, we don't want to leave a
+// nested DoWorkItem on the stack.
+//
+// 2.2: Other considerations
+// 2.2.1: A ScopedDoWorkItem may be created (if it was not already present) at
+// the entry to Check() to record a wakeup in the case that the pump
+// slept. It is important to note that this occurs both in
+// |HandleObserverCheck| and |HandleCheck| to ensure that at every point
+// as the pump enters the Dispatch phase it is awake. In the case it is
+// already awake, this is a very cheap operation.
+//
+// 3: Dispatch
+// 3.1 Detection of nested loops
+// 3.1.1: |state_->do_work_depth| is incremented on entry and decremented on
+// exit. This is used to detect nesting case B.
+//
+// 3.1.2: Nested loops can be quit at any point, and so ScopedDoWorkItems can't
+// be left on the stack for the same reasons as in 1.1.1/2.1.1.
+//
+// 3.2 Other considerations
+// 3.2.1: Since DoWork creates its own work items, ScopedDoWorkItems are not
+// used as this would trigger nesting in all cases.
+//
+// 4: Post GLib
+// 4.1: Detection of nested loops
+// 4.1.1: |state_->do_work_depth| is also increased during the DoWork in Run()
+// as nesting in that case [calling glib from third party code] needs to
+// clear all work items after return to avoid improper destruction order.
+//
+// 4.2: Other considerations:
+// 4.2.1: DoWork uses its own work item, so no ScopedDoWorkItems are active in
+// this case.
struct WorkSource : public GSource {
raw_ptr<MessagePumpGlib> pump;
@@ -130,8 +277,28 @@
}
// I wish these could be const, but g_source_new wants non-const.
-GSourceFuncs WorkSourceFuncs = {WorkSourcePrepare, WorkSourceCheck,
- WorkSourceDispatch, nullptr};
+GSourceFuncs g_work_source_funcs = {WorkSourcePrepare, WorkSourceCheck,
+ WorkSourceDispatch, nullptr};
+
+struct ObserverSource : public GSource {
+ raw_ptr<MessagePumpGlib> pump;
+};
+
+gboolean ObserverPrepare(GSource* gsource, gint* timeout_ms) {
+ auto* source = static_cast<ObserverSource*>(gsource);
+ source->pump->HandleObserverPrepare();
+ *timeout_ms = -1;
+ // We always want to poll.
+ return FALSE;
+}
+
+gboolean ObserverCheck(GSource* gsource) {
+ auto* source = static_cast<ObserverSource*>(gsource);
+ return source->pump->HandleObserverCheck();
+}
+
+GSourceFuncs g_observer_funcs = {ObserverPrepare, ObserverCheck, nullptr,
+ nullptr};
struct FdWatchSource : public GSource {
raw_ptr<MessagePumpGlib> pump;
@@ -171,6 +338,25 @@
// Used to flag that the current Run() invocation should return ASAP.
bool should_quit = false;
+ // Keeps track of the number of calls to DoWork() on the stack for the current
+ // Run() invocation. Used to detect reentrancy from DoWork in order to make
+ // decisions about tracking nested work.
+ int do_work_depth = 0;
+
+ // Value of g_main_depth() captured before the call to
+ // g_main_context_iteration() in Run(). nullopt if Run() is not calling
+ // g_main_context_iteration(). Used to track whether the pump has forced a
+ // nested state due to a native pump.
+ absl::optional<int> g_depth_on_iteration;
+
+ // Used to keep track of the native event work items processed by the message
+ // pump.
+ Delegate::ScopedDoWorkItem scoped_do_work_item;
+
+ // Used to force the pump into a nested state when a native runloop was
+ // dispatched from main.
+ Delegate::ScopedDoWorkItem native_loop_do_work_item;
+
// The information of the next task available at this run-level. Stored in
// RunState because different set of tasks can be accessible at various
// run-levels (e.g. non-nestable tasks).
@@ -199,8 +385,13 @@
wakeup_gpollfd_->fd = wakeup_pipe_read_;
wakeup_gpollfd_->events = G_IO_IN;
+ observer_source_ = std::unique_ptr<GSource, GSourceDeleter>(
+ g_source_new(&g_observer_funcs, sizeof(ObserverSource)));
+ static_cast<ObserverSource*>(observer_source_.get())->pump = this;
+ g_source_attach(observer_source_.get(), context_);
+
work_source_ = std::unique_ptr<GSource, GSourceDeleter>(
- g_source_new(&WorkSourceFuncs, sizeof(WorkSource)));
+ g_source_new(&g_work_source_funcs, sizeof(WorkSource)));
static_cast<WorkSource*>(work_source_.get())->pump = this;
g_source_add_poll(work_source_.get(), wakeup_gpollfd_.get());
g_source_set_priority(work_source_.get(), kPriorityWork);
@@ -327,19 +518,74 @@
return controller->Attach(this);
}
+void MessagePumpGlib::HandleObserverPrepare() {
+ // |state_| may be null during tests.
+ if (!state_) {
+ return;
+ }
+
+ if (state_->do_work_depth > 0) {
+ // Contingency 1.1.1 detailed above
+ SetScopedWorkItem();
+ ClearScopedWorkItem();
+ } else {
+ // Contingency 1.1.2 detailed above
+ NestIfRequired();
+ }
+
+ return;
+}
+
+bool MessagePumpGlib::HandleObserverCheck() {
+ // |state_| may be null in tests.
+ if (!state_) {
+ return FALSE;
+ }
+
+ // Make sure we record the fact that we're awake. Chrome won't get Check()ed
+ // if a higher priority work item returns TRUE from Check().
+ EnsureSetScopedWorkItem();
+ if (state_->do_work_depth > 0) {
+ // Contingency 2.1.1
+ ClearScopedWorkItem();
+ }
+
+ // The observer never needs to run anything.
+ return FALSE;
+}
+
// Return the timeout we want passed to poll.
int MessagePumpGlib::HandlePrepare() {
// |state_| may be null during tests.
if (!state_)
return 0;
- return GetTimeIntervalMilliseconds(state_->next_work_info.delayed_run_time);
+ const int next_wakeup_millis =
+ GetTimeIntervalMilliseconds(state_->next_work_info.delayed_run_time);
+ if (next_wakeup_millis != 0) {
+ // When this is called, it is not possible to know for sure if a
+ // ScopedWorkItem is on the stack, because HandleObserverCheck may have set
+ // it during an iteration of the pump where a high priority native work item
+ // executed.
+ EnsureClearedScopedWorkItem();
+ state_->delegate->BeforeWait();
+ }
+
+ return next_wakeup_millis;
}
bool MessagePumpGlib::HandleCheck() {
if (!state_) // state_ may be null during tests.
return false;
+ // Ensure pump is awake.
+ EnsureSetScopedWorkItem();
+
+ if (state_->do_work_depth > 0) {
+ // Contingency 2.1.1
+ ClearScopedWorkItem();
+ }
+
// We usually have a single message on the wakeup pipe, since we are only
// signaled when the queue went from empty to non-empty, but there can be
// two messages if a task posted a task, hence we read at most two bytes.
@@ -371,7 +617,18 @@
}
void MessagePumpGlib::HandleDispatch() {
+ // Contingency 3.2.1
+ EnsureClearedScopedWorkItem();
+
+ // Contingency 3.1.1
+ ++state_->do_work_depth;
state_->next_work_info = state_->delegate->DoWork();
+ --state_->do_work_depth;
+
+ if (state_ && state_->do_work_depth > 0) {
+ // Contingency 3.1.2
+ EnsureClearedScopedWorkItem();
+ }
}
void MessagePumpGlib::Run(Delegate* delegate) {
@@ -391,14 +648,28 @@
// callbacks. This is so we only quit our own loops, and we don't quit
// nested loops run by others. TODO(deanm): Is this what we want?
for (;;) {
+ // ScopedWorkItem to account for any native work until the runloop starts
+ // running chrome work.
+ SetScopedWorkItem();
+
// Don't block if we think we have more work to do.
bool block = !more_work_is_plausible;
+ OnEntryToGlib();
more_work_is_plausible = g_main_context_iteration(context_, block);
+ OnExitFromGlib();
+
if (state_->should_quit)
break;
+ // Contingency 4.2.1
+ EnsureClearedScopedWorkItem();
+
+ // Contingency 4.1.1
+ ++state_->do_work_depth;
state_->next_work_info = state_->delegate->DoWork();
+ --state_->do_work_depth;
+
more_work_is_plausible |= state_->next_work_info.is_immediate();
if (state_->should_quit)
break;
@@ -471,4 +742,128 @@
return state_->should_quit;
}
+void MessagePumpGlib::SetScopedWorkItem() {
+ // |state_| can be null during tests
+ if (!state_) {
+ return;
+ }
+ // If there exists a ScopedDoWorkItem in the current RunState, it cannot be
+ // overwritten.
+ CHECK(state_->scoped_do_work_item.IsNull());
+
+ // In the case that we're more than two work items deep, don't bother tracking
+ // individual native events anymore. Note that this won't cause out-of-order
+ // end work items, because the work item is cleared before entering the second
+ // DoWork().
+ if (state_->do_work_depth < 2) {
+ state_->scoped_do_work_item = state_->delegate->BeginWorkItem();
+ }
+}
+
+void MessagePumpGlib::ClearScopedWorkItem() {
+ // |state_| can be null during tests
+ if (!state_) {
+ return;
+ }
+
+ CHECK(!state_->scoped_do_work_item.IsNull());
+ // See identical check in SetScopedWorkItem
+ if (state_->do_work_depth < 2) {
+ state_->scoped_do_work_item = Delegate::ScopedDoWorkItem();
+ }
+}
+
+void MessagePumpGlib::EnsureSetScopedWorkItem() {
+ // |state_| can be null during tests
+ if (!state_) {
+ return;
+ }
+ if (state_->scoped_do_work_item.IsNull()) {
+ SetScopedWorkItem();
+ }
+}
+
+void MessagePumpGlib::EnsureClearedScopedWorkItem() {
+ // |state_| can be null during tests
+ if (!state_) {
+ return;
+ }
+ if (!state_->scoped_do_work_item.IsNull()) {
+ ClearScopedWorkItem();
+ }
+}
+
+void MessagePumpGlib::RegisterNested() {
+ // |state_| can be null during tests
+ if (!state_) {
+ return;
+ }
+ CHECK(state_->native_loop_do_work_item.IsNull());
+
+ // Transfer `scoped_do_work_item` to `native_do_work_item`, and so the
+ // ephemeral `scoped_do_work_item` will be coming in and out of existence on
+ // top of `native_do_work_item`, whose state hasn't been deleted.
+
+ if (state_->scoped_do_work_item.IsNull()) {
+ state_->native_loop_do_work_item = state_->delegate->BeginWorkItem();
+ } else {
+ // This clears state_->scoped_do_work_item.
+ state_->native_loop_do_work_item = std::move(state_->scoped_do_work_item);
+ }
+ SetScopedWorkItem();
+ ClearScopedWorkItem();
+}
+
+void MessagePumpGlib::UnregisterNested() {
+ // |state_| can be null during tests
+ if (!state_) {
+ return;
+ }
+ CHECK(!state_->native_loop_do_work_item.IsNull());
+
+ EnsureClearedScopedWorkItem();
+ // Nesting exits here.
+ state_->native_loop_do_work_item = Delegate::ScopedDoWorkItem();
+}
+
+void MessagePumpGlib::NestIfRequired() {
+ // |state_| can be null during tests
+ if (!state_) {
+ return;
+ }
+ if (state_->native_loop_do_work_item.IsNull() &&
+ state_->g_depth_on_iteration.has_value() &&
+ g_main_depth() != state_->g_depth_on_iteration.value()) {
+ RegisterNested();
+ }
+}
+
+void MessagePumpGlib::UnnestIfRequired() {
+ // |state_| can be null during tests
+ if (!state_) {
+ return;
+ }
+ if (!state_->native_loop_do_work_item.IsNull()) {
+ UnregisterNested();
+ }
+}
+
+void MessagePumpGlib::OnEntryToGlib() {
+ // |state_| can be null during tests
+ if (!state_) {
+ return;
+ }
+ CHECK(!state_->g_depth_on_iteration.has_value());
+ state_->g_depth_on_iteration.emplace(g_main_depth());
+}
+
+void MessagePumpGlib::OnExitFromGlib() {
+ // |state_| can be null during tests
+ if (!state_) {
+ return;
+ }
+ state_->g_depth_on_iteration.reset();
+ UnnestIfRequired();
+}
+
} // namespace base
diff --git a/base/message_loop/message_pump_glib.h b/base/message_loop/message_pump_glib.h
index 1b0fcce..647f3cc 100644
--- a/base/message_loop/message_pump_glib.h
+++ b/base/message_loop/message_pump_glib.h
@@ -93,6 +93,12 @@
bool HandleCheck();
void HandleDispatch();
+ // Very similar to the above, with the key difference that these functions are
+ // only used to track work items and never indicate work is available, and
+ // poll indefinitely.
+ void HandleObserverPrepare();
+ bool HandleObserverCheck();
+
// Overridden from MessagePump:
void Run(Delegate* delegate) override;
void Quit() override;
@@ -131,6 +137,38 @@
raw_ptr<RunState> state_;
+ // Starts tracking a new work item and stores a `ScopedDoWorkItem` in
+ // `state_`.
+ void SetScopedWorkItem();
+ // Gets rid of the current scoped work item.
+ void ClearScopedWorkItem();
+ // Ensures there's a ScopedDoWorkItem at the current run-level. This can be
+ // useful for contexts where the caller can't tell whether they just woke up
+ // or are continuing from native work.
+ void EnsureSetScopedWorkItem();
+ // Ensures there's no ScopedDoWorkItem at the current run-level. This can be
+ // useful in contexts where the caller knows that a sleep is imminent but
+ // doesn't know if the current context captures ongoing work (back from
+ // native).
+ void EnsureClearedScopedWorkItem();
+
+ // Called before entrance to g_main_context_iteration to record context
+ // related to nesting depth to track native nested loops which would otherwise
+ // be invisible.
+ void OnEntryToGlib();
+ // Cleans up state set in OnEntryToGlib.
+ void OnExitFromGlib();
+ // Forces the pump into a nested state by creating two work items back to
+ // back.
+ void RegisterNested();
+ // Removes all of the pump's ScopedDoWorkItems to remove the state of nesting
+ // which was forced onto the pump.
+ void UnregisterNested();
+ // Nest if pump is not already marked as nested.
+ void NestIfRequired();
+ // Remove the nesting if the pump is nested.
+ void UnnestIfRequired();
+
std::unique_ptr<GMainContext, GMainContextDeleter> owned_context_;
// This is a GLib structure that we can add event sources to. On the main
// thread, we use the default GLib context, which is the one to which all GTK
@@ -141,6 +179,10 @@
// the message pump is destroyed.
std::unique_ptr<GSource, GSourceDeleter> work_source_;
+ // The observer source. It is shared by all calls to Run and destroyed when
+ // the message pump is destroyed.
+ std::unique_ptr<GSource, GSourceDeleter> observer_source_;
+
// We use a wakeup pipe to make sure we'll get out of the glib polling phase
// when another thread has scheduled us to do some work. There is a glib
// mechanism g_main_context_wakeup, but this won't guarantee that our event's
diff --git a/base/message_loop/message_pump_glib_unittest.cc b/base/message_loop/message_pump_glib_unittest.cc
index b202b40..7b5406e 100644
--- a/base/message_loop/message_pump_glib_unittest.cc
+++ b/base/message_loop/message_pump_glib_unittest.cc
@@ -6,6 +6,7 @@
#include <glib.h>
#include <math.h>
+#include "build/build_config.h"
#include <algorithm>
#include <vector>
@@ -27,6 +28,7 @@
#include "base/task/single_thread_task_executor.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/task_environment.h"
+#include "base/test/trace_event_analyzer.h"
#include "base/threading/thread.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -540,6 +542,75 @@
run_loop.Run();
}
+namespace {
+
+class NestedEventAnalyzer {
+ public:
+ NestedEventAnalyzer() {
+ trace_analyzer::Start(TRACE_DISABLED_BY_DEFAULT("base"));
+ }
+
+ size_t CountEvents() {
+ std::unique_ptr<trace_analyzer::TraceAnalyzer> analyzer =
+ trace_analyzer::Stop();
+ trace_analyzer::TraceEventVector events;
+ return analyzer->FindEvents(trace_analyzer::Query::EventName() ==
+ trace_analyzer::Query::String("Nested"),
+ &events);
+ }
+};
+
+} // namespace
+
+// TODO(crbug.com/1434860): Re-enable this test
+#if BUILDFLAG(IS_LINUX) && defined(MEMORY_SANITIZER)
+#define MAYBE_TestNativeNestedLoopWithoutDoWork \
+ DISABLED_TestNativeNestedLoopWithoutDoWork
+#else
+#define MAYBE_TestNativeNestedLoopWithoutDoWork \
+ TestNativeNestedLoopWithoutDoWork
+#endif
+TEST_F(MessagePumpGLibTest, MAYBE_TestNativeNestedLoopWithoutDoWork) {
+ // Tests that nesting is triggered correctly if a message loop is run
+ // from a native event (gtk event) outside of a work item (not in a posted
+ // task).
+
+ RunLoop run_loop;
+ NestedEventAnalyzer analyzer;
+
+ base::CurrentThread::Get()->EnableMessagePumpTimeKeeperMetrics(
+ "GlibMainLoopTest");
+
+ scoped_refptr<GLibLoopRunner> runner = base::MakeRefCounted<GLibLoopRunner>();
+ injector()->AddEvent(
+ 0,
+ BindOnce(
+ [](EventInjector* injector, scoped_refptr<GLibLoopRunner> runner,
+ OnceClosure done) {
+ CurrentThread::ScopedAllowApplicationTasksInNativeNestedLoop allow;
+ runner->RunLoop();
+ },
+ Unretained(injector()), runner, run_loop.QuitClosure()));
+
+ injector()->AddDummyEvent(0);
+ injector()->AddDummyEvent(0);
+ injector()->AddDummyEvent(0);
+
+ SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
+ FROM_HERE, BindOnce(&GLibLoopRunner::Quit, runner), Milliseconds(40));
+
+ SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
+ FROM_HERE, run_loop.QuitClosure(), Milliseconds(40));
+
+ run_loop.Run();
+
+ // It would be expected that there be one single event, but it seems like this
+ // is counting the Begin/End of the Nested trace event. Each of the two events
+ // found are of duration 0 with distinct timestamps. It has also been
+ // confirmed that nesting occurs only once.
+ CHECK_EQ(analyzer.CountEvents(), 2ul);
+}
+
// Tests for WatchFileDescriptor API
class MessagePumpGLibFdWatchTest : public testing::Test {
protected:
diff --git a/base/message_loop/message_pump_libevent.h b/base/message_loop/message_pump_libevent.h
index 4017d93..0389de9 100644
--- a/base/message_loop/message_pump_libevent.h
+++ b/base/message_loop/message_pump_libevent.h
@@ -162,7 +162,10 @@
// State used only with libevent
std::unique_ptr<event> event_;
- raw_ptr<MessagePumpLibevent> libevent_pump_ = nullptr;
+
+ // Tests (e.g. FdWatchControllerPosixTest) deliberately make this dangle.
+ raw_ptr<MessagePumpLibevent, DisableDanglingPtrDetection> libevent_pump_ =
+ nullptr;
// State used only with epoll
WeakPtr<MessagePumpEpoll> epoll_pump_;
diff --git a/base/message_loop/message_pump_unittest.cc b/base/message_loop/message_pump_unittest.cc
index 9e19703..b11167b 100644
--- a/base/message_loop/message_pump_unittest.cc
+++ b/base/message_loop/message_pump_unittest.cc
@@ -139,6 +139,13 @@
MessagePumpTest() : message_pump_(MessagePump::Create(GetParam())) {}
protected:
+#if defined(USE_GLIB)
+ // Because of a GLIB implementation quirk, the pump doesn't do the same things
+ // between each DoWork. In this case, it won't set/clear a ScopedDoWorkItem
+ // because we run a chrome work item in the runloop outside of GLIB's control,
+ // so we oscillate between setting and not setting PreDoWorkExpectations.
+ std::map<MessagePump::Delegate*, int> do_work_counts;
+#endif
void AddPreDoWorkExpectations(
testing::StrictMock<MockMessagePumpDelegate>& delegate) {
#if BUILDFLAG(IS_WIN)
@@ -155,14 +162,35 @@
EXPECT_CALL(delegate, MockOnEndWorkItem).Times(AtMost(1));
}
#endif // BUILDFLAG(IS_WIN)
+#if defined(USE_GLIB)
+ do_work_counts.try_emplace(&delegate, 0);
+ if (GetParam() == MessagePumpType::UI) {
+ if (++do_work_counts[&delegate] % 2) {
+ // The GLib MessagePump will do native work before chrome work on
+ // startup.
+ EXPECT_CALL(delegate, MockOnBeginWorkItem);
+ EXPECT_CALL(delegate, MockOnEndWorkItem);
+ }
+ }
+#endif // defined(USE_GLIB)
}
void AddPostDoWorkExpectations(
testing::StrictMock<MockMessagePumpDelegate>& delegate) {
+#if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_NACL)
// MessagePumpLibEvent checks for native notifications once after processing
// a DoWork() but only instantiates a ScopedDoWorkItem that triggers
// MessagePumpLibevent::OnLibeventNotification() which this test does not
// so there are no post-work expectations at the moment.
+#endif
+#if defined(USE_GLIB)
+ if (GetParam() == MessagePumpType::UI) {
+ // The GLib MessagePump can create and destroy work items between DoWorks
+ // depending on internal state.
+ EXPECT_CALL(delegate, MockOnBeginWorkItem).Times(AtMost(1));
+ EXPECT_CALL(delegate, MockOnEndWorkItem).Times(AtMost(1));
+ }
+#endif // defined(USE_GLIB)
}
std::unique_ptr<MessagePump> message_pump_;
@@ -182,6 +210,16 @@
message_pump_->Quit();
return MessagePump::Delegate::NextWorkInfo{TimeTicks::Max()};
}));
+
+ // MessagePumpGlib uses a work item between a HandleDispatch() call and
+ // passing control back to the chrome loop, which handles the Quit() despite
+ // us not necessarily doing any native work during that time.
+#if defined(USE_GLIB)
+ if (GetParam() == MessagePumpType::UI) {
+ AddPostDoWorkExpectations(delegate);
+ }
+#endif
+
EXPECT_CALL(delegate, DoIdleWork()).Times(0);
message_pump_->ScheduleWork();
@@ -215,10 +253,14 @@
return MessagePump::Delegate::NextWorkInfo{TimeTicks::Max()};
}));
- // PostDoWorkExpectations for the first DoWork.
+ // The `nested_delegate` will quit first.
+ AddPostDoWorkExpectations(nested_delegate);
+
+ // Return a delayed task with |yield_to_native| set, and exit.
AddPostDoWorkExpectations(delegate);
AddPreDoWorkExpectations(delegate);
+
EXPECT_CALL(delegate, DoWork).WillOnce(Invoke([this] {
message_pump_->Quit();
return MessagePump::Delegate::NextWorkInfo{TimeTicks::Max()};
@@ -245,8 +287,8 @@
}));
AddPostDoWorkExpectations(delegate);
- // Return a delayed task with |yield_to_native| set, and exit.
AddPreDoWorkExpectations(delegate);
+ // Return a delayed task with |yield_to_native| set, and exit.
EXPECT_CALL(delegate, DoWork).WillOnce(Invoke([this] {
message_pump_->Quit();
auto now = TimeTicks::Now();
@@ -357,9 +399,13 @@
message_pump_->Quit();
return MessagePump::Delegate::NextWorkInfo{TimeTicks::Max()};
}));
+
+ AddPostDoWorkExpectations(delegate);
+
#if BUILDFLAG(IS_IOS)
EXPECT_CALL(delegate, DoIdleWork).Times(AnyNumber());
#endif
+
message_pump_->Run(&delegate);
}
@@ -383,6 +429,11 @@
return MessagePump::Delegate::NextWorkInfo{TimeTicks::Max()};
}));
+ // We quit `nested_delegate` before `delegate`
+ AddPostDoWorkExpectations(nested_delegate);
+
+ AddPostDoWorkExpectations(delegate);
+
#if BUILDFLAG(IS_IOS)
EXPECT_CALL(nested_delegate, DoIdleWork).Times(AnyNumber());
EXPECT_CALL(delegate, DoIdleWork).Times(AnyNumber());
diff --git a/base/profiler/stack_sampler.cc b/base/profiler/stack_sampler.cc
index 65d3eb9..b1659b1 100644
--- a/base/profiler/stack_sampler.cc
+++ b/base/profiler/stack_sampler.cc
@@ -150,8 +150,15 @@
if ((++stack_size_histogram_sampling_counter_ %
kUMAHistogramDownsampleAmount) == 0) {
// Record the size of the stack to tune kLargeStackSize.
- UmaHistogramMemoryKB("Memory.StackSamplingProfiler.StackSampleSize",
- saturated_cast<int>(stack_size / kBytesPerKilobyte));
+ // UmaHistogramMemoryKB has a min of 1000, which isn't useful for our
+ // purposes, so call UmaHistogramCustomCounts directly.
+ // Min is 4KB, since that's the normal pagesize and setting kLargeStackSize
+ // smaller than that would be pointless. Max is 8MB since that's the
+ // current ChromeOS stack size; we shouldn't be able to get a number
+ // larger than that.
+ UmaHistogramCustomCounts(
+ "Memory.StackSamplingProfiler.StackSampleSize2",
+ saturated_cast<int>(stack_size / kBytesPerKilobyte), 4, 8 * 1024, 50);
}
// We expect to very rarely see stacks larger than kLargeStackSize. If we see
diff --git a/base/profiler/stack_sampler.h b/base/profiler/stack_sampler.h
index 9622453..d6154ce 100644
--- a/base/profiler/stack_sampler.h
+++ b/base/profiler/stack_sampler.h
@@ -94,7 +94,7 @@
StackSamplerTestDelegate* test_delegate = nullptr);
#if BUILDFLAG(IS_CHROMEOS)
- // How often to record the "Memory.StackSamplingProfiler.StackSampleSize" UMA
+ // How often to record the "Memory.StackSamplingProfiler.StackSampleSize2" UMA
// histogram. Specifically, only 1 in kUMAHistogramDownsampleAmount calls to
// RecordStackFrames will add a sample to the histogram. RecordStackFrames is
// called many times a second. We don't need multiple samples per second to
@@ -130,7 +130,7 @@
const raw_ptr<StackSamplerTestDelegate> test_delegate_;
#if BUILDFLAG(IS_CHROMEOS)
- // Counter for "Memory.StackSamplingProfiler.StackSampleSize" UMA histogram.
+ // Counter for "Memory.StackSamplingProfiler.StackSampleSize2" UMA histogram.
// See comments above kUMAHistogramDownsampleAmount. Unsigned so that overflow
// isn't undefined behavior.
uint32_t stack_size_histogram_sampling_counter_ = 0;
diff --git a/base/profiler/stack_sampler_unittest.cc b/base/profiler/stack_sampler_unittest.cc
index 41d593f..c5b9b5f 100644
--- a/base/profiler/stack_sampler_unittest.cc
+++ b/base/profiler/stack_sampler_unittest.cc
@@ -319,16 +319,16 @@
PlatformThread::CurrentId());
// Should have no new samples in the
- // Memory.StackSamplingProfiler.StackSampleSize histogram.
+ // Memory.StackSamplingProfiler.StackSampleSize2 histogram.
histogram_tester.ExpectUniqueSample(
- "Memory.StackSamplingProfiler.StackSampleSize", kExpectedSizeKB, 0);
+ "Memory.StackSamplingProfiler.StackSampleSize2", kExpectedSizeKB, 0);
}
stack_sampler->RecordStackFrames(stack_buffer.get(), &profile_builder,
PlatformThread::CurrentId());
histogram_tester.ExpectUniqueSample(
- "Memory.StackSamplingProfiler.StackSampleSize", kExpectedSizeKB, 1);
+ "Memory.StackSamplingProfiler.StackSampleSize2", kExpectedSizeKB, 1);
}
#endif // #if BUILDFLAG(IS_CHROMEOS)
diff --git a/base/test/android/javatests/src/org/chromium/base/test/BaseJUnit4ClassRunner.java b/base/test/android/javatests/src/org/chromium/base/test/BaseJUnit4ClassRunner.java
index 7f4d192..208b7b6 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/BaseJUnit4ClassRunner.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/BaseJUnit4ClassRunner.java
@@ -237,7 +237,7 @@
@CallSuper
protected List<TestRule> getDefaultTestRules() {
return Arrays.asList(new BaseJUnit4TestRule(), new MockitoErrorHandler(),
- new UnitTestLifetimeAssertRule());
+ new UnitTestLifetimeAssertRule(), new ResettersForTestingTestRule());
}
/**
diff --git a/base/test/android/javatests/src/org/chromium/base/test/ResettersForTestingTestRule.java b/base/test/android/javatests/src/org/chromium/base/test/ResettersForTestingTestRule.java
new file mode 100644
index 0000000..7797306
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/ResettersForTestingTestRule.java
@@ -0,0 +1,32 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import org.chromium.base.ResettersForTesting;
+
+/**
+ * Ensures that all resetters are cleaned up after a test. The resetters are registered through
+ * {@link ResettersForTesting#register(Runnable)} and are typically used whenever we have code that
+ * has <code>public static void setFooForTesting(...)</code> constructs.
+ */
+class ResettersForTestingTestRule implements TestRule {
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ try {
+ base.evaluate();
+ } finally {
+ ResettersForTesting.executeResetters();
+ }
+ }
+ };
+ }
+}
diff --git a/base/test/android/junit/src/org/chromium/base/test/BaseRobolectricTestRunner.java b/base/test/android/junit/src/org/chromium/base/test/BaseRobolectricTestRunner.java
index a0d0608..defe3ae 100644
--- a/base/test/android/junit/src/org/chromium/base/test/BaseRobolectricTestRunner.java
+++ b/base/test/android/junit/src/org/chromium/base/test/BaseRobolectricTestRunner.java
@@ -18,6 +18,7 @@
import org.chromium.base.Flag;
import org.chromium.base.LifetimeAssert;
import org.chromium.base.PathUtils;
+import org.chromium.base.ResettersForTesting;
import org.chromium.base.ThreadUtils;
import org.chromium.base.library_loader.LibraryLoader;
import org.chromium.base.library_loader.LibraryProcessType;
@@ -68,6 +69,7 @@
} finally {
CommandLineFlags.tearDownMethod();
CommandLineFlags.tearDownClass();
+ ResettersForTesting.executeResetters();
ApplicationStatus.destroyForJUnitTests();
ContextUtils.clearApplicationContextForTests();
PathUtils.resetForTesting();
diff --git a/components/policy/core/common/async_policy_loader.cc b/components/policy/core/common/async_policy_loader.cc
index f22b9ad..4720c11 100644
--- a/components/policy/core/common/async_policy_loader.cc
+++ b/components/policy/core/common/async_policy_loader.cc
@@ -67,8 +67,7 @@
// `management_service_` must be called on the main thread.
// base::Unretained is okay here since `management_service_` is an instance of
// PlatformManagementService which is a singleton that outlives this class.
- if (!platform_management_trustworthiness_.has_value() &&
- management_service_) {
+ if (NeedManagementBitBeforeLoad()) {
DCHECK_EQ(management_service_, PlatformManagementService::GetInstance());
ui_thread_task_runner_->PostTaskAndReplyWithResult(
FROM_HERE,
@@ -125,6 +124,11 @@
Reload(force);
}
+bool AsyncPolicyLoader::NeedManagementBitBeforeLoad() {
+ return !platform_management_trustworthiness_.has_value() &&
+ management_service_;
+}
+
PolicyBundle AsyncPolicyLoader::InitialLoad(
const scoped_refptr<SchemaMap>& schema_map) {
// This is the first load, early during startup. Use this to record the
diff --git a/components/policy/core/common/async_policy_loader.h b/components/policy/core/common/async_policy_loader.h
index 7ee411f..c77212b 100644
--- a/components/policy/core/common/async_policy_loader.h
+++ b/components/policy/core/common/async_policy_loader.h
@@ -51,7 +51,9 @@
virtual ~AsyncPolicyLoader();
// Gets a SequencedTaskRunner backed by the background thread.
- base::SequencedTaskRunner* task_runner() const { return task_runner_.get(); }
+ const scoped_refptr<base::SequencedTaskRunner>& task_runner() const {
+ return task_runner_;
+ }
// Returns the currently configured policies. Load() is always invoked on
// the background thread, except for the initial Load() at startup which is
@@ -81,7 +83,7 @@
// When |periodic_updates_| is true a reload is posted periodically, if it
// hasn't been triggered recently. This makes sure the policies are reloaded
// if the update events aren't triggered.
- void Reload(bool force);
+ virtual void Reload(bool force);
// Returns `true` and only if the platform is not managed by a trusted source.
bool ShouldFilterSensitivePolicies();
@@ -97,6 +99,11 @@
reload_interval_ = reload_interval;
}
+ protected:
+ // Return true if we need to asynchronously get
+ //`platform_management_trustworthiness_` bit before reloading policies.
+ bool NeedManagementBitBeforeLoad();
+
private:
// Allow AsyncPolicyProvider to call Init().
friend class AsyncPolicyProvider;
diff --git a/components/policy/core/common/policy_logger.cc b/components/policy/core/common/policy_logger.cc
index 416c7a3..1b45e8b 100644
--- a/components/policy/core/common/policy_logger.cc
+++ b/components/policy/core/common/policy_logger.cc
@@ -179,7 +179,7 @@
DCHECK_CALLED_ON_VALID_SEQUENCE(logs_list_sequence_checker_);
logs_.emplace_back(std::move(new_log));
- if (!is_log_deletion_scheduled_) {
+ if (!is_log_deletion_scheduled_ && is_log_deletion_enabled_) {
ScheduleOldLogsDeletion();
}
}
@@ -225,6 +225,10 @@
#endif // BUILDFLAG(IS_ANDROID)
}
+void PolicyLogger::EnableLogDeletion() {
+ is_log_deletion_enabled_ = true;
+}
+
size_t PolicyLogger::GetPolicyLogsSizeForTesting() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(logs_list_sequence_checker_);
return logs_.size();
@@ -234,6 +238,7 @@
DCHECK_CALLED_ON_VALID_SEQUENCE(logs_list_sequence_checker_);
logs_.erase(logs_.begin(), logs_.end());
is_log_deletion_scheduled_ = false;
+ is_log_deletion_enabled_ = false;
}
} // namespace policy
diff --git a/components/policy/core/common/policy_logger.h b/components/policy/core/common/policy_logger.h
index 9171b9a..2886fda 100644
--- a/components/policy/core/common/policy_logger.h
+++ b/components/policy/core/common/policy_logger.h
@@ -161,6 +161,9 @@
// Checks if browser is running on Android.
bool IsPolicyLoggingEnabled() const;
+ // Sets `is_log_deletion_enabled_` to allow scheduling old log deletion.
+ void EnableLogDeletion();
+
// Returns the logs size for testing purposes.
size_t GetPolicyLogsSizeForTesting() const;
@@ -181,6 +184,13 @@
// flag.
void ScheduleOldLogsDeletion();
+ // Log deletion scheduling fails in unit tests when there is no task
+ // environment (See crbug.com/1434241). To avoid having a task environment in
+ // every existing and new unit test that calls a function with logs, this flag
+ // is disabled in unit tests, and enabled everywhere else early in the policy
+ // stack initialization from `BrowserPolicyConnector::Init`.
+ bool is_log_deletion_enabled_{false};
+
bool is_log_deletion_scheduled_{false};
std::vector<Log> logs_ GUARDED_BY_CONTEXT(logs_list_sequence_checker_);
diff --git a/components/policy/core/common/policy_logger_unittest.cc b/components/policy/core/common/policy_logger_unittest.cc
index ff1c75f..a6ffbac 100644
--- a/components/policy/core/common/policy_logger_unittest.cc
+++ b/components/policy/core/common/policy_logger_unittest.cc
@@ -72,6 +72,7 @@
// Checks that the deletion of expired logs works as expected.
TEST_F(PolicyLoggerTest, DeleteOldLogs) {
PolicyLogger* policy_logger = policy::PolicyLogger::GetInstance();
+ policy_logger->EnableLogDeletion();
size_t logs_size_before_adding = policy_logger->GetPolicyLogsSizeForTesting();
AddLogs("First log at t=0.", policy_logger);
diff --git a/components/policy/core/common/scoped_critical_policy_section.cc b/components/policy/core/common/scoped_critical_policy_section.cc
new file mode 100644
index 0000000..99a5af1
--- /dev/null
+++ b/components/policy/core/common/scoped_critical_policy_section.cc
@@ -0,0 +1,119 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/scoped_critical_policy_section.h"
+
+#include <userenv.h>
+#include <windows.h>
+
+#include "base/functional/bind.h"
+#include "base/logging.h"
+#include "base/task/bind_post_task.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
+#include "base/time/time.h"
+#include "components/policy/core/common/policy_types.h"
+
+namespace policy {
+
+namespace {
+
+void EnterSection(
+ ScopedCriticalPolicySection::OnSectionEnteredCallback callback) {
+ ScopedCriticalPolicySection::Handles handles;
+ // We need both user and machine handles. Based on MSFT doc, user handle must
+ // be acquired first to prevent dead lock.
+ // https://learn.microsoft.com/en-us/windows/win32/api/userenv/nf-userenv-entercriticalpolicysection
+ //
+ // If we failed to aquire lock or the API is timeout, we will read the policy
+ // regardless, as we used to have.
+ handles.user_handle = ::EnterCriticalPolicySection(false);
+ if (!handles.user_handle) {
+ PLOG(WARNING) << "Failed to enter user critical policy section.";
+ }
+ handles.machine_handle = ::EnterCriticalPolicySection(true);
+ if (!handles.machine_handle) {
+ PLOG(WARNING) << "Failed to enter machine critical policy section.";
+ }
+ std::move(callback).Run(handles);
+}
+
+} // namespace
+
+// static
+void ScopedCriticalPolicySection::Enter(
+ base::OnceClosure callback,
+ const scoped_refptr<base::SequencedTaskRunner>& task_runner) {
+ DCHECK(task_runner->RunsTasksInCurrentSequence());
+ EnterWithEnterSectionCallback(std::move(callback), EnterSectionCallback(),
+ task_runner);
+}
+
+// static
+void ScopedCriticalPolicySection::EnterWithEnterSectionCallback(
+ base::OnceClosure callback,
+ EnterSectionCallback enter_section_callback,
+ const scoped_refptr<base::SequencedTaskRunner>& task_runner) {
+ auto scoped_section =
+ std::make_unique<ScopedCriticalPolicySection>(task_runner);
+
+ scoped_section->enter_section_callback_ =
+ enter_section_callback ? std::move(enter_section_callback)
+ : base::BindOnce(&EnterSection);
+ scoped_section->Init(base::BindOnce(
+ [](std::unique_ptr<ScopedCriticalPolicySection> scoped_section,
+ base::OnceClosure callback) { std::move(callback).Run(); },
+ std::move(scoped_section), std::move(callback)));
+}
+
+ScopedCriticalPolicySection::ScopedCriticalPolicySection(
+ const scoped_refptr<base::SequencedTaskRunner>& task_runner)
+ : task_runner_(task_runner) {}
+
+ScopedCriticalPolicySection::~ScopedCriticalPolicySection() {
+ if (machine_handle_) {
+ ::LeaveCriticalPolicySection(machine_handle_);
+ }
+
+ if (user_handle_) {
+ ::LeaveCriticalPolicySection(user_handle_);
+ }
+}
+
+void ScopedCriticalPolicySection::Init(base::OnceClosure callback) {
+ DCHECK(!callback_);
+ callback_ = std::move(callback);
+
+ if (enter_section_callback_) {
+ // Call ::EnterCriticalPolicySection in a different thread as the API could
+ // take minutes to return.
+ // Using `PostTask` instead of `PostTaskAndReplyWithResult` allows unit test
+ // mimic blocking function easily.
+ auto on_section_entered = base::BindPostTask(
+ task_runner_,
+ base::BindOnce(&ScopedCriticalPolicySection::OnSectionEntered,
+ weak_factory_.GetWeakPtr()));
+ base::ThreadPool::PostTask(
+ FROM_HERE, {base::MayBlock()},
+ base::BindOnce(std::move(enter_section_callback_),
+ std::move(on_section_entered)));
+ }
+
+ // Based on UMA data, 15 seconds timeout is enough for 99.9% cases.
+ task_runner_->PostDelayedTask(
+ FROM_HERE,
+ base::BindOnce(&ScopedCriticalPolicySection::OnSectionEntered,
+ weak_factory_.GetWeakPtr(), Handles()),
+ base::Seconds(15));
+}
+
+void ScopedCriticalPolicySection::OnSectionEntered(Handles handles) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ machine_handle_ = handles.machine_handle;
+ user_handle_ = handles.user_handle;
+ std::move(callback_).Run();
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/scoped_critical_policy_section.h b/components/policy/core/common/scoped_critical_policy_section.h
new file mode 100644
index 0000000..cc3d246
--- /dev/null
+++ b/components/policy/core/common/scoped_critical_policy_section.h
@@ -0,0 +1,72 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_SCOPED_CRITICAL_POLICY_SECTION_H_
+#define COMPONENTS_POLICY_CORE_COMMON_SCOPED_CRITICAL_POLICY_SECTION_H_
+
+#include "base/functional/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "components/policy/policy_export.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace policy {
+
+// Scoped class for ::EnterCriticalPolicySection API. The class make sure we try
+// to acquire the section before reading the policy values. It will leave the
+// section in the end and self destory.
+class POLICY_EXPORT ScopedCriticalPolicySection {
+ public:
+ struct Handles {
+ HANDLE machine_handle;
+ HANDLE user_handle;
+ };
+
+ using OnSectionEnteredCallback = base::OnceCallback<void(Handles)>;
+ using EnterSectionCallback =
+ base::OnceCallback<void(OnSectionEnteredCallback)>;
+
+ // Create and own `ScopedCriticalPolicySection` instance. And destory itself
+ // after `callback` being invoked.
+ // This must be called on the background thread. When loading policy on the
+ // main thread, we can't wait for the API as everything must be returned
+ // synchronously.
+ static void Enter(
+ base::OnceClosure callback,
+ const scoped_refptr<base::SequencedTaskRunner>& task_runner);
+
+ // Same but with custome function to enter critical policy section`. Only
+ // used for testing purposes.
+ static void EnterWithEnterSectionCallback(
+ base::OnceClosure callback,
+ EnterSectionCallback enter_section_callback,
+ const scoped_refptr<base::SequencedTaskRunner>& task_runner);
+
+ ScopedCriticalPolicySection(
+ const scoped_refptr<base::SequencedTaskRunner>& task_runner);
+ ScopedCriticalPolicySection(const ScopedCriticalPolicySection&) = delete;
+ ScopedCriticalPolicySection& operator=(const ScopedCriticalPolicySection&) =
+ delete;
+ ~ScopedCriticalPolicySection();
+
+ void Init(base::OnceClosure callback);
+
+ private:
+ void OnSectionEntered(Handles handles);
+
+ HANDLE machine_handle_ = nullptr;
+ HANDLE user_handle_ = nullptr;
+ const scoped_refptr<base::SequencedTaskRunner> task_runner_;
+ base::OnceClosure callback_;
+
+ EnterSectionCallback enter_section_callback_;
+
+ base::WeakPtrFactory<ScopedCriticalPolicySection> weak_factory_{this};
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_SCOPED_CRITICAL_POLICY_SECTION_H_
diff --git a/components/policy/core/common/scoped_critical_policy_section_unittest.cc b/components/policy/core/common/scoped_critical_policy_section_unittest.cc
new file mode 100644
index 0000000..07d1718
--- /dev/null
+++ b/components/policy/core/common/scoped_critical_policy_section_unittest.cc
@@ -0,0 +1,72 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/scoped_critical_policy_section.h"
+
+#include "base/functional/bind.h"
+#include "base/functional/callback_helpers.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/task/thread_pool.h"
+#include "base/test/mock_callback.h"
+#include "base/test/task_environment.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/time/time.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+class ScopedCriticalPolicySectionTest : public ::testing::Test {
+ public:
+ ScopedCriticalPolicySectionTest()
+ : task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME),
+ task_runner_(base::ThreadPool::CreateSequencedTaskRunner({})) {}
+
+ ~ScopedCriticalPolicySectionTest() override = default;
+ base::SequencedTaskRunner* task_runner() { return task_runner_.get(); }
+
+ base::test::TaskEnvironment* task_environment() { return &task_environment_; }
+
+ private:
+ base::test::TaskEnvironment task_environment_;
+ scoped_refptr<base::SequencedTaskRunner> task_runner_;
+};
+
+TEST_F(ScopedCriticalPolicySectionTest, Normal) {
+ ::testing::StrictMock<base::MockOnceClosure> mock_callback;
+ EXPECT_CALL(mock_callback, Run()).Times(1);
+ ScopedCriticalPolicySection::EnterWithEnterSectionCallback(
+ mock_callback.Get(),
+ base::BindOnce(
+ [](ScopedCriticalPolicySection::OnSectionEnteredCallback callback) {
+ std::move(callback).Run(ScopedCriticalPolicySection::Handles());
+ }),
+ task_runner());
+ task_environment()->RunUntilIdle();
+}
+
+TEST_F(ScopedCriticalPolicySectionTest, Timeout) {
+ auto fake_enter_section_function = base::BindOnce(
+ [](ScopedCriticalPolicySection::OnSectionEnteredCallback callback) {
+ base::ThreadPool::PostDelayedTask(
+ FROM_HERE,
+ base::BindOnce(std::move(callback),
+ ScopedCriticalPolicySection::Handles()),
+ base::Seconds(30));
+ });
+
+ ::testing::StrictMock<base::MockOnceClosure> mock_callback;
+ EXPECT_CALL(mock_callback, Run()).Times(0);
+ ScopedCriticalPolicySection::EnterWithEnterSectionCallback(
+ mock_callback.Get(), std::move(fake_enter_section_function),
+ task_runner());
+ task_environment()->RunUntilIdle();
+ ::testing::Mock::VerifyAndClearExpectations(&mock_callback);
+
+ EXPECT_CALL(mock_callback, Run()).Times(1);
+ task_environment()->FastForwardBy(base::Seconds(15));
+}
+
+} // namespace policy