From 367538330c78771d7a717bee8dc0b3e7461a87a9 Mon Sep 17 00:00:00 2001 From: mmenke Date: Sat, 9 May 2015 08:40:53 -0700 Subject: [PATCH] Allow fetchers to watch URLRequestContextGetters for context shutdown. Fetchers outliving the context they're using has been the main cause of AssertNoURLRequests crashes. This will let the ~26 URLRequestContextGetter implementations be responsible for dealing with lifetime issues, rather than their 160+ consumers. URLRequestContextGetter subclasses still need to be modified to actually inform the fetcher of URLRequestContext shutdown. BUG=471069 Review URL: https://codereview.chromium.org/1100603002 Cr-Commit-Position: refs/heads/master@{#329046} --- net/base/net_error_list.h | 4 + net/net.gypi | 1 + net/url_request/url_fetcher_core.cc | 74 +++++++---- net/url_request/url_fetcher_core.h | 15 ++- net/url_request/url_fetcher_impl_unittest.cc | 124 ++++++++++++++++-- net/url_request/url_request_context_getter.cc | 23 ++++ net/url_request/url_request_context_getter.h | 42 ++++-- .../url_request_context_getter_observer.h | 35 +++++ 8 files changed, 264 insertions(+), 54 deletions(-) create mode 100644 net/url_request/url_request_context_getter_observer.h diff --git a/net/base/net_error_list.h b/net/base/net_error_list.h index 7a8281f532bff0..363b305be1329b 100644 --- a/net/base/net_error_list.h +++ b/net/base/net_error_list.h @@ -103,6 +103,10 @@ NET_ERROR(BLOCKED_ENROLLMENT_CHECK_PENDING, -24) // retry or a redirect, but the upload stream doesn't support that operation. NET_ERROR(UPLOAD_STREAM_REWIND_NOT_SUPPORTED, -25) +// The request failed because the URLRequestContext is shutting down, or has +// been shut down. +NET_ERROR(CONTEXT_SHUT_DOWN, -26) + // A connection was closed (corresponding to a TCP FIN). NET_ERROR(CONNECTION_CLOSED, -100) diff --git a/net/net.gypi b/net/net.gypi index 227428115da09e..4cc69b84fc78d1 100644 --- a/net/net.gypi +++ b/net/net.gypi @@ -1178,6 +1178,7 @@ 'url_request/url_request_context_builder.h', 'url_request/url_request_context_getter.cc', 'url_request/url_request_context_getter.h', + 'url_request/url_request_context_getter_observer.h', 'url_request/url_request_context_storage.cc', 'url_request/url_request_context_storage.h', 'url_request/url_request_data_job.cc', diff --git a/net/url_request/url_fetcher_core.cc b/net/url_request/url_fetcher_core.cc index f38f9a497a7b10..d34f20f4d0919a 100644 --- a/net/url_request/url_fetcher_core.cc +++ b/net/url_request/url_fetcher_core.cc @@ -469,6 +469,11 @@ void URLFetcherCore::OnReadCompleted(URLRequest* request, } } +void URLFetcherCore::OnContextShuttingDown() { + DCHECK(request_); + CancelRequestAndInformDelegate(ERR_CONTEXT_SHUT_DOWN); +} + void URLFetcherCore::CancelAll() { g_registry.Get().CancelAll(); } @@ -508,11 +513,17 @@ void URLFetcherCore::StartURLRequest() { return; } + if (!request_context_getter_->GetURLRequestContext()) { + CancelRequestAndInformDelegate(ERR_CONTEXT_SHUT_DOWN); + return; + } + DCHECK(request_context_getter_.get()); DCHECK(!request_.get()); g_registry.Get().AddURLFetcherCore(this); current_response_bytes_ = 0; + request_context_getter_->AddObserver(this); request_ = request_context_getter_->GetURLRequestContext()->CreateRequest( original_url_, DEFAULT_PRIORITY, this); request_->set_stack_trace(stack_trace_); @@ -600,10 +611,7 @@ void URLFetcherCore::StartURLRequest() { void URLFetcherCore::DidInitializeWriter(int result) { if (result != OK) { - CancelURLRequest(result); - delegate_task_runner_->PostTask( - FROM_HERE, - base::Bind(&URLFetcherCore::InformDelegateFetchIsComplete, this)); + CancelRequestAndInformDelegate(result); return; } StartURLRequestWhenAppropriate(); @@ -617,27 +625,33 @@ void URLFetcherCore::StartURLRequestWhenAppropriate() { DCHECK(request_context_getter_.get()); - int64 delay = 0; - if (!original_url_throttler_entry_.get()) { - URLRequestThrottlerManager* manager = - request_context_getter_->GetURLRequestContext()->throttler_manager(); - if (manager) { + // Check if the request should be delayed, and if so, post a task to start it + // after the delay has expired. Otherwise, start it now. + + URLRequestContext* context = request_context_getter_->GetURLRequestContext(); + // If the context has been shut down, or there's no ThrottlerManager, just + // start the request. In the former case, StartURLRequest() will just inform + // the URLFetcher::Delegate the request has been canceled. + if (context && context->throttler_manager()) { + if (!original_url_throttler_entry_.get()) { original_url_throttler_entry_ = - manager->RegisterRequestUrl(original_url_); + context->throttler_manager()->RegisterRequestUrl(original_url_); } - } - if (original_url_throttler_entry_.get()) { - delay = original_url_throttler_entry_->ReserveSendingTimeForNextRequest( - GetBackoffReleaseTime()); - } - if (delay == 0) { - StartURLRequest(); - } else { - base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( - FROM_HERE, base::Bind(&URLFetcherCore::StartURLRequest, this), - base::TimeDelta::FromMilliseconds(delay)); + if (original_url_throttler_entry_.get()) { + int64 delay = + original_url_throttler_entry_->ReserveSendingTimeForNextRequest( + GetBackoffReleaseTime()); + if (delay != 0) { + base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, base::Bind(&URLFetcherCore::StartURLRequest, this), + base::TimeDelta::FromMilliseconds(delay)); + return; + } + } } + + StartURLRequest(); } void URLFetcherCore::CancelURLRequest(int error) { @@ -703,10 +717,7 @@ void URLFetcherCore::NotifyMalformedContent() { void URLFetcherCore::DidFinishWriting(int result) { if (result != OK) { - CancelURLRequest(result); - delegate_task_runner_->PostTask( - FROM_HERE, - base::Bind(&URLFetcherCore::InformDelegateFetchIsComplete, this)); + CancelRequestAndInformDelegate(result); return; } // If the file was successfully closed, then the URL request is complete. @@ -768,7 +779,15 @@ void URLFetcherCore::RetryOrCompleteUrlFetch() { DCHECK(posted || !delegate_); } +void URLFetcherCore::CancelRequestAndInformDelegate(int result) { + CancelURLRequest(result); + delegate_task_runner_->PostTask( + FROM_HERE, + base::Bind(&URLFetcherCore::InformDelegateFetchIsComplete, this)); +} + void URLFetcherCore::ReleaseRequest() { + request_context_getter_->RemoveObserver(this); upload_progress_checker_timer_.reset(); request_.reset(); g_registry.Get().RemoveURLFetcherCore(this); @@ -827,11 +846,8 @@ int URLFetcherCore::WriteBuffer(scoped_refptr data) { void URLFetcherCore::DidWriteBuffer(scoped_refptr data, int result) { if (result < 0) { // Handle errors. - CancelURLRequest(result); response_writer_->Finish(base::Bind(&EmptyCompletionCallback)); - delegate_task_runner_->PostTask( - FROM_HERE, - base::Bind(&URLFetcherCore::InformDelegateFetchIsComplete, this)); + CancelRequestAndInformDelegate(result); return; } diff --git a/net/url_request/url_fetcher_core.h b/net/url_request/url_fetcher_core.h index 193edb46528d83..b20720e3caf778 100644 --- a/net/url_request/url_fetcher_core.h +++ b/net/url_request/url_fetcher_core.h @@ -20,6 +20,7 @@ #include "net/http/http_request_headers.h" #include "net/url_request/url_fetcher.h" #include "net/url_request/url_request.h" +#include "net/url_request/url_request_context_getter_observer.h" #include "net/url_request/url_request_status.h" #include "url/gurl.h" @@ -36,9 +37,9 @@ class URLFetcherResponseWriter; class URLRequestContextGetter; class URLRequestThrottlerEntryInterface; -class URLFetcherCore - : public base::RefCountedThreadSafe, - public URLRequest::Delegate { +class URLFetcherCore : public base::RefCountedThreadSafe, + public URLRequest::Delegate, + public URLRequestContextGetterObserver { public: URLFetcherCore(URLFetcher* fetcher, const GURL& original_url, @@ -133,6 +134,9 @@ class URLFetcherCore void OnCertificateRequested(URLRequest* request, SSLCertRequestInfo* cert_request_info) override; + // Overridden from URLRequestContextGetterObserver: + void OnContextShuttingDown() override; + URLFetcherDelegate* delegate() const { return delegate_; } static void CancelAll(); static int GetNumFetcherCores(); @@ -142,6 +146,7 @@ class URLFetcherCore private: friend class base::RefCountedThreadSafe; + // TODO(mmenke): Remove this class. class Registry { public: Registry(); @@ -177,6 +182,10 @@ class URLFetcherCore void DidFinishWriting(int result); void RetryOrCompleteUrlFetch(); + // Cancels the URLRequest and informs the delegate that it failed with the + // specified error. Must be called on network thread. + void CancelRequestAndInformDelegate(int result); + // Deletes the request, removes it from the registry, and removes the // destruction observer. void ReleaseRequest(); diff --git a/net/url_request/url_fetcher_impl_unittest.cc b/net/url_request/url_fetcher_impl_unittest.cc index e376ce56983c66..a76eaa4854adbf 100644 --- a/net/url_request/url_fetcher_impl_unittest.cc +++ b/net/url_request/url_fetcher_impl_unittest.cc @@ -181,7 +181,8 @@ class FetcherTestURLRequestContextGetter : public URLRequestContextGetter { scoped_refptr network_task_runner, const std::string& hanging_domain) : network_task_runner_(network_task_runner), - hanging_domain_(hanging_domain) {} + hanging_domain_(hanging_domain), + shutting_down_(false) {} // Sets callback to be invoked when the getter is destroyed. void set_on_destruction_callback( @@ -189,22 +190,25 @@ class FetcherTestURLRequestContextGetter : public URLRequestContextGetter { on_destruction_callback_ = on_destruction_callback; } - scoped_refptr GetNetworkTaskRunner() - const override { - return network_task_runner_; - } - // URLRequestContextGetter: FetcherTestURLRequestContext* GetURLRequestContext() override { // Calling this on the wrong thread may be either a bug in the test or a bug // in production code. EXPECT_TRUE(network_task_runner_->BelongsToCurrentThread()); + if (shutting_down_) + return nullptr; + if (!context_) context_.reset(new FetcherTestURLRequestContext(hanging_domain_)); return context_.get(); } + scoped_refptr GetNetworkTaskRunner() + const override { + return network_task_runner_; + } + // Adds a throttler entry with the specified parameters. Does this // synchronously if the context lives on the current thread, or posts a task // to the relevant thread otherwise. @@ -242,6 +246,30 @@ class FetcherTestURLRequestContextGetter : public URLRequestContextGetter { entry->ReserveSendingTimeForNextRequest(base::TimeTicks()); } + // Tells the getter to act as if the URLRequestContext is about to be shut + // down. + void Shutdown() { + if (!network_task_runner_->RunsTasksOnCurrentThread()) { + network_task_runner_->PostTask( + FROM_HERE, + base::Bind(&FetcherTestURLRequestContextGetter::Shutdown, this)); + return; + } + + shutting_down_ = true; + NotifyContextShuttingDown(); + // Should now be safe to destroy the context. Context will check it has no + // pending requests. + context_.reset(); + } + + // Convenience method to access the context as a FetcherTestURLRequestContext + // without going through GetURLRequestContext. + FetcherTestURLRequestContext* context() { + DCHECK(network_task_runner_->BelongsToCurrentThread()); + return context_.get(); + } + protected: ~FetcherTestURLRequestContextGetter() override { // |context_| may only be deleted on the network thread. Fortunately, @@ -255,6 +283,7 @@ class FetcherTestURLRequestContextGetter : public URLRequestContextGetter { const std::string hanging_domain_; scoped_ptr context_; + bool shutting_down_; base::Closure on_destruction_callback_; @@ -464,8 +493,9 @@ TEST_F(URLFetcherTest, CancelAll) { scoped_refptr context_getter( CreateSameThreadContextGetter()); - MockHostResolver* mock_resolver = - context_getter->GetURLRequestContext()->mock_resolver(); + // Force context creation. + context_getter->GetURLRequestContext(); + MockHostResolver* mock_resolver = context_getter->context()->mock_resolver(); WaitingURLFetcherDelegate delegate; delegate.CreateFetcher(hanging_url(), URLFetcher::GET, context_getter); @@ -485,8 +515,9 @@ TEST_F(URLFetcherTest, DontRetryOnNetworkChangedByDefault) { scoped_refptr context_getter( CreateSameThreadContextGetter()); - MockHostResolver* mock_resolver = - context_getter->GetURLRequestContext()->mock_resolver(); + // Force context creation. + context_getter->GetURLRequestContext(); + MockHostResolver* mock_resolver = context_getter->context()->mock_resolver(); WaitingURLFetcherDelegate delegate; delegate.CreateFetcher(hanging_url(), URLFetcher::GET, context_getter); @@ -517,8 +548,9 @@ TEST_F(URLFetcherTest, RetryOnNetworkChangedAndFail) { scoped_refptr context_getter( CreateSameThreadContextGetter()); - MockHostResolver* mock_resolver = - context_getter->GetURLRequestContext()->mock_resolver(); + // Force context creation. + context_getter->GetURLRequestContext(); + MockHostResolver* mock_resolver = context_getter->context()->mock_resolver(); WaitingURLFetcherDelegate delegate; delegate.CreateFetcher(hanging_url(), URLFetcher::GET, context_getter); @@ -563,8 +595,9 @@ TEST_F(URLFetcherTest, RetryOnNetworkChangedAndSucceed) { scoped_refptr context_getter( CreateSameThreadContextGetter()); - MockHostResolver* mock_resolver = - context_getter->GetURLRequestContext()->mock_resolver(); + // Force context creation. + context_getter->GetURLRequestContext(); + MockHostResolver* mock_resolver = context_getter->context()->mock_resolver(); WaitingURLFetcherDelegate delegate; delegate.CreateFetcher(hanging_url(), URLFetcher::GET, context_getter); @@ -1204,6 +1237,69 @@ TEST_F(URLFetcherTest, ReuseFetcherForSameURL) { EXPECT_EQ("request2", data); } +TEST_F(URLFetcherTest, ShutdownSameThread) { + scoped_refptr context_getter( + CreateSameThreadContextGetter()); + + // Create a fetcher and wait for it to create a request. + WaitingURLFetcherDelegate delegate1; + delegate1.CreateFetcher(hanging_url(), URLFetcher::GET, context_getter); + delegate1.fetcher()->Start(); + // Need to spin the loop to ensure the URLRequest is created and started. + base::RunLoop().RunUntilIdle(); + + // Create and start another fetcher, but don't wait for it to start. The task + // to start the request should be in the message loop. + WaitingURLFetcherDelegate delegate2; + delegate2.CreateFetcher(hanging_url(), URLFetcher::GET, context_getter); + delegate2.fetcher()->Start(); + + // Check that shutting down the getter cancels the request synchronously, + // allowing the context to be destroyed. + context_getter->Shutdown(); + + // Wait for the first fetcher, make sure it failed. + delegate1.WaitForComplete(); + EXPECT_FALSE(delegate1.fetcher()->GetStatus().is_success()); + EXPECT_EQ(ERR_CONTEXT_SHUT_DOWN, delegate1.fetcher()->GetStatus().error()); + + // Wait for the second fetcher, make sure it failed. + delegate2.WaitForComplete(); + EXPECT_FALSE(delegate2.fetcher()->GetStatus().is_success()); + EXPECT_EQ(ERR_CONTEXT_SHUT_DOWN, delegate2.fetcher()->GetStatus().error()); + + // New fetchers should automatically fail without making new requests. This + // should follow the same path as the second fetcher, but best to be safe. + WaitingURLFetcherDelegate delegate3; + delegate3.CreateFetcher(hanging_url(), URLFetcher::GET, context_getter); + delegate3.fetcher()->Start(); + delegate3.WaitForComplete(); + EXPECT_FALSE(delegate3.fetcher()->GetStatus().is_success()); + EXPECT_EQ(ERR_CONTEXT_SHUT_DOWN, delegate3.fetcher()->GetStatus().error()); +} + +TEST_F(URLFetcherTest, ShutdownCrossThread) { + scoped_refptr context_getter( + CreateCrossThreadContextGetter()); + + WaitingURLFetcherDelegate delegate1; + delegate1.CreateFetcher(hanging_url(), URLFetcher::GET, context_getter); + delegate1.fetcher()->Start(); + // Check that shutting the context getter lets the context be destroyed safely + // and cancels the request. + context_getter->Shutdown(); + delegate1.WaitForComplete(); + EXPECT_FALSE(delegate1.fetcher()->GetStatus().is_success()); + EXPECT_EQ(ERR_CONTEXT_SHUT_DOWN, delegate1.fetcher()->GetStatus().error()); + + // New requests should automatically fail without making new requests. + WaitingURLFetcherDelegate delegate2; + delegate2.CreateFetcher(hanging_url(), URLFetcher::GET, context_getter); + delegate2.StartFetcherAndWait(); + EXPECT_FALSE(delegate2.fetcher()->GetStatus().is_success()); + EXPECT_EQ(ERR_CONTEXT_SHUT_DOWN, delegate2.fetcher()->GetStatus().error()); +} + // Get a small file. TEST_F(URLFetcherTest, FileTestSmallGet) { const char kFileToFetch[] = "simple.html"; diff --git a/net/url_request/url_request_context_getter.cc b/net/url_request/url_request_context_getter.cc index d743885e399950..34d77809be5386 100644 --- a/net/url_request/url_request_context_getter.cc +++ b/net/url_request/url_request_context_getter.cc @@ -7,9 +7,22 @@ #include "base/location.h" #include "base/single_thread_task_runner.h" #include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_context_getter_observer.h" namespace net { +void URLRequestContextGetter::AddObserver( + URLRequestContextGetterObserver* observer) { + DCHECK(GetNetworkTaskRunner()->BelongsToCurrentThread()); + observer_list_.AddObserver(observer); +} + +void URLRequestContextGetter::RemoveObserver( + URLRequestContextGetterObserver* observer) { + DCHECK(GetNetworkTaskRunner()->BelongsToCurrentThread()); + observer_list_.RemoveObserver(observer); +} + URLRequestContextGetter::URLRequestContextGetter() {} URLRequestContextGetter::~URLRequestContextGetter() {} @@ -35,6 +48,16 @@ void URLRequestContextGetter::OnDestruct() const { // This is also true if the IO thread is gone. } +void URLRequestContextGetter::NotifyContextShuttingDown() { + DCHECK(GetNetworkTaskRunner()->BelongsToCurrentThread()); + + // Once shutdown starts, this must always return NULL. + DCHECK(!GetURLRequestContext()); + + FOR_EACH_OBSERVER(URLRequestContextGetterObserver, observer_list_, + OnContextShuttingDown()); +} + TrivialURLRequestContextGetter::TrivialURLRequestContextGetter( URLRequestContext* context, const scoped_refptr& main_task_runner) diff --git a/net/url_request/url_request_context_getter.h b/net/url_request/url_request_context_getter.h index 3f3a850d5a52a4..52cd341f970b52 100644 --- a/net/url_request/url_request_context_getter.h +++ b/net/url_request/url_request_context_getter.h @@ -5,7 +5,9 @@ #ifndef NET_URL_REQUEST_URL_REQUEST_CONTEXT_GETTER_H_ #define NET_URL_REQUEST_URL_REQUEST_CONTEXT_GETTER_H_ +#include "base/macros.h" #include "base/memory/ref_counted.h" +#include "base/observer_list.h" #include "base/sequenced_task_runner_helpers.h" #include "net/base/net_export.h" @@ -16,6 +18,7 @@ class SingleThreadTaskRunner; namespace net { class CookieStore; class URLRequestContext; +class URLRequestContextGetterObserver; struct URLRequestContextGetterTraits; @@ -24,6 +27,9 @@ class NET_EXPORT URLRequestContextGetter : public base::RefCountedThreadSafe { public: + // Returns the URLRequestContextGetter's URLRequestContext. Must only be + // called on the network task runner. Once NotifyContextShuttingDown() is + // invoked, must always return nullptr. virtual URLRequestContext* GetURLRequestContext() = 0; // Returns a SingleThreadTaskRunner corresponding to the thread on @@ -32,6 +38,12 @@ class NET_EXPORT URLRequestContextGetter virtual scoped_refptr GetNetworkTaskRunner() const = 0; + // Adds / removes an observer to watch for shutdown of |this|'s context. Must + // only be called on network thread. May not be called once + // GetURLRequestContext() starts returning nullptr. + void AddObserver(URLRequestContextGetterObserver* observer); + void RemoveObserver(URLRequestContextGetterObserver* observer); + protected: friend class base::RefCountedThreadSafe; @@ -41,10 +53,24 @@ class NET_EXPORT URLRequestContextGetter URLRequestContextGetter(); virtual ~URLRequestContextGetter(); + // Called to indicate the URLRequestContext is about to be shutdown, so + // observers need to abort any URLRequests they own. The implementation of + // this class is responsible for making sure this gets called. + // + // Must be called once and only once *before* context tear down begins, so any + // pending requests can be torn down safely. Right before calling this method, + // subclasses must ensure GetURLRequestContext returns nullptr, to protect + // against reentrancy. + void NotifyContextShuttingDown(); + private: - // OnDestruct is meant to ensure deletion on the thread on which the request + // OnDestruct is used to ensure deletion on the thread on which the request // IO happens. void OnDestruct() const; + + ObserverList observer_list_; + + DISALLOW_COPY_AND_ASSIGN(URLRequestContextGetter); }; struct URLRequestContextGetterTraits { @@ -56,10 +82,10 @@ struct URLRequestContextGetterTraits { // For use in shimming a URLRequestContext into a URLRequestContextGetter. class NET_EXPORT TrivialURLRequestContextGetter : public URLRequestContextGetter { -public: - TrivialURLRequestContextGetter( - URLRequestContext* context, - const scoped_refptr& main_task_runner); + public: + TrivialURLRequestContextGetter( + URLRequestContext* context, + const scoped_refptr& main_task_runner); // URLRequestContextGetter implementation: URLRequestContext* GetURLRequestContext() override; @@ -67,10 +93,10 @@ class NET_EXPORT TrivialURLRequestContextGetter scoped_refptr GetNetworkTaskRunner() const override; -private: - ~TrivialURLRequestContextGetter() override; + private: + ~TrivialURLRequestContextGetter() override; - URLRequestContext* context_; + URLRequestContext* context_; const scoped_refptr main_task_runner_; DISALLOW_COPY_AND_ASSIGN(TrivialURLRequestContextGetter); diff --git a/net/url_request/url_request_context_getter_observer.h b/net/url_request/url_request_context_getter_observer.h new file mode 100644 index 00000000000000..53cd6dc2c64476 --- /dev/null +++ b/net/url_request/url_request_context_getter_observer.h @@ -0,0 +1,35 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_URL_REQUEST_URL_REQUEST_CONTEXT_GETTER_OBSERVER_H_ +#define NET_URL_REQUEST_URL_REQUEST_CONTEXT_GETTER_OBSERVER_H_ + +#include "base/macros.h" +#include "net/base/net_export.h" + +namespace net { +class URLRequestContextGetter; + +// Interface for watching when a URLRequestContext associated with a +// URLRequestContextGetter is shutting down. +class NET_EXPORT URLRequestContextGetterObserver { + public: + URLRequestContextGetterObserver() {} + + // Called before the URLRequestContext shuts down. When called, the Getter's + // GetURLRequestContext method must return NULL to protected against + // re-entrancy, but the Context must still be valid and GetNetworkTaskRunner() + // must return the thread it lives on. Called on the network thread. + virtual void OnContextShuttingDown() = 0; + + protected: + virtual ~URLRequestContextGetterObserver() {} + + private: + DISALLOW_COPY_AND_ASSIGN(URLRequestContextGetterObserver); +}; + +} // namespace net + +#endif // NET_URL_REQUEST_URL_REQUEST_CONTEXT_GETTER_OBSERVER_H_