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