Commit 8fd73aa3 authored by qsr's avatar qsr Committed by Commit bot

mojo: Allow basic RunLoop to be quit from a posted task.

Right now, if the runloop is observing handles and a delayed task tries
to quit it, it will block until one handle gets a signal. This CL
ensures that a delayed task can quit the runloop.

R=jamesr@chromium.org

Review URL: https://codereview.chromium.org/588593002

Cr-Commit-Position: refs/heads/master@{#296378}
parent 6c788d67
......@@ -93,37 +93,40 @@ bool RunLoop::HasHandler(const Handle& handle) const {
}
void RunLoop::Run() {
assert(current() == this);
RunState* old_state = run_state_;
RunState run_state;
run_state_ = &run_state;
while (!run_state.should_quit) {
DoDelayedWork();
Wait(false);
}
run_state_ = old_state;
RunInternal(UNTIL_EMPTY);
}
void RunLoop::RunUntilIdle() {
RunInternal(UNTIL_IDLE);
}
void RunLoop::RunInternal(RunMode run_mode) {
assert(current() == this);
RunState* old_state = run_state_;
RunState run_state;
run_state_ = &run_state;
while (!run_state.should_quit) {
DoDelayedWork();
if (!Wait(true) && delayed_tasks_.empty())
for (;;) {
bool did_work = DoDelayedWork();
if (run_state.should_quit)
break;
did_work |= Wait(run_mode == UNTIL_IDLE);
if (run_state.should_quit)
break;
if (!did_work && run_mode == UNTIL_IDLE)
break;
}
run_state_ = old_state;
}
void RunLoop::DoDelayedWork() {
bool RunLoop::DoDelayedWork() {
MojoTimeTicks now = GetTimeTicksNow();
if (!delayed_tasks_.empty() && delayed_tasks_.top().run_time <= now) {
PendingTask task = delayed_tasks_.top();
delayed_tasks_.pop();
task.task.Run();
return true;
}
return false;
}
void RunLoop::Quit() {
......
......@@ -40,13 +40,13 @@ class RunLoop {
void RemoveHandler(const Handle& handle);
bool HasHandler(const Handle& handle) const;
// Runs the loop servicing handles as they are ready. This returns when Quit()
// is invoked, or there no more handles.
// Runs the loop servicing handles and tasks as they are ready. This returns
// when Quit() is invoked, or there are no more handles nor tasks.
void Run();
// Runs the loop servicing any handles that are ready. Does not wait for
// handles to become ready before returning. Returns early if Quit() is
// invoked.
// Runs the loop servicing any handles and tasks that are ready. Does not wait
// for handles or tasks to become ready before returning. Returns early if
// Quit() is invoked.
void RunUntilIdle();
void Quit();
......@@ -83,13 +83,25 @@ class RunLoop {
IGNORE_DEADLINE
};
// Do one unit of delayed work, if eligible.
void DoDelayedWork();
// Mode of operation of the run loop.
enum RunMode {
UNTIL_EMPTY,
UNTIL_IDLE
};
// Runs the loop servicing any handles and tasks that are ready. If
// |run_mode| is |UNTIL_IDLE|, does not wait for handles or tasks to become
// ready before returning. Returns early if Quit() is invoked.
void RunInternal(RunMode run_mode);
// Do one unit of delayed work, if eligible. Returns true is a task was run.
bool DoDelayedWork();
// Waits for a handle to be ready. Returns after servicing at least one
// handle (or there are no more handles) unless |non_blocking| is true,
// in which case it will also return if servicing at least one handle
// would require blocking. Returns true if a RunLoopHandler was notified.
// Waits for a handle to be ready or until the next task must be run. Returns
// after servicing at least one handle (or there are no more handles) unless
// a task must be run or |non_blocking| is true, in which case it will also
// return if no task is registered and servicing at least one handle would
// require blocking. Returns true if a RunLoopHandler was notified.
bool Wait(bool non_blocking);
// Notifies handlers of |error|. If |check| == CHECK_DEADLINE, this will
......
......@@ -413,5 +413,25 @@ TEST_F(RunLoopTest, DelayedTaskOrder) {
EXPECT_EQ(3, sequence[2]);
}
struct QuittingTask {
explicit QuittingTask(RunLoop* run_loop) : run_loop(run_loop) {}
void Run() const { run_loop->Quit(); }
RunLoop* run_loop;
};
TEST_F(RunLoopTest, QuitFromDelayedTask) {
TestRunLoopHandler handler;
MessagePipe test_pipe;
RunLoop run_loop;
run_loop.AddHandler(&handler,
test_pipe.handle0.get(),
MOJO_HANDLE_SIGNAL_READABLE,
MOJO_DEADLINE_INDEFINITE);
run_loop.PostDelayedTask(Closure(QuittingTask(&run_loop)), 0);
run_loop.Run();
}
} // namespace
} // namespace mojo
......@@ -18,34 +18,35 @@ class AsyncWaitTest(unittest.TestCase):
self.handles = system.MessagePipe()
self.cancel = self.handles.handle0.AsyncWait(system.HANDLE_SIGNAL_READABLE,
system.DEADLINE_INDEFINITE,
self.OnResult)
self.loop.PostDelayedTask(self.WriteToHandle, 100)
self._OnResult)
def tearDown(self):
self.cancel()
self.handles = None
self.array = None
self.loop = None
def OnResult(self, value):
def _OnResult(self, value):
self.array.append(value)
def WriteToHandle(self):
def _WriteToHandle(self):
self.handles.handle1.WriteMessage()
def testAsyncWait(self):
def _PostWriteAndRun(self):
self.loop.PostDelayedTask(self._WriteToHandle, 0)
self.loop.RunUntilIdle()
def testAsyncWait(self):
self._PostWriteAndRun()
self.assertEquals(len(self.array), 1)
self.assertEquals(system.RESULT_OK, self.array[0])
self.cancel()
def testAsyncWaitCancel(self):
self.loop.PostDelayedTask(self.cancel, 50)
self.loop.RunUntilIdle()
self.loop.PostDelayedTask(self.cancel, 0)
self._PostWriteAndRun()
self.assertEquals(len(self.array), 0)
self.cancel()
def testAsyncWaitImmediateCancel(self):
self.cancel()
self.loop.RunUntilIdle()
self._PostWriteAndRun()
self.assertEquals(len(self.array), 0)
self.cancel()
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