summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjoth@chromium.org <joth@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-10-21 11:21:23 +0000
committerjoth@chromium.org <joth@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-10-21 11:21:23 +0000
commit23b9fbef6262820af93c1312674d94715b2c4baf (patch)
tree31cfb0c3c7a3a65f60ff0be6a96e21cd49e35dc8
parentd9adf11dd209e90a880833de750a253d4d6621eb (diff)
downloadchromium_src-23b9fbef6262820af93c1312674d94715b2c4baf.zip
chromium_src-23b9fbef6262820af93c1312674d94715b2c4baf.tar.gz
chromium_src-23b9fbef6262820af93c1312674d94715b2c4baf.tar.bz2
Fix bug 59917 - Network position cache can get time clashes
Now use a lru list for cache eviction. Also adds a test for the cache, and makes the cache public for testing. BUG=59917 TEST=GeolocationNetworkProviderTest.NetworkPositionCache Review URL: http://codereview.chromium.org/3954001 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@63351 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/geolocation/network_location_provider.cc174
-rw-r--r--chrome/browser/geolocation/network_location_provider.h41
-rw-r--r--chrome/browser/geolocation/network_location_provider_unittest.cc48
3 files changed, 164 insertions, 99 deletions
diff --git a/chrome/browser/geolocation/network_location_provider.cc b/chrome/browser/geolocation/network_location_provider.cc
index 1f77fbc..cb3fbf5 100644
--- a/chrome/browser/geolocation/network_location_provider.cc
+++ b/chrome/browser/geolocation/network_location_provider.cc
@@ -12,103 +12,92 @@ namespace {
// The maximum period of time we'll wait for a complete set of device data
// before sending the request.
const int kDataCompleteWaitPeriod = 1000 * 2; // 2 seconds
-
-// The maximum size of the cache of positions for previously requested device
-// data.
-const size_t kMaximumCacheSize = 10;
} // namespace
-// The PositionCache handles caching and retrieving a position returned by a
-// network location provider. It is not thread safe. It's methods are called on
-// multiple threads by NetworkLocationProvider, but the timing is such that
-// thread safety is not required.
-class NetworkLocationProvider::PositionCache {
- public:
- // Caches the current position response for the current set of cell ID and
- // WiFi data. Returns true on success, false otherwise.
- bool CachePosition(const GatewayData& gateway_data,
- const RadioData& radio_data,
- const WifiData& wifi_data,
- const Geoposition& position) {
- // Check that we can generate a valid key for the device data.
- string16 key;
- if (!MakeKey(gateway_data, radio_data, wifi_data, &key)) {
- return false;
- }
- // If the cache is full, remove the oldest entry.
- if (cache_.size() == kMaximumCacheSize) {
- DCHECK(cache_times_.size() == kMaximumCacheSize);
- CacheTimesMap::iterator oldest_entry = cache_times_.begin();
- DCHECK(oldest_entry != cache_times_.end());
- cache_.erase(oldest_entry->second);
- cache_times_.erase(oldest_entry);
- }
- // Insert the position into the cache.
- std::pair<CacheMap::iterator, bool> result =
- cache_.insert(std::make_pair(key, position));
- DCHECK(result.second);
- cache_times_[position.timestamp] = result.first;
- DCHECK(cache_.size() == cache_times_.size());
- return true;
- }
+// static
+const size_t NetworkLocationProvider::PositionCache::kMaximumSize = 10;
- // Searches for a cached position response for the current set of cell ID and
- // WiFi data. Returns the cached position if available, NULL otherwise.
- const Geoposition *FindPosition(const GatewayData &gateway_data,
- const RadioData &radio_data,
- const WifiData &wifi_data) {
- string16 key;
- if (!MakeKey(gateway_data, radio_data, wifi_data, &key)) {
- return NULL;
- }
- CacheMap::const_iterator iter = cache_.find(key);
- return iter == cache_.end() ? NULL : &iter->second;
+bool NetworkLocationProvider::PositionCache::CachePosition(
+ const GatewayData& gateway_data,
+ const WifiData& wifi_data,
+ const Geoposition& position) {
+ // Check that we can generate a valid key for the device data.
+ string16 key;
+ if (!MakeKey(gateway_data, wifi_data, &key)) {
+ return false;
}
+ // If the cache is full, remove the oldest entry.
+ if (cache_.size() == kMaximumSize) {
+ DCHECK(cache_age_list_.size() == kMaximumSize);
+ CacheAgeList::iterator oldest_entry = cache_age_list_.begin();
+ DCHECK(oldest_entry != cache_age_list_.end());
+ cache_.erase(*oldest_entry);
+ cache_age_list_.erase(oldest_entry);
+ }
+ DCHECK_LT(cache_.size(), kMaximumSize);
+ // Insert the position into the cache.
+ std::pair<CacheMap::iterator, bool> result =
+ cache_.insert(std::make_pair(key, position));
+ if (!result.second) {
+ NOTREACHED(); // We never try to add the same key twice.
+ CHECK_EQ(cache_.size(), cache_age_list_.size());
+ return false;
+ }
+ cache_age_list_.push_back(result.first);
+ DCHECK_EQ(cache_.size(), cache_age_list_.size());
+ return true;
+}
- // Makes the key for the map of cached positions, using a set of
- // device data. Returns true if a good key was generated, false otherwise.
- static bool MakeKey(const GatewayData& gateway_data,
- const RadioData& /*radio_data*/,
- const WifiData& wifi_data,
- string16* key) {
- // Currently we use only the WiFi data and gateway data, and base the
- // key only on the MAC addresses.
- // TODO(steveblock): Make use of radio_data.
- DCHECK(key);
- key->clear();
- key->reserve(wifi_data.access_point_data.size() * 19 +
- gateway_data.router_data.size() * 19);
- const string16 separator(ASCIIToUTF16("|"));
- for (GatewayData::RouterDataSet::const_iterator iter =
- gateway_data.router_data.begin();
- iter != gateway_data.router_data.end();
- iter++) {
- *key += separator;
- *key += iter->mac_address;
- *key += separator;
- }
- for (WifiData::AccessPointDataSet::const_iterator iter =
- wifi_data.access_point_data.begin();
- iter != wifi_data.access_point_data.end();
- iter++) {
- *key += separator;
- *key += iter->mac_address;
- *key += separator;
- }
- // If the key is the empty string, return false, as we don't want to cache a
- // position for such a set of device data.
- return !key->empty();
+// Searches for a cached position response for the current set of cell ID and
+// WiFi data. Returns the cached position if available, NULL otherwise.
+const Geoposition* NetworkLocationProvider::PositionCache::FindPosition(
+ const GatewayData& gateway_data,
+ const WifiData& wifi_data) {
+ string16 key;
+ if (!MakeKey(gateway_data, wifi_data, &key)) {
+ return NULL;
}
+ CacheMap::const_iterator iter = cache_.find(key);
+ return iter == cache_.end() ? NULL : &iter->second;
+}
- private:
- // The cache of positions. This is stored using two maps. One map is keyed on
- // a string that represents a set of device data, the other is keyed on the
- // timestamp of the position.
- typedef std::map<string16, Geoposition> CacheMap;
- CacheMap cache_;
- typedef std::map<base::Time, CacheMap::iterator> CacheTimesMap;
- CacheTimesMap cache_times_;
-};
+// Makes the key for the map of cached positions, using a set of
+// device data. Returns true if a good key was generated, false otherwise.
+//
+// static
+bool NetworkLocationProvider::PositionCache::MakeKey(
+ const GatewayData& gateway_data,
+ const WifiData& wifi_data,
+ string16* key) {
+ // Currently we use only the WiFi data and gateway data, and base the
+ // key only on the MAC addresses.
+ // TODO(joth): Make use of radio_data.
+ DCHECK(key);
+ key->clear();
+ const size_t kCharsPerMacAddress = 6 * 3 + 1; // e.g. "11:22:33:44:55:66|"
+ key->reserve(wifi_data.access_point_data.size() * kCharsPerMacAddress
+ + gateway_data.router_data.size() * kCharsPerMacAddress);
+ const string16 separator(ASCIIToUTF16("|"));
+ for (GatewayData::RouterDataSet::const_iterator iter =
+ gateway_data.router_data.begin();
+ iter != gateway_data.router_data.end();
+ iter++) {
+ *key += separator;
+ *key += iter->mac_address;
+ *key += separator;
+ }
+ for (WifiData::AccessPointDataSet::const_iterator iter =
+ wifi_data.access_point_data.begin();
+ iter != wifi_data.access_point_data.end();
+ iter++) {
+ *key += separator;
+ *key += iter->mac_address;
+ *key += separator;
+ }
+ // If the key is the empty string, return false, as we don't want to cache a
+ // position for such a set of device data.
+ return !key->empty();
+}
// NetworkLocationProvider factory function
LocationProviderBase* NewNetworkLocationProvider(
@@ -207,8 +196,7 @@ void NetworkLocationProvider::LocationResponseAvailable(
// Record the position and update our cache.
position_ = position;
if (position.IsValidFix()) {
- position_cache_->CachePosition(gateway_data, radio_data,
- wifi_data, position);
+ position_cache_->CachePosition(gateway_data, wifi_data, position);
}
// Record access_token if it's set.
@@ -273,7 +261,7 @@ void NetworkLocationProvider::RequestPosition() {
return;
const Geoposition* cached_position =
- position_cache_->FindPosition(gateway_data_, radio_data_, wifi_data_);
+ position_cache_->FindPosition(gateway_data_, wifi_data_);
DCHECK(!device_data_updated_timestamp_.is_null()) <<
"Timestamp must be set before looking up position";
if (cached_position) {
diff --git a/chrome/browser/geolocation/network_location_provider.h b/chrome/browser/geolocation/network_location_provider.h
index e86f93c..47267f2 100644
--- a/chrome/browser/geolocation/network_location_provider.h
+++ b/chrome/browser/geolocation/network_location_provider.h
@@ -6,6 +6,7 @@
#define CHROME_BROWSER_GEOLOCATION_NETWORK_LOCATION_PROVIDER_H_
#pragma once
+#include <list>
#include <string>
#include "base/basictypes.h"
@@ -27,6 +28,43 @@ class NetworkLocationProvider
public WifiDataProvider::ListenerInterface,
public NetworkLocationRequest::ListenerInterface {
public:
+ // Cache of recently resolved locations. Public for tests.
+ class PositionCache {
+ public:
+ // The maximum size of the cache of positions for previously requested
+ // device data.
+ static const size_t kMaximumSize;
+
+ // Caches the current position response for the current set of cell ID and
+ // WiFi data. In the case of the cache exceeding kMaximumSize this will
+ // evict old entries in FIFO orderer of being added.
+ // Returns true on success, false otherwise.
+ bool CachePosition(const GatewayData& gateway_data,
+ const WifiData& wifi_data,
+ const Geoposition& position);
+
+ // Searches for a cached position response for the current set of device
+ // data. Returns NULL if the position is not in the cache, or the cached
+ // position if available. Ownership remains with the cache.
+ const Geoposition* FindPosition(const GatewayData& gateway_data,
+ const WifiData& wifi_data);
+
+ private:
+ // Makes the key for the map of cached positions, using a set of
+ // device data. Returns true if a good key was generated, false otherwise.
+ static bool MakeKey(const GatewayData& gateway_data,
+ const WifiData& wifi_data,
+ string16* key);
+
+ // The cache of positions. This is stored as a map keyed on a string that
+ // represents a set of device data, and a list to provide
+ // least-recently-added eviction.
+ typedef std::map<string16, Geoposition> CacheMap;
+ CacheMap cache_;
+ typedef std::list<CacheMap::iterator> CacheAgeList;
+ CacheAgeList cache_age_list_; // Oldest first.
+ };
+
NetworkLocationProvider(AccessTokenStore* access_token_store,
URLRequestContextGetter* context,
const GURL& url,
@@ -41,9 +79,6 @@ class NetworkLocationProvider
virtual void OnPermissionGranted(const GURL& requesting_frame);
private:
- // PositionCache is an implementation detail of NetworkLocationProvider.
- class PositionCache;
-
// Satisfies a position request from cache or network.
void RequestPosition();
diff --git a/chrome/browser/geolocation/network_location_provider_unittest.cc b/chrome/browser/geolocation/network_location_provider_unittest.cc
index c0e2023..9bef39c 100644
--- a/chrome/browser/geolocation/network_location_provider_unittest.cc
+++ b/chrome/browser/geolocation/network_location_provider_unittest.cc
@@ -14,9 +14,13 @@
#include "testing/gtest/include/gtest/gtest.h"
namespace {
+// Constants used in multiple tests.
const char kTestServerUrl[] = "https://www.geolocation.test/service";
const char kTestHost[] = "myclienthost.test";
const char kTestHostUrl[] = "http://myclienthost.test/some/path";
+// Using #define so we can easily paste this into various other strings.
+#define REFERENCE_ACCESS_TOKEN "2:k7j3G6LaL6u_lafw:4iXOeOpTh1glSXe"
+
} // namespace
// Stops the specified (nested) message loop when the listener is called back.
@@ -125,6 +129,7 @@ class GeolocationNetworkProviderTest : public testing::Test {
virtual void TearDown() {
WifiDataProvider::ResetFactory();
RadioDataProvider::ResetFactory();
+ GatewayDataProvider::ResetFactory();
URLFetcher::set_factory(NULL);
}
@@ -192,6 +197,15 @@ class GeolocationNetworkProviderTest : public testing::Test {
return data;
}
+ static Geoposition CreateReferencePosition(int id) {
+ Geoposition pos;
+ pos.latitude = id;
+ pos.longitude = -(id + 1);
+ pos.altitude = 2 * id;
+ pos.timestamp = base::Time::Now();
+ return pos;
+ }
+
static void ParseGatewayRequest(const std::string& request_data,
GatewayData* gateway_data_out) {
scoped_ptr<Value> value(base::JSONReader::Read(request_data, false));
@@ -412,9 +426,7 @@ TEST_F(GeolocationNetworkProviderTest, MultipleWifiScansComplete) {
TestURLFetcher* fetcher = get_url_fetcher_and_advance_id();
ASSERT_TRUE(fetcher != NULL);
CheckEmptyRequestIsValid(fetcher->upload_data());
- // Complete the network request with bad position fix (using #define so we
- // can paste this into various other strings below)
- #define REFERENCE_ACCESS_TOKEN "2:k7j3G6LaL6u_lafw:4iXOeOpTh1glSXe"
+ // Complete the network request with bad position fix.
const char* kNoFixNetworkResponse =
"{"
" \"location\": null,"
@@ -737,3 +749,33 @@ TEST_F(GeolocationNetworkProviderTest,
CheckRequestIsValid(fetcher->upload_data(), 0,
kScanCount, REFERENCE_ACCESS_TOKEN);
}
+
+TEST_F(GeolocationNetworkProviderTest, NetworkPositionCache) {
+ NetworkLocationProvider::PositionCache cache;
+
+ const int kCacheSize = NetworkLocationProvider::PositionCache::kMaximumSize;
+ for (int i = 0; i < kCacheSize * 2 + 1; ++i) {
+ Geoposition pos = CreateReferencePosition(i);
+ bool ret = cache.CachePosition(CreateReferenceRouterData(2),
+ CreateReferenceWifiScanData(i), pos);
+ EXPECT_TRUE(ret) << i;
+ const Geoposition* item = cache.FindPosition(
+ CreateReferenceRouterData(2),
+ CreateReferenceWifiScanData(i));
+ ASSERT_TRUE(item) << i;
+ EXPECT_EQ(pos.latitude, item->latitude) << i;
+ EXPECT_EQ(pos.longitude, item->longitude) << i;
+ if (i < kCacheSize) {
+ // Nothing should have spilled yet; check oldest item is still there.
+ EXPECT_TRUE(cache.FindPosition(CreateReferenceRouterData(2),
+ CreateReferenceWifiScanData(0)));
+ } else {
+ const int evicted = i - kCacheSize;
+ EXPECT_FALSE(cache.FindPosition(CreateReferenceRouterData(2),
+ CreateReferenceWifiScanData(evicted)));
+ EXPECT_TRUE(cache.FindPosition(CreateReferenceRouterData(2),
+ CreateReferenceWifiScanData(evicted + 1)));
+ }
+ }
+}
+