| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "net/disk_cache/memory/mem_backend_impl.h" | 5 #include "net/disk_cache/memory/mem_backend_impl.h" |
| 6 | 6 |
| 7 #include <algorithm> |
| 8 #include <functional> |
| 7 #include <utility> | 9 #include <utility> |
| 8 | 10 |
| 9 #include "base/logging.h" | 11 #include "base/logging.h" |
| 10 #include "base/sys_info.h" | 12 #include "base/sys_info.h" |
| 11 #include "net/base/net_errors.h" | 13 #include "net/base/net_errors.h" |
| 12 #include "net/disk_cache/cache_util.h" | 14 #include "net/disk_cache/cache_util.h" |
| 13 #include "net/disk_cache/memory/mem_entry_impl.h" | 15 #include "net/disk_cache/memory/mem_entry_impl.h" |
| 14 | 16 |
| 15 using base::Time; | 17 using base::Time; |
| 16 | 18 |
| 19 namespace disk_cache { |
| 20 |
| 17 namespace { | 21 namespace { |
| 18 | 22 |
| 19 const int kDefaultInMemoryCacheSize = 10 * 1024 * 1024; | 23 const int kDefaultInMemoryCacheSize = 10 * 1024 * 1024; |
| 20 const int kCleanUpMargin = 1024 * 1024; | 24 const int kDefaultEvictionSize = kDefaultInMemoryCacheSize / 10; |
| 21 | 25 |
| 22 int LowWaterAdjust(int high_water) { | 26 bool CheckLRUListOrder(const base::LinkedList<MemEntryImpl>& lru_list) { |
| 23 if (high_water < kCleanUpMargin) | 27 // TODO(gavinp): Check MemBackendImpl::current_size_ here as well. |
| 24 return 0; | 28 base::Time previous_last_use_time; |
| 25 | 29 for (base::LinkNode<MemEntryImpl>* node = lru_list.head(); |
| 26 return high_water - kCleanUpMargin; | 30 node != lru_list.end(); node = node->next()) { |
| 31 if (node->value()->GetLastUsed() < previous_last_use_time) |
| 32 return false; |
| 33 previous_last_use_time = node->value()->GetLastUsed(); |
| 34 } |
| 35 return true; |
| 27 } | 36 } |
| 28 | 37 |
| 29 } // namespace | 38 } // namespace |
| 30 | 39 |
| 31 namespace disk_cache { | |
| 32 | |
| 33 MemBackendImpl::MemBackendImpl(net::NetLog* net_log) | 40 MemBackendImpl::MemBackendImpl(net::NetLog* net_log) |
| 34 : max_size_(0), current_size_(0), net_log_(net_log), weak_factory_(this) { | 41 : max_size_(0), current_size_(0), net_log_(net_log), weak_factory_(this) { |
| 35 } | 42 } |
| 36 | 43 |
| 37 MemBackendImpl::~MemBackendImpl() { | 44 MemBackendImpl::~MemBackendImpl() { |
| 38 EntryMap::iterator it = entries_.begin(); | 45 DCHECK(CheckLRUListOrder(lru_list_)); |
| 39 while (it != entries_.end()) { | 46 while (!entries_.empty()) |
| 40 it->second->Doom(); | 47 entries_.begin()->second->Doom(); |
| 41 it = entries_.begin(); | |
| 42 } | |
| 43 DCHECK(!current_size_); | 48 DCHECK(!current_size_); |
| 44 } | 49 } |
| 45 | 50 |
| 46 // Static. | 51 // static |
| 47 scoped_ptr<Backend> MemBackendImpl::CreateBackend(int max_bytes, | 52 scoped_ptr<Backend> MemBackendImpl::CreateBackend(int max_bytes, |
| 48 net::NetLog* net_log) { | 53 net::NetLog* net_log) { |
| 49 scoped_ptr<MemBackendImpl> cache(new MemBackendImpl(net_log)); | 54 scoped_ptr<MemBackendImpl> cache(new MemBackendImpl(net_log)); |
| 50 cache->SetMaxSize(max_bytes); | 55 cache->SetMaxSize(max_bytes); |
| 51 if (cache->Init()) | 56 if (cache->Init()) |
| 52 return std::move(cache); | 57 return std::move(cache); |
| 53 | 58 |
| 54 LOG(ERROR) << "Unable to create cache"; | 59 LOG(ERROR) << "Unable to create cache"; |
| 55 return nullptr; | 60 return nullptr; |
| 56 } | 61 } |
| 57 | 62 |
| 58 bool MemBackendImpl::Init() { | 63 bool MemBackendImpl::Init() { |
| 59 if (max_size_) | 64 if (max_size_) |
| 60 return true; | 65 return true; |
| 61 | 66 |
| 62 int64_t total_memory = base::SysInfo::AmountOfPhysicalMemory(); | 67 int64_t total_memory = base::SysInfo::AmountOfPhysicalMemory(); |
| 63 | 68 |
| 64 if (total_memory <= 0) { | 69 if (total_memory <= 0) { |
| 65 max_size_ = kDefaultInMemoryCacheSize; | 70 max_size_ = kDefaultInMemoryCacheSize; |
| 66 return true; | 71 return true; |
| 67 } | 72 } |
| 68 | 73 |
| 69 // We want to use up to 2% of the computer's memory, with a limit of 50 MB, | 74 // We want to use up to 2% of the computer's memory, with a limit of 50 MB, |
| 70 // reached on systemd with more than 2.5 GB of RAM. | 75 // reached on system with more than 2.5 GB of RAM. |
| 71 total_memory = total_memory * 2 / 100; | 76 total_memory = total_memory * 2 / 100; |
| 72 if (total_memory > kDefaultInMemoryCacheSize * 5) | 77 if (total_memory > kDefaultInMemoryCacheSize * 5) |
| 73 max_size_ = kDefaultInMemoryCacheSize * 5; | 78 max_size_ = kDefaultInMemoryCacheSize * 5; |
| 74 else | 79 else |
| 75 max_size_ = static_cast<int32_t>(total_memory); | 80 max_size_ = static_cast<int32_t>(total_memory); |
| 76 | 81 |
| 77 return true; | 82 return true; |
| 78 } | 83 } |
| 79 | 84 |
| 80 bool MemBackendImpl::SetMaxSize(int max_bytes) { | 85 bool MemBackendImpl::SetMaxSize(int max_bytes) { |
| 81 static_assert(sizeof(max_bytes) == sizeof(max_size_), | 86 static_assert(sizeof(max_bytes) == sizeof(max_size_), |
| 82 "unsupported int model"); | 87 "unsupported int model"); |
| 83 if (max_bytes < 0) | 88 if (max_bytes < 0) |
| 84 return false; | 89 return false; |
| 85 | 90 |
| 86 // Zero size means use the default. | 91 // Zero size means use the default. |
| 87 if (!max_bytes) | 92 if (!max_bytes) |
| 88 return true; | 93 return true; |
| 89 | 94 |
| 90 max_size_ = max_bytes; | 95 max_size_ = max_bytes; |
| 91 return true; | 96 return true; |
| 92 } | 97 } |
| 93 | 98 |
| 94 void MemBackendImpl::InternalDoomEntry(MemEntryImpl* entry) { | |
| 95 // Only parent entries can be passed into this method. | |
| 96 DCHECK(entry->type() == MemEntryImpl::kParentEntry); | |
| 97 | |
| 98 rankings_.Remove(entry); | |
| 99 EntryMap::iterator it = entries_.find(entry->GetKey()); | |
| 100 if (it != entries_.end()) | |
| 101 entries_.erase(it); | |
| 102 else | |
| 103 NOTREACHED(); | |
| 104 | |
| 105 entry->InternalDoom(); | |
| 106 } | |
| 107 | |
| 108 void MemBackendImpl::UpdateRank(MemEntryImpl* node) { | |
| 109 rankings_.UpdateRank(node); | |
| 110 } | |
| 111 | |
| 112 void MemBackendImpl::ModifyStorageSize(int32_t old_size, int32_t new_size) { | |
| 113 if (old_size >= new_size) | |
| 114 SubstractStorageSize(old_size - new_size); | |
| 115 else | |
| 116 AddStorageSize(new_size - old_size); | |
| 117 } | |
| 118 | |
| 119 int MemBackendImpl::MaxFileSize() const { | 99 int MemBackendImpl::MaxFileSize() const { |
| 120 return max_size_ / 8; | 100 return max_size_ / 8; |
| 121 } | 101 } |
| 122 | 102 |
| 123 void MemBackendImpl::InsertIntoRankingList(MemEntryImpl* entry) { | 103 void MemBackendImpl::OnEntryInserted(MemEntryImpl* entry) { |
| 124 rankings_.Insert(entry); | 104 lru_list_.Append(entry); |
| 125 } | 105 } |
| 126 | 106 |
| 127 void MemBackendImpl::RemoveFromRankingList(MemEntryImpl* entry) { | 107 void MemBackendImpl::OnEntryUpdated(MemEntryImpl* entry) { |
| 128 rankings_.Remove(entry); | 108 DCHECK(CheckLRUListOrder(lru_list_)); |
| 109 // LinkedList<>::RemoveFromList() removes |entry| from |lru_list_|. |
| 110 entry->RemoveFromList(); |
| 111 lru_list_.Append(entry); |
| 112 } |
| 113 |
| 114 void MemBackendImpl::OnEntryDoomed(MemEntryImpl* entry) { |
| 115 DCHECK(CheckLRUListOrder(lru_list_)); |
| 116 if (entry->type() == MemEntryImpl::PARENT_ENTRY) |
| 117 entries_.erase(entry->key()); |
| 118 // LinkedList<>::RemoveFromList() removes |entry| from |lru_list_|. |
| 119 entry->RemoveFromList(); |
| 120 } |
| 121 |
| 122 void MemBackendImpl::ModifyStorageSize(int32_t delta) { |
| 123 current_size_ += delta; |
| 124 if (delta > 0) |
| 125 EvictIfNeeded(); |
| 129 } | 126 } |
| 130 | 127 |
| 131 net::CacheType MemBackendImpl::GetCacheType() const { | 128 net::CacheType MemBackendImpl::GetCacheType() const { |
| 132 return net::MEMORY_CACHE; | 129 return net::MEMORY_CACHE; |
| 133 } | 130 } |
| 134 | 131 |
| 135 int32_t MemBackendImpl::GetEntryCount() const { | 132 int32_t MemBackendImpl::GetEntryCount() const { |
| 136 return static_cast<int32_t>(entries_.size()); | 133 return static_cast<int32_t>(entries_.size()); |
| 137 } | 134 } |
| 138 | 135 |
| 139 int MemBackendImpl::OpenEntry(const std::string& key, Entry** entry, | 136 int MemBackendImpl::OpenEntry(const std::string& key, |
| 137 Entry** entry, |
| 140 const CompletionCallback& callback) { | 138 const CompletionCallback& callback) { |
| 141 if (OpenEntry(key, entry)) | 139 EntryMap::iterator it = entries_.find(key); |
| 142 return net::OK; | 140 if (it == entries_.end()) |
| 141 return net::ERR_FAILED; |
| 143 | 142 |
| 144 return net::ERR_FAILED; | 143 it->second->Open(); |
| 144 |
| 145 *entry = it->second; |
| 146 return net::OK; |
| 145 } | 147 } |
| 146 | 148 |
| 147 int MemBackendImpl::CreateEntry(const std::string& key, Entry** entry, | 149 int MemBackendImpl::CreateEntry(const std::string& key, |
| 150 Entry** entry, |
| 148 const CompletionCallback& callback) { | 151 const CompletionCallback& callback) { |
| 149 if (CreateEntry(key, entry)) | 152 std::pair<EntryMap::iterator, bool> create_result = |
| 150 return net::OK; | 153 entries_.insert(EntryMap::value_type(key, nullptr)); |
| 154 const bool did_insert = create_result.second; |
| 155 if (!did_insert) |
| 156 return net::ERR_FAILED; |
| 151 | 157 |
| 152 return net::ERR_FAILED; | 158 MemEntryImpl* cache_entry = new MemEntryImpl(this, key, net_log_); |
| 159 create_result.first->second = cache_entry; |
| 160 *entry = cache_entry; |
| 161 return net::OK; |
| 153 } | 162 } |
| 154 | 163 |
| 155 int MemBackendImpl::DoomEntry(const std::string& key, | 164 int MemBackendImpl::DoomEntry(const std::string& key, |
| 156 const CompletionCallback& callback) { | 165 const CompletionCallback& callback) { |
| 157 if (DoomEntry(key)) | 166 EntryMap::iterator it = entries_.find(key); |
| 158 return net::OK; | 167 if (it == entries_.end()) |
| 168 return net::ERR_FAILED; |
| 159 | 169 |
| 160 return net::ERR_FAILED; | 170 it->second->Doom(); |
| 171 return net::OK; |
| 161 } | 172 } |
| 162 | 173 |
| 163 int MemBackendImpl::DoomAllEntries(const CompletionCallback& callback) { | 174 int MemBackendImpl::DoomAllEntries(const CompletionCallback& callback) { |
| 164 if (DoomAllEntries()) | 175 return DoomEntriesBetween(Time(), Time(), callback); |
| 165 return net::OK; | |
| 166 | |
| 167 return net::ERR_FAILED; | |
| 168 } | 176 } |
| 169 | 177 |
| 170 int MemBackendImpl::DoomEntriesBetween(const base::Time initial_time, | 178 int MemBackendImpl::DoomEntriesBetween(Time initial_time, |
| 171 const base::Time end_time, | 179 Time end_time, |
| 172 const CompletionCallback& callback) { | 180 const CompletionCallback& callback) { |
| 173 if (DoomEntriesBetween(initial_time, end_time)) | 181 if (end_time.is_null()) |
| 174 return net::OK; | 182 end_time = Time::Max(); |
| 175 | 183 |
| 176 return net::ERR_FAILED; | 184 DCHECK_GE(end_time, initial_time); |
| 185 |
| 186 base::LinkNode<MemEntryImpl>* node = lru_list_.head(); |
| 187 while (node != lru_list_.end() && node->value()->GetLastUsed() < initial_time) |
| 188 node = node->next(); |
| 189 while (node != lru_list_.end() && node->value()->GetLastUsed() < end_time) { |
| 190 MemEntryImpl* to_doom = node->value(); |
| 191 node = node->next(); |
| 192 to_doom->Doom(); |
| 193 } |
| 194 |
| 195 return net::OK; |
| 177 } | 196 } |
| 178 | 197 |
| 179 int MemBackendImpl::DoomEntriesSince(const base::Time initial_time, | 198 int MemBackendImpl::DoomEntriesSince(Time initial_time, |
| 180 const CompletionCallback& callback) { | 199 const CompletionCallback& callback) { |
| 181 if (DoomEntriesSince(initial_time)) | 200 return DoomEntriesBetween(initial_time, Time::Max(), callback); |
| 182 return net::OK; | |
| 183 | |
| 184 return net::ERR_FAILED; | |
| 185 } | 201 } |
| 186 | 202 |
| 187 int MemBackendImpl::CalculateSizeOfAllEntries( | 203 int MemBackendImpl::CalculateSizeOfAllEntries( |
| 188 const CompletionCallback& callback) { | 204 const CompletionCallback& callback) { |
| 189 return current_size_; | 205 return current_size_; |
| 190 } | 206 } |
| 191 | 207 |
| 192 class MemBackendImpl::MemIterator : public Backend::Iterator { | 208 class MemBackendImpl::MemIterator final : public Backend::Iterator { |
| 193 public: | 209 public: |
| 194 explicit MemIterator(base::WeakPtr<MemBackendImpl> backend) | 210 explicit MemIterator(base::WeakPtr<MemBackendImpl> backend) |
| 195 : backend_(backend), current_(NULL) { | 211 : backend_(backend), current_(nullptr) {} |
| 196 } | |
| 197 | 212 |
| 198 int OpenNextEntry(Entry** next_entry, | 213 int OpenNextEntry(Entry** next_entry, |
| 199 const CompletionCallback& callback) override { | 214 const CompletionCallback& callback) override { |
| 200 if (!backend_) | 215 if (!backend_) |
| 201 return net::ERR_FAILED; | 216 return net::ERR_FAILED; |
| 202 | 217 |
| 203 MemEntryImpl* node = backend_->rankings_.GetNext(current_); | 218 // Iterate using |lru_list_|, from most recently used to least recently |
| 219 // used, for compatibility with the unit tests that assume this behaviour. |
| 220 // Consider the last element if we are beginning an iteration, otherwise |
| 221 // progressively move earlier in the LRU list. |
| 222 current_ = current_ ? current_->previous() : backend_->lru_list_.tail(); |
| 223 |
| 204 // We should never return a child entry so iterate until we hit a parent | 224 // We should never return a child entry so iterate until we hit a parent |
| 205 // entry. | 225 // entry. |
| 206 while (node && node->type() != MemEntryImpl::kParentEntry) | 226 while (current_ != backend_->lru_list_.end() && |
| 207 node = backend_->rankings_.GetNext(node); | 227 current_->value()->type() != MemEntryImpl::PARENT_ENTRY) { |
| 208 *next_entry = node; | 228 current_ = current_->previous(); |
| 209 current_ = node; | 229 } |
| 230 if (current_ == backend_->lru_list_.end()) { |
| 231 *next_entry = nullptr; |
| 232 return net::ERR_FAILED; |
| 233 } |
| 210 | 234 |
| 211 if (node) { | 235 current_->value()->Open(); |
| 212 node->Open(); | 236 *next_entry = current_->value(); |
| 213 return net::OK; | 237 return net::OK; |
| 214 } | |
| 215 return net::ERR_FAILED; | |
| 216 } | 238 } |
| 217 | 239 |
| 218 private: | 240 private: |
| 219 base::WeakPtr<MemBackendImpl> backend_; | 241 base::WeakPtr<MemBackendImpl> backend_; |
| 220 MemEntryImpl* current_; | 242 base::LinkNode<MemEntryImpl>* current_; |
| 221 }; | 243 }; |
| 222 | 244 |
| 223 scoped_ptr<Backend::Iterator> MemBackendImpl::CreateIterator() { | 245 scoped_ptr<Backend::Iterator> MemBackendImpl::CreateIterator() { |
| 224 return scoped_ptr<Backend::Iterator>( | 246 return scoped_ptr<Backend::Iterator>( |
| 225 new MemIterator(weak_factory_.GetWeakPtr())); | 247 new MemIterator(weak_factory_.GetWeakPtr())); |
| 226 } | 248 } |
| 227 | 249 |
| 228 void MemBackendImpl::OnExternalCacheHit(const std::string& key) { | 250 void MemBackendImpl::OnExternalCacheHit(const std::string& key) { |
| 229 EntryMap::iterator it = entries_.find(key); | 251 EntryMap::iterator it = entries_.find(key); |
| 230 if (it != entries_.end()) { | 252 if (it != entries_.end()) |
| 231 UpdateRank(it->second); | 253 it->second->UpdateStateOnUse(MemEntryImpl::ENTRY_WAS_NOT_MODIFIED); |
| 254 } |
| 255 |
| 256 void MemBackendImpl::EvictIfNeeded() { |
| 257 if (current_size_ <= max_size_) |
| 258 return; |
| 259 |
| 260 int target_size = std::max(0, max_size_ - kDefaultEvictionSize); |
| 261 |
| 262 base::LinkNode<MemEntryImpl>* entry = lru_list_.head(); |
| 263 while (current_size_ > target_size && entry != lru_list_.end()) { |
| 264 MemEntryImpl* to_doom = entry->value(); |
| 265 entry = entry->next(); |
| 266 if (!to_doom->InUse()) |
| 267 to_doom->Doom(); |
| 232 } | 268 } |
| 233 } | 269 } |
| 234 | 270 |
| 235 bool MemBackendImpl::OpenEntry(const std::string& key, Entry** entry) { | |
| 236 EntryMap::iterator it = entries_.find(key); | |
| 237 if (it == entries_.end()) | |
| 238 return false; | |
| 239 | |
| 240 it->second->Open(); | |
| 241 | |
| 242 *entry = it->second; | |
| 243 return true; | |
| 244 } | |
| 245 | |
| 246 bool MemBackendImpl::CreateEntry(const std::string& key, Entry** entry) { | |
| 247 EntryMap::iterator it = entries_.find(key); | |
| 248 if (it != entries_.end()) | |
| 249 return false; | |
| 250 | |
| 251 MemEntryImpl* cache_entry = new MemEntryImpl(this); | |
| 252 if (!cache_entry->CreateEntry(key, net_log_)) { | |
| 253 delete entry; | |
| 254 return false; | |
| 255 } | |
| 256 | |
| 257 rankings_.Insert(cache_entry); | |
| 258 entries_[key] = cache_entry; | |
| 259 | |
| 260 *entry = cache_entry; | |
| 261 return true; | |
| 262 } | |
| 263 | |
| 264 bool MemBackendImpl::DoomEntry(const std::string& key) { | |
| 265 Entry* entry; | |
| 266 if (!OpenEntry(key, &entry)) | |
| 267 return false; | |
| 268 | |
| 269 entry->Doom(); | |
| 270 entry->Close(); | |
| 271 return true; | |
| 272 } | |
| 273 | |
| 274 bool MemBackendImpl::DoomAllEntries() { | |
| 275 TrimCache(true); | |
| 276 return true; | |
| 277 } | |
| 278 | |
| 279 bool MemBackendImpl::DoomEntriesBetween(const Time initial_time, | |
| 280 const Time end_time) { | |
| 281 if (end_time.is_null()) | |
| 282 return DoomEntriesSince(initial_time); | |
| 283 | |
| 284 DCHECK(end_time >= initial_time); | |
| 285 | |
| 286 MemEntryImpl* node = rankings_.GetNext(NULL); | |
| 287 // Last valid entry before |node|. | |
| 288 // Note, that entries after |node| may become invalid during |node| doom in | |
| 289 // case when they are child entries of it. It is guaranteed that | |
| 290 // parent node will go prior to it childs in ranking list (see | |
| 291 // InternalReadSparseData and InternalWriteSparseData). | |
| 292 MemEntryImpl* last_valid = NULL; | |
| 293 | |
| 294 // rankings_ is ordered by last used, this will descend through the cache | |
| 295 // and start dooming items before the end_time, and will stop once it reaches | |
| 296 // an item used before the initial time. | |
| 297 while (node) { | |
| 298 if (node->GetLastUsed() < initial_time) | |
| 299 break; | |
| 300 | |
| 301 if (node->GetLastUsed() < end_time) | |
| 302 node->Doom(); | |
| 303 else | |
| 304 last_valid = node; | |
| 305 node = rankings_.GetNext(last_valid); | |
| 306 } | |
| 307 | |
| 308 return true; | |
| 309 } | |
| 310 | |
| 311 bool MemBackendImpl::DoomEntriesSince(const Time initial_time) { | |
| 312 for (;;) { | |
| 313 // Get the entry in the front. | |
| 314 Entry* entry = rankings_.GetNext(NULL); | |
| 315 | |
| 316 // Break the loop when there are no more entries or the entry is too old. | |
| 317 if (!entry || entry->GetLastUsed() < initial_time) | |
| 318 return true; | |
| 319 entry->Doom(); | |
| 320 } | |
| 321 } | |
| 322 | |
| 323 void MemBackendImpl::TrimCache(bool empty) { | |
| 324 MemEntryImpl* next = rankings_.GetPrev(NULL); | |
| 325 if (!next) | |
| 326 return; | |
| 327 | |
| 328 int target_size = empty ? 0 : LowWaterAdjust(max_size_); | |
| 329 while (current_size_ > target_size && next) { | |
| 330 MemEntryImpl* node = next; | |
| 331 next = rankings_.GetPrev(next); | |
| 332 if (!node->InUse() || empty) { | |
| 333 node->Doom(); | |
| 334 } | |
| 335 } | |
| 336 | |
| 337 return; | |
| 338 } | |
| 339 | |
| 340 void MemBackendImpl::AddStorageSize(int32_t bytes) { | |
| 341 current_size_ += bytes; | |
| 342 DCHECK_GE(current_size_, 0); | |
| 343 | |
| 344 if (current_size_ > max_size_) | |
| 345 TrimCache(false); | |
| 346 } | |
| 347 | |
| 348 void MemBackendImpl::SubstractStorageSize(int32_t bytes) { | |
| 349 current_size_ -= bytes; | |
| 350 DCHECK_GE(current_size_, 0); | |
| 351 } | |
| 352 | |
| 353 } // namespace disk_cache | 271 } // namespace disk_cache |
| OLD | NEW |