Commit f8755f0f authored by Sami Kyostila's avatar Sami Kyostila Committed by Commit Bot

content/public/test: Fix raciness in NestedMessagePumpAndroid

This patch fixes raciness and thread safety issues in
NestedMessagePumpAndroid by reimplementing it on top of the regular
MessagePumpAndroid. This allows us to remove the complicated 100ms
timeslicing logic between Java and C++ tasks and just share the
underlying Looper directly.

Bug: 872269
Change-Id: Id5f5e42f2dac6f065769d69743d78b1fb04bb916
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2207171Reviewed-by: default avatarMichael Thiessen <mthiesse@chromium.org>
Reviewed-by: default avatarAndrew Grieve <agrieve@chromium.org>
Commit-Queue: Andrew Grieve <agrieve@chromium.org>
Auto-Submit: Sami Kyöstilä <skyostil@chromium.org>
Cr-Commit-Position: refs/heads/master@{#792326}
parent fbbc0832
...@@ -58,6 +58,7 @@ class BASE_EXPORT MessagePumpForUI : public MessagePump { ...@@ -58,6 +58,7 @@ class BASE_EXPORT MessagePumpForUI : public MessagePump {
protected: protected:
void SetDelegate(Delegate* delegate) { delegate_ = delegate; } void SetDelegate(Delegate* delegate) { delegate_ = delegate; }
void ResetShouldQuit() { quit_ = false; }
virtual bool IsTestImplementation() const; virtual bool IsTestImplementation() const;
private: private:
......
...@@ -57,24 +57,18 @@ public class NestedSystemMessageHandler { ...@@ -57,24 +57,18 @@ public class NestedSystemMessageHandler {
/** /**
* Runs a single nested task on the provided MessageQueue * Runs a single nested task on the provided MessageQueue
*
* @return whether the current loop should quit.
*/ */
public static boolean runSingleNestedLooperTask(MessageQueue queue) public static void runSingleNestedLooperTask(MessageQueue queue)
throws IllegalArgumentException, IllegalAccessException, SecurityException, throws IllegalArgumentException, IllegalAccessException, SecurityException,
InvocationTargetException { InvocationTargetException {
boolean quitLoop = false; // This call will block if there are no messages in the queue. It will
// also run or more pending C++ tasks as a side effect before returning
// |msg|.
Message msg = (Message) sNextMethod.invoke(queue); Message msg = (Message) sNextMethod.invoke(queue);
if (msg == null) return true; if (msg == null) return;
if (msg.what == QUIT_MESSAGE) {
quitLoop = true;
}
Handler target = (Handler) sMessageTargetField.get(msg); Handler target = (Handler) sMessageTargetField.get(msg);
if (target == null) { if (target != null) {
// No target is a magic identifier for the quit message.
quitLoop = true;
} else {
target.dispatchMessage(msg); target.dispatchMessage(msg);
} }
...@@ -83,27 +77,23 @@ public class NestedSystemMessageHandler { ...@@ -83,27 +77,23 @@ public class NestedSystemMessageHandler {
sMessageFlagsField.set(msg, oldFlags & ~(1 << 0 /* FLAG_IN_USE */)); sMessageFlagsField.set(msg, oldFlags & ~(1 << 0 /* FLAG_IN_USE */));
msg.recycle(); msg.recycle();
return quitLoop;
} }
/** /**
* Processes messages from the current MessageQueue till the queue becomes idle. * Dispatches the first message from the current MessageQueue, blocking
* until a task becomes available if the queue is empty. Callbacks for
* other event handlers registered to the thread's looper (e.g.,
* MessagePumpAndroid) may also be processed as a side-effect.
*
* Returns true if task dispatching succeeded, or false if an exception was
* thrown.
*/ */
@SuppressWarnings("unused") @SuppressWarnings("unused")
@CalledByNative @CalledByNative
private static boolean runNestedLoopTillIdle() { private static boolean dispatchOneMessage() {
MessageQueue queue = Looper.myQueue(); MessageQueue queue = Looper.myQueue();
queue.addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
sHandler.sendMessage(sHandler.obtainMessage(QUIT_MESSAGE));
return false;
}
});
try { try {
while (!runSingleNestedLooperTask(queue)) { runSingleNestedLooperTask(queue);
}
} catch (IllegalArgumentException | IllegalAccessException | SecurityException } catch (IllegalArgumentException | IllegalAccessException | SecurityException
| InvocationTargetException e) { | InvocationTargetException e) {
e.printStackTrace(); e.printStackTrace();
...@@ -111,4 +101,16 @@ public class NestedSystemMessageHandler { ...@@ -111,4 +101,16 @@ public class NestedSystemMessageHandler {
} }
return true; return true;
} }
/*
* Causes a previous call to dispatchOneMessage() to stop blocking and
* return.
*/
@SuppressWarnings("unused")
@CalledByNative
private static void postQuitMessage() {
// Causes MessageQueue.next() to return in case it was blocking waiting
// for more messages.
sHandler.sendMessage(sHandler.obtainMessage(QUIT_MESSAGE));
}
} }
...@@ -5,120 +5,44 @@ ...@@ -5,120 +5,44 @@
#include "content/public/test/nested_message_pump_android.h" #include "content/public/test/nested_message_pump_android.h"
#include "base/android/jni_android.h" #include "base/android/jni_android.h"
#include "base/android/scoped_java_ref.h"
#include "base/check_op.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "content/public/test/android/test_support_content_jni_headers/NestedSystemMessageHandler_jni.h" #include "content/public/test/android/test_support_content_jni_headers/NestedSystemMessageHandler_jni.h"
namespace content { namespace content {
struct NestedMessagePumpAndroid::RunState { NestedMessagePumpAndroid::NestedMessagePumpAndroid() = default;
RunState(base::MessagePump::Delegate* delegate, int run_depth) NestedMessagePumpAndroid::~NestedMessagePumpAndroid() = default;
: delegate(delegate),
run_depth(run_depth), // We need to override Run() instead of using the implementation from
should_quit(false), // base::MessagePumpForUI because one of the side-effects of
waitable_event(base::WaitableEvent::ResetPolicy::AUTOMATIC, // dispatchOneMessage() is calling Looper::pollOnce(). If that happens while we
base::WaitableEvent::InitialState::NOT_SIGNALED) { // are inside Alooper_pollOnce(), we get a crash because Android Looper
waitable_event.declare_only_used_while_idle(); // isn't re-entrant safe. Instead, we keep the entire run loop in Java (in
} // MessageQueue.next()).
void NestedMessagePumpAndroid::Run(base::MessagePump::Delegate* delegate) {
base::MessagePump::Delegate* delegate; auto* env = base::android::AttachCurrentThread();
SetDelegate(delegate);
// Used to count how many Run() invocations are on the stack. ResetShouldQuit();
int run_depth; ScheduleWork();
// Used to flag that the current Run() invocation should return ASAP. while (!ShouldQuit()) {
bool should_quit; // Dispatch the first available Java message and one or more C++ messages as
// a side effect. If there are no Java messages available, this call will
// Used to sleep until there is more work to do. // block until one becomes available (while continuing to process C++ work).
base::WaitableEvent waitable_event; bool ret = Java_NestedSystemMessageHandler_dispatchOneMessage(env);
};
NestedMessagePumpAndroid::NestedMessagePumpAndroid()
: state_(NULL) {
}
NestedMessagePumpAndroid::~NestedMessagePumpAndroid() {
}
void NestedMessagePumpAndroid::Run(Delegate* delegate) {
RunState state(delegate, state_ ? state_->run_depth + 1 : 1);
RunState* previous_state = state_;
state_ = &state;
JNIEnv* env = base::android::AttachCurrentThread();
DCHECK(env);
// Need to cap the wait time to allow task processing on the java
// side. Otherwise, a long wait time on the native will starve java
// tasks.
base::TimeDelta max_delay = base::TimeDelta::FromMilliseconds(100);
for (;;) {
if (state_->should_quit)
break;
Delegate::NextWorkInfo next_work_info = delegate->DoWork();
bool has_more_immediate_work = next_work_info.is_immediate();
if (state_->should_quit)
break;
if (has_more_immediate_work)
continue;
has_more_immediate_work = state_->delegate->DoIdleWork();
if (state_->should_quit)
break;
if (has_more_immediate_work)
continue;
// No native tasks to process right now. Process tasks from the Java
// System message handler. This will return when the java message queue
// is idle.
bool ret = Java_NestedSystemMessageHandler_runNestedLoopTillIdle(env);
CHECK(ret) << "Error running java message loop, tests will likely fail."; CHECK(ret) << "Error running java message loop, tests will likely fail.";
if (next_work_info.delayed_run_time.is_max()) {
state_->waitable_event.TimedWait(max_delay);
} else {
base::TimeDelta delay = next_work_info.remaining_delay();
if (delay > max_delay)
delay = max_delay;
DCHECK_GT(delay, base::TimeDelta());
state_->waitable_event.TimedWait(delay);
}
} }
state_ = previous_state;
} }
void NestedMessagePumpAndroid::Attach(base::MessagePump::Delegate* delegate) {}
void NestedMessagePumpAndroid::Quit() { void NestedMessagePumpAndroid::Quit() {
if (state_) { // Wake up the Java message dispatcher to exit the loop in Run().
state_->should_quit = true; auto* env = base::android::AttachCurrentThread();
state_->waitable_event.Signal(); Java_NestedSystemMessageHandler_postQuitMessage(env);
return; MessagePumpForUI::Quit();
}
}
void NestedMessagePumpAndroid::ScheduleWork() {
if (state_) {
state_->waitable_event.Signal();
return;
}
} }
void NestedMessagePumpAndroid::ScheduleDelayedWork( // Since this pump allows Run() to be called on the UI thread, there's no need
const base::TimeTicks& delayed_work_time) { // to also attach the pump on that thread. Making attach a no-op also prevents
// Since this is always called from the same thread as Run(), there is nothing // the top level run loop from being considered a nested one.
// to do as the loop is already running. It will wait in Run() with the void NestedMessagePumpAndroid::Attach(Delegate*) {}
// correct timeout when it's out of immediate tasks.
// TODO(gab): Consider removing ScheduleDelayedWork() when all pumps function
// this way (bit.ly/merge-message-pump-do-work).
}
} // namespace content } // namespace content
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
#ifndef CONTENT_PUBLIC_TEST_NESTED_MESSAGE_PUMP_ANDROID_H_ #ifndef CONTENT_PUBLIC_TEST_NESTED_MESSAGE_PUMP_ANDROID_H_
#define CONTENT_PUBLIC_TEST_NESTED_MESSAGE_PUMP_ANDROID_H_ #define CONTENT_PUBLIC_TEST_NESTED_MESSAGE_PUMP_ANDROID_H_
#include "base/android/jni_android.h"
#include "base/compiler_specific.h" #include "base/compiler_specific.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/message_loop/message_pump_android.h" #include "base/message_loop/message_pump_android.h"
...@@ -19,19 +18,11 @@ class NestedMessagePumpAndroid : public base::MessagePumpForUI { ...@@ -19,19 +18,11 @@ class NestedMessagePumpAndroid : public base::MessagePumpForUI {
NestedMessagePumpAndroid(); NestedMessagePumpAndroid();
~NestedMessagePumpAndroid() override; ~NestedMessagePumpAndroid() override;
void Run(Delegate* delegate) override; void Run(Delegate*) override;
void Quit() override; void Quit() override;
void ScheduleWork() override; void Attach(Delegate*) override;
void ScheduleDelayedWork(const base::TimeTicks& delayed_work_time) override;
void Attach(Delegate* delegate) override;
private: private:
// We may make recursive calls to Run, so we save state that needs to be
// separate between them in this structure type.
struct RunState;
RunState* state_;
DISALLOW_COPY_AND_ASSIGN(NestedMessagePumpAndroid); DISALLOW_COPY_AND_ASSIGN(NestedMessagePumpAndroid);
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment