Allow work that was deferred on account of not being runnable in a nested loop

to be processed when returning to an outer loop.

BUG=11470 13442 13468
TEST=base_unittests, test cases in bugs
Review URL: http://codereview.chromium.org/146006

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@19272 0039d316-1c4b-4281-b951-d872f2087c98
Esse commit está contido em:
mark@chromium.org
2009-06-25 19:23:11 +00:00
commit 9ba33ec3ca
3 arquivos alterados com 277 adições e 133 exclusões
+54 -16
Ver Arquivo
@@ -593,6 +593,7 @@ enum TaskType {
QUITMESSAGELOOP,
ORDERERD,
PUMPS,
SLEEP,
};
// Saves the order in which the tasks executed.
@@ -623,6 +624,7 @@ std::ostream& operator <<(std::ostream& os, TaskType type) {
case QUITMESSAGELOOP: os << "QUITMESSAGELOOP"; break;
case ORDERERD: os << "ORDERERD"; break;
case PUMPS: os << "PUMPS"; break;
case SLEEP: os << "SLEEP"; break;
default:
NOTREACHED();
os << "Unknown TaskType";
@@ -765,6 +767,22 @@ class QuitTask : public OrderedTasks {
}
};
class SleepTask : public OrderedTasks {
public:
SleepTask(TaskList* order, int cookie, int ms)
: OrderedTasks(order, SLEEP, cookie), ms_(ms) {
}
virtual void Run() {
RunStart();
PlatformThread::Sleep(ms_);
RunEnd();
}
private:
int ms_;
};
#if defined(OS_WIN)
class Recursive2Tasks : public Task {
@@ -1027,7 +1045,8 @@ void RunTest_NonNestableWithNoNesting(MessageLoop::Type message_loop_type) {
}
// Tests that non nestable tasks don't run when there's code in the call stack.
void RunTest_NonNestableInNestedLoop(MessageLoop::Type message_loop_type) {
void RunTest_NonNestableInNestedLoop(MessageLoop::Type message_loop_type,
bool use_delayed) {
MessageLoop loop(message_loop_type);
TaskList order;
@@ -1035,26 +1054,39 @@ void RunTest_NonNestableInNestedLoop(MessageLoop::Type message_loop_type) {
MessageLoop::current()->PostTask(FROM_HERE,
new TaskThatPumps(&order, 1));
Task* task = new OrderedTasks(&order, 2);
MessageLoop::current()->PostNonNestableTask(FROM_HERE, task);
if (use_delayed) {
MessageLoop::current()->PostNonNestableDelayedTask(FROM_HERE, task, 1);
} else {
MessageLoop::current()->PostNonNestableTask(FROM_HERE, task);
}
MessageLoop::current()->PostTask(FROM_HERE, new OrderedTasks(&order, 3));
MessageLoop::current()->PostTask(FROM_HERE, new OrderedTasks(&order, 4));
Task* non_nestable_quit = new QuitTask(&order, 5);
MessageLoop::current()->PostNonNestableTask(FROM_HERE, non_nestable_quit);
MessageLoop::current()->PostTask(FROM_HERE, new SleepTask(&order, 4, 50));
MessageLoop::current()->PostTask(FROM_HERE, new OrderedTasks(&order, 5));
Task* non_nestable_quit = new QuitTask(&order, 6);
if (use_delayed) {
MessageLoop::current()->PostNonNestableDelayedTask(FROM_HERE,
non_nestable_quit,
2);
} else {
MessageLoop::current()->PostNonNestableTask(FROM_HERE, non_nestable_quit);
}
MessageLoop::current()->Run();
// FIFO order.
ASSERT_EQ(10U, order.size());
ASSERT_EQ(12U, order.size());
EXPECT_EQ(order[ 0], TaskItem(PUMPS, 1, true));
EXPECT_EQ(order[ 1], TaskItem(ORDERERD, 3, true));
EXPECT_EQ(order[ 2], TaskItem(ORDERERD, 3, false));
EXPECT_EQ(order[ 3], TaskItem(ORDERERD, 4, true));
EXPECT_EQ(order[ 4], TaskItem(ORDERERD, 4, false));
EXPECT_EQ(order[ 5], TaskItem(PUMPS, 1, false));
EXPECT_EQ(order[ 6], TaskItem(ORDERERD, 2, true));
EXPECT_EQ(order[ 7], TaskItem(ORDERERD, 2, false));
EXPECT_EQ(order[ 8], TaskItem(QUITMESSAGELOOP, 5, true));
EXPECT_EQ(order[ 9], TaskItem(QUITMESSAGELOOP, 5, false));
EXPECT_EQ(order[ 3], TaskItem(SLEEP, 4, true));
EXPECT_EQ(order[ 4], TaskItem(SLEEP, 4, false));
EXPECT_EQ(order[ 5], TaskItem(ORDERERD, 5, true));
EXPECT_EQ(order[ 6], TaskItem(ORDERERD, 5, false));
EXPECT_EQ(order[ 7], TaskItem(PUMPS, 1, false));
EXPECT_EQ(order[ 8], TaskItem(ORDERERD, 2, true));
EXPECT_EQ(order[ 9], TaskItem(ORDERERD, 2, false));
EXPECT_EQ(order[10], TaskItem(QUITMESSAGELOOP, 6, true));
EXPECT_EQ(order[11], TaskItem(QUITMESSAGELOOP, 6, false));
}
#if defined(OS_WIN)
@@ -1365,9 +1397,15 @@ TEST(MessageLoopTest, NonNestableWithNoNesting) {
}
TEST(MessageLoopTest, NonNestableInNestedLoop) {
RunTest_NonNestableInNestedLoop(MessageLoop::TYPE_DEFAULT);
RunTest_NonNestableInNestedLoop(MessageLoop::TYPE_UI);
RunTest_NonNestableInNestedLoop(MessageLoop::TYPE_IO);
RunTest_NonNestableInNestedLoop(MessageLoop::TYPE_DEFAULT, false);
RunTest_NonNestableInNestedLoop(MessageLoop::TYPE_UI, false);
RunTest_NonNestableInNestedLoop(MessageLoop::TYPE_IO, false);
}
TEST(MessageLoopTest, NonNestableDelayedInNestedLoop) {
RunTest_NonNestableInNestedLoop(MessageLoop::TYPE_DEFAULT, true);
RunTest_NonNestableInNestedLoop(MessageLoop::TYPE_UI, true);
RunTest_NonNestableInNestedLoop(MessageLoop::TYPE_IO, true);
}
#if defined(OS_WIN)
+50 -23
Ver Arquivo
@@ -57,6 +57,11 @@ class MessagePumpCFRunLoopBase : public MessagePump {
// The thread's run loop.
CFRunLoopRef run_loop_;
// The recursion depth of the currently-executing CFRunLoopRun loop on the
// run loop's thread. 0 if no run loops are running inside of whatever scope
// the object was created in.
int nesting_level_;
private:
// Timer callback scheduled by ScheduleDelayedWork. This does not do any
// work, but it signals delayed_work_source_ so that delayed work can be
@@ -64,25 +69,61 @@ class MessagePumpCFRunLoopBase : public MessagePump {
static void RunDelayedWorkTimer(CFRunLoopTimerRef timer, void* info);
// Perform highest-priority work. This is associated with work_source_
// signalled by ScheduleWork.
static void RunWork(void* info);
// signalled by ScheduleWork. The static method calls the instance method;
// the instance method returns true if work was done.
static void RunWorkSource(void* info);
bool RunWork();
// Perform delayed-priority work. This is associated with
// delayed_work_source_ signalled by RunDelayedWorkTimer, and is responsible
// for calling ScheduleDelayedWork again if appropriate.
static void RunDelayedWork(void* info);
// for calling ScheduleDelayedWork again if appropriate. The static method
// calls the instance method; the instance method returns true if more
// delayed work is available.
static void RunDelayedWorkSource(void* info);
bool RunDelayedWork();
// Perform idle-priority work. This is normally called by PreWaitObserver,
// but is also associated with idle_work_source_. When this function
// actually does perform idle work, it will resignal that source. The
// static method calls the instance method; the instance method returns
// true if idle work was done.
static void RunIdleWorkSource(void* info);
bool RunIdleWork();
// Perform work that may have been deferred because it was not runnable
// within a nested run loop. This is associated with
// nesting_deferred_work_source_ and is signalled by EnterExitObserver when
// a run loop exits, so that an outer loop will be able to perform the
// necessary tasks. The static method calls the instance method; the
// instance method returns true if anything was done.
static void RunNestingDeferredWorkSource(void* info);
bool RunNestingDeferredWork();
// Observer callback responsible for performing idle-priority work, before
// the run loop goes to sleep. Associated with idle_work_observer_.
static void RunIdleWork(CFRunLoopObserverRef observer,
CFRunLoopActivity activity, void* info);
static void PreWaitObserver(CFRunLoopObserverRef observer,
CFRunLoopActivity activity, void* info);
// The timer, sources, and observer are described above alongside their
// Observer callback called when the run loop starts and stops, at the
// beginning and end of calls to CFRunLoopRun. This is used to maintain
// nesting_level_. Associated with enter_exit_observer_.
static void EnterExitObserver(CFRunLoopObserverRef observer,
CFRunLoopActivity activity, void* info);
// Called by EnterExitObserver after performing maintenance on nesting_level_.
// This allows subclasses an opportunity to perform additional processing on
// the basis of run loops starting and stopping.
virtual void EnterExitRunLoop(CFRunLoopActivity activity);
// The timer, sources, and observers are described above alongside their
// callbacks.
CFRunLoopTimerRef delayed_work_timer_;
CFRunLoopSourceRef work_source_;
CFRunLoopSourceRef delayed_work_source_;
CFRunLoopObserverRef idle_work_observer_;
CFRunLoopSourceRef idle_work_source_;
CFRunLoopSourceRef nesting_deferred_work_source_;
CFRunLoopObserverRef pre_wait_observer_;
CFRunLoopObserverRef enter_exit_observer_;
// (weak) Delegate passed as an argument to the innermost Run call.
Delegate* delegate_;
@@ -93,26 +134,12 @@ class MessagePumpCFRunLoopBase : public MessagePump {
class MessagePumpCFRunLoop : public MessagePumpCFRunLoopBase {
public:
MessagePumpCFRunLoop();
virtual ~MessagePumpCFRunLoop();
virtual void DoRun(Delegate* delegate);
virtual void Quit();
private:
// Observer callback called when the run loop starts and stops, at the
// beginning and end of calls to CFRunLoopRun. This is used to maintain
// nesting_level_ and to handle deferred loop quits. Associated with
// enter_exit_observer_.
static void EnterExitRunLoop(CFRunLoopObserverRef observer,
CFRunLoopActivity activity, void* info);
// Observer for EnterExitRunLoop.
CFRunLoopObserverRef enter_exit_observer_;
// The recursion depth of the currently-executing CFRunLoopRun loop on the
// run loop's thread. 0 if no run loops are running inside of whatever scope
// the object was created in.
int nesting_level_;
virtual void EnterExitRunLoop(CFRunLoopActivity activity);
// The recursion depth (calculated in the same way as nesting_level_) of the
// innermost executing CFRunLoopRun loop started by a call to Run.
+173 -94
Ver Arquivo
@@ -22,7 +22,9 @@ namespace base {
// Must be called on the run loop thread.
MessagePumpCFRunLoopBase::MessagePumpCFRunLoopBase()
: delegate_(NULL) {
: nesting_level_(0),
delegate_(NULL)
{
run_loop_ = CFRunLoopGetCurrent();
CFRetain(run_loop_);
@@ -42,34 +44,69 @@ MessagePumpCFRunLoopBase::MessagePumpCFRunLoopBase()
CFRunLoopSourceContext source_context = CFRunLoopSourceContext();
source_context.info = this;
source_context.perform = RunWork;
source_context.perform = RunWorkSource;
work_source_ = CFRunLoopSourceCreate(NULL, // allocator
0, // priority
1, // priority
&source_context);
CFRunLoopAddSource(run_loop_, work_source_, kCFRunLoopCommonModes);
source_context.perform = RunDelayedWork;
source_context.perform = RunDelayedWorkSource;
delayed_work_source_ = CFRunLoopSourceCreate(NULL, // allocator
1, // priority
2, // priority
&source_context);
CFRunLoopAddSource(run_loop_, delayed_work_source_, kCFRunLoopCommonModes);
source_context.perform = RunIdleWorkSource;
idle_work_source_ = CFRunLoopSourceCreate(NULL, // allocator
3, // priority
&source_context);
CFRunLoopAddSource(run_loop_, idle_work_source_, kCFRunLoopCommonModes);
source_context.perform = RunNestingDeferredWorkSource;
nesting_deferred_work_source_ = CFRunLoopSourceCreate(NULL, // allocator
0, // priority
&source_context);
CFRunLoopAddSource(run_loop_, nesting_deferred_work_source_,
kCFRunLoopCommonModes);
CFRunLoopObserverContext observer_context = CFRunLoopObserverContext();
observer_context.info = this;
idle_work_observer_ = CFRunLoopObserverCreate(NULL, // allocator
kCFRunLoopBeforeWaiting,
true, // repeat
0, // priority
RunIdleWork,
&observer_context);
CFRunLoopAddObserver(run_loop_, idle_work_observer_, kCFRunLoopCommonModes);
pre_wait_observer_ = CFRunLoopObserverCreate(NULL, // allocator
kCFRunLoopBeforeWaiting,
true, // repeat
0, // priority
PreWaitObserver,
&observer_context);
CFRunLoopAddObserver(run_loop_, pre_wait_observer_, kCFRunLoopCommonModes);
enter_exit_observer_ = CFRunLoopObserverCreate(NULL, // allocator
kCFRunLoopEntry |
kCFRunLoopExit,
true, // repeat
0, // priority
EnterExitObserver,
&observer_context);
CFRunLoopAddObserver(run_loop_, enter_exit_observer_, kCFRunLoopCommonModes);
}
// Ideally called on the run loop thread.
// Ideally called on the run loop thread. If other run loops were running
// lower on the run loop thread's stack when this object was created, the
// same number of run loops must be running when this object is destroyed.
MessagePumpCFRunLoopBase::~MessagePumpCFRunLoopBase() {
CFRunLoopRemoveObserver(run_loop_, idle_work_observer_,
CFRunLoopRemoveObserver(run_loop_, enter_exit_observer_,
kCFRunLoopCommonModes);
CFRelease(idle_work_observer_);
CFRelease(enter_exit_observer_);
CFRunLoopRemoveObserver(run_loop_, pre_wait_observer_,
kCFRunLoopCommonModes);
CFRelease(pre_wait_observer_);
CFRunLoopRemoveSource(run_loop_, nesting_deferred_work_source_,
kCFRunLoopCommonModes);
CFRelease(nesting_deferred_work_source_);
CFRunLoopRemoveSource(run_loop_, idle_work_source_, kCFRunLoopCommonModes);
CFRelease(idle_work_source_);
CFRunLoopRemoveSource(run_loop_, delayed_work_source_, kCFRunLoopCommonModes);
CFRelease(delayed_work_source_);
@@ -125,7 +162,7 @@ void MessagePumpCFRunLoopBase::ScheduleDelayedWork(
// static
void MessagePumpCFRunLoopBase::RunDelayedWorkTimer(CFRunLoopTimerRef timer,
void* info) {
MessagePumpCFRunLoop* self = static_cast<MessagePumpCFRunLoop*>(info);
MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info);
// CFRunLoopTimers fire outside of the priority scheme for CFRunLoopSources.
// In order to establish the proper priority where delegate_->DoDelayedWork
@@ -137,102 +174,159 @@ void MessagePumpCFRunLoopBase::RunDelayedWorkTimer(CFRunLoopTimerRef timer,
// Called from the run loop.
// static
void MessagePumpCFRunLoopBase::RunWork(void* info) {
MessagePumpCFRunLoop* self = static_cast<MessagePumpCFRunLoop*>(info);
void MessagePumpCFRunLoopBase::RunWorkSource(void* info) {
MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info);
self->RunWork();
}
// Called by MessagePumpCFRunLoopBase::RunWorkSource.
bool MessagePumpCFRunLoopBase::RunWork() {
// If we're on the main event loop, the NSApp runloop won't clean up the
// autoreleasepool until there is UI event, so use a local one for any
// autorelease pool until there is a UI event, so use a local one for any
// autoreleased objects to ensure they go away sooner.
ScopedNSAutoreleasePool autorelease_pool;
// Call DoWork once, and if something was done, arrange to come back here
// again as long as the loop is still running.
if (self->delegate_->DoWork()) {
CFRunLoopSourceSignal(self->work_source_);
bool did_work = delegate_->DoWork();
if (did_work) {
CFRunLoopSourceSignal(work_source_);
}
return did_work;
}
// Called from the run loop.
// static
void MessagePumpCFRunLoopBase::RunDelayedWork(void* info) {
MessagePumpCFRunLoop* self = static_cast<MessagePumpCFRunLoop*>(info);
void MessagePumpCFRunLoopBase::RunDelayedWorkSource(void* info) {
MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info);
self->RunDelayedWork();
}
// Called by MessagePumpCFRunLoopBase::RunDelayedWorkSource.
bool MessagePumpCFRunLoopBase::RunDelayedWork() {
// If we're on the main event loop, the NSApp runloop won't clean up the
// autoreleasepool until there is UI event, so use a local one for any
// autorelease pool until there is a UI event, so use a local one for any
// autoreleased objects to ensure they go away sooner.
ScopedNSAutoreleasePool autorelease_pool;
Time next_time;
self->delegate_->DoDelayedWork(&next_time);
if (!next_time.is_null()) {
delegate_->DoDelayedWork(&next_time);
bool more_work = !next_time.is_null();
if (more_work) {
TimeDelta delay = next_time - Time::Now();
if (delay > TimeDelta()) {
// There's more delayed work to be done in the future.
self->ScheduleDelayedWork(next_time);
ScheduleDelayedWork(next_time);
} else {
// There's more delayed work to be done, and its time is in the past.
// Arrange to come back here directly as long as the loop is still
// running.
CFRunLoopSourceSignal(self->delayed_work_source_);
CFRunLoopSourceSignal(delayed_work_source_);
}
}
return more_work;
}
// Called from the run loop.
// static
void MessagePumpCFRunLoopBase::RunIdleWork(CFRunLoopObserverRef observer,
CFRunLoopActivity activity,
void* info) {
MessagePumpCFRunLoop* self = static_cast<MessagePumpCFRunLoop*>(info);
void MessagePumpCFRunLoopBase::RunIdleWorkSource(void* info) {
MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info);
self->RunIdleWork();
}
// Called by MessagePumpCFRunLoopBase::RunIdleWorkSource.
bool MessagePumpCFRunLoopBase::RunIdleWork() {
// If we're on the main event loop, the NSApp runloop won't clean up the
// autoreleasepool until there is UI event, so use a local one for any
// autorelease pool until there is a UI event, so use a local one for any
// autoreleased objects to ensure they go away sooner.
ScopedNSAutoreleasePool autorelease_pool;
// The "self->delegate_ &&" part of the clause is needed for the case of
// the temporary modal first run dialog. The dialog is displayed really
// early in the Chrome launch process at which time self->delegate_ is null.
// TODO: remove the "self->delegate_ &&" clause from the bellow condition once
// we remove the modal first run dialog.
if (self->delegate_ && self->delegate_->DoIdleWork()) {
// If idle work was done, don't let the loop go to sleep. More idle work
// might be waiting.
CFRunLoopWakeUp(self->run_loop_);
// Call DoIdleWork once, and if something was done, arrange to come back here
// again as long as the loop is still running.
bool did_work = delegate_->DoIdleWork();
if (did_work) {
CFRunLoopSourceSignal(idle_work_source_);
}
return did_work;
}
// Called from the run loop.
// static
void MessagePumpCFRunLoopBase::RunNestingDeferredWorkSource(void* info) {
MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info);
self->RunNestingDeferredWork();
}
// Called by MessagePumpCFRunLoopBase::RunNestingDeferredWorkSource.
bool MessagePumpCFRunLoopBase::RunNestingDeferredWork() {
if (!RunWork()) {
if (!RunDelayedWork()) {
if (!RunIdleWork()) {
return false;
}
}
}
return true;
}
// Called from the run loop.
// static
void MessagePumpCFRunLoopBase::PreWaitObserver(CFRunLoopObserverRef observer,
CFRunLoopActivity activity,
void* info) {
MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info);
// Attempt to do some idle work before going to sleep.
self->RunIdleWork();
}
// Called from the run loop.
// static
void MessagePumpCFRunLoopBase::EnterExitObserver(CFRunLoopObserverRef observer,
CFRunLoopActivity activity,
void* info) {
MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info);
switch (activity) {
case kCFRunLoopEntry:
++self->nesting_level_;
break;
case kCFRunLoopExit:
--self->nesting_level_;
if (self->nesting_level_) {
// It's possible that some work was not performed because it was
// inappropriate to do within a nested loop. When leaving any inner
// loop, signal the nesting-deferred work source to ensure that such
// work be afforded an opportunity to be processed if appropriate.
CFRunLoopSourceSignal(self->nesting_deferred_work_source_);
}
break;
default:
break;
}
self->EnterExitRunLoop(activity);
}
// Called by MessagePumpCFRunLoopBase::EnterExitRunLoop. The default
// implementation is a no-op.
void MessagePumpCFRunLoopBase::EnterExitRunLoop(CFRunLoopActivity activity) {
}
// Must be called on the run loop thread.
MessagePumpCFRunLoop::MessagePumpCFRunLoop()
: nesting_level_(0),
innermost_quittable_(0),
: innermost_quittable_(0),
quit_pending_(false) {
CFRunLoopObserverContext observer_context = CFRunLoopObserverContext();
observer_context.info = this;
enter_exit_observer_ = CFRunLoopObserverCreate(NULL, // allocator
kCFRunLoopEntry |
kCFRunLoopExit,
true, // repeat
0, // priority
EnterExitRunLoop,
&observer_context);
CFRunLoopAddObserver(run_loop_, enter_exit_observer_, kCFRunLoopCommonModes);
}
// Ideally called on the run loop thread. If other CFRunLoopRun loops were
// Called by MessagePumpCFRunLoopBase::DoRun. If other CFRunLoopRun loops were
// running lower on the run loop thread's stack when this object was created,
// the same number of CFRunLoopRun loops must be running when this object is
// destroyed.
MessagePumpCFRunLoop::~MessagePumpCFRunLoop() {
CFRunLoopRemoveObserver(run_loop_, enter_exit_observer_,
kCFRunLoopCommonModes);
CFRelease(enter_exit_observer_);
}
// Called by CFRunLoopBase::DoRun. If other CFRunLoopRun loops were running
// lower on the run loop thread's stack when this object was created, the same
// number of CFRunLoopRun loops must be running for the outermost call to Run.
// Run/DoRun are reentrant after that point.
// the same number of CFRunLoopRun loops must be running for the outermost call
// to Run. Run/DoRun are reentrant after that point.
void MessagePumpCFRunLoop::DoRun(Delegate* delegate) {
// nesting_level_ will be incremented in EnterExitRunLoop, so set
// innermost_quittable_ accordingly.
@@ -267,32 +361,17 @@ void MessagePumpCFRunLoop::Quit() {
}
}
// Called from the run loop.
// static
void MessagePumpCFRunLoop::EnterExitRunLoop(CFRunLoopObserverRef observer,
CFRunLoopActivity activity,
void* info) {
MessagePumpCFRunLoop* self = static_cast<MessagePumpCFRunLoop*>(info);
switch (activity) {
case kCFRunLoopEntry:
// If the run loop was entered by a call to Run, this will properly
// balance the decrement done in Run before entering the loop.
++self->nesting_level_;
break;
case kCFRunLoopExit:
if (--self->nesting_level_ == self->innermost_quittable_ &&
self->quit_pending_) {
// Quit was called while loops other than those managed by this object
// were running further inside a run loop managed by this object. Now
// that all unmanaged inner run loops are gone, stop the loop running
// just inside Run.
CFRunLoopStop(self->run_loop_);
self->quit_pending_ = false;
}
break;
default:
break;
// Called by MessagePumpCFRunLoopBase::EnterExitObserver.
void MessagePumpCFRunLoop::EnterExitRunLoop(CFRunLoopActivity activity) {
if (activity == kCFRunLoopExit &&
nesting_level_ == innermost_quittable_ &&
quit_pending_) {
// Quit was called while loops other than those managed by this object
// were running further inside a run loop managed by this object. Now
// that all unmanaged inner run loops are gone, stop the loop running
// just inside Run.
CFRunLoopStop(run_loop_);
quit_pending_ = false;
}
}