Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(114)

Side by Side Diff: chrome/browser/nacl_host/pnacl_translation_cache.cc

Issue 15647018: Add read support to PNaClTranslationCache (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 7 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright 2013 The Chromium Authors. All rights reserved. 1 // Copyright 2013 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 "chrome/browser/nacl_host/pnacl_translation_cache.h" 5 #include "chrome/browser/nacl_host/pnacl_translation_cache.h"
6 6
7 #include <string> 7 #include <string>
8 8
9 #include "base/files/file_path.h" 9 #include "base/files/file_path.h"
10 #include "base/logging.h" 10 #include "base/logging.h"
(...skipping 15 matching lines...) Expand all
26 26
27 } // namespace 27 } // namespace
28 28
29 namespace pnacl_cache { 29 namespace pnacl_cache {
30 // These are in pnacl_cache namespace instead of static so they can be used 30 // These are in pnacl_cache namespace instead of static so they can be used
31 // by the unit test. 31 // by the unit test.
32 const int kMaxDiskCacheSize = 1000 * 1024 * 1024; 32 const int kMaxDiskCacheSize = 1000 * 1024 * 1024;
33 const int kMaxMemCacheSize = 100 * 1024 * 1024; 33 const int kMaxMemCacheSize = 100 * 1024 * 1024;
34 34
35 ////////////////////////////////////////////////////////////////////// 35 //////////////////////////////////////////////////////////////////////
36 // Handle Storing to Cache. 36 // Handle Reading/Writing to Cache.
37 37
38 // PNaClTranslationCacheWriteEntry is a shim that provides storage for the 38 // PNaClTranslationCacheEntry is a shim that provides storage for the
39 // 'key' and 'data' strings as the disk_cache is performing various async 39 // 'key' and 'data' strings as the disk_cache is performing various async
40 // operations. It also tracks the open disk_cache::Entry 40 // operations. It also tracks the open disk_cache::Entry
41 // and ensures that the entry is closed. 41 // and ensures that the entry is closed.
42 class PNaClTranslationCacheWriteEntry 42 class PNaClTranslationCacheEntry
43 : public base::RefCounted<PNaClTranslationCacheWriteEntry> { 43 : public base::RefCounted<PNaClTranslationCacheEntry> {
44 public: 44 public:
45 PNaClTranslationCacheWriteEntry(base::WeakPtr<PNaClTranslationCache> cache, 45 PNaClTranslationCacheEntry(base::WeakPtr<PNaClTranslationCache> cache,
46 const std::string& key, 46 const std::string& key,
47 const std::string& nexe, 47 std::string* nexe,
48 const net::CompletionCallback& callback); 48 const CompletionCallback& callback,
49 bool is_read);
49 50
50 void Cache(); 51 void Start();
51 52
52 // --- 53 // Writes: ---
53 // v | 54 // v |
54 // Cache -> Open Existing --------------> Write ---> Close 55 // Start -> Open Existing --------------> Write ---> Close
55 // \ ^ 56 // \ ^
56 // \ / 57 // \ /
57 // --> Create -- 58 // --> Create --
59 // Reads:
60 // Start -> Open --------Read ----> Close
61 // | ^
62 // |__|
58 enum CacheStep { 63 enum CacheStep {
59 UNINITIALIZED, 64 UNINITIALIZED,
60 OPEN_ENTRY, 65 OPEN_ENTRY,
61 CREATE_ENTRY, 66 CREATE_ENTRY,
62 WRITE_ENTRY, 67 TRANSFER_ENTRY,
63 CLOSE_ENTRY 68 CLOSE_ENTRY
64 }; 69 };
65 70
66 private: 71 private:
67 friend class base::RefCounted<PNaClTranslationCacheWriteEntry>; 72 friend class base::RefCounted<PNaClTranslationCacheEntry>;
68 ~PNaClTranslationCacheWriteEntry(); 73 ~PNaClTranslationCacheEntry();
69 74
70 void CreateEntry(); 75 void CreateEntry();
71 76
72 void OpenEntry(); 77 void OpenEntry();
73 78
74 void WriteEntry(int bytes_to_skip); 79 void WriteEntry(int offset, int len);
80 void ReadEntry(int offset, int len);
jvoung (off chromium) 2013/06/05 00:42:28 extra space between functions to be consistent
Derek Schuff 2013/06/05 05:01:59 even better; made consistent and added comments.
75 81
76 void CloseEntry(int rv); 82 void CloseEntry(int rv);
77 83
84 // Handles state transitions, tracks bytes transferred
78 void DispatchNext(int rv); 85 void DispatchNext(int rv);
79 86
87 // Get the total transfer size. For reads, must be called after the entry
88 // has been info has been fetched from the backend.
89 int GetTransferSize();
90
80 base::WeakPtr<PNaClTranslationCache> cache_; 91 base::WeakPtr<PNaClTranslationCache> cache_;
81 92
82 std::string key_; 93 std::string key_;
83 std::string nexe_; 94 std::string* nexe_;
84 disk_cache::Entry* entry_; 95 disk_cache::Entry* entry_;
85 CacheStep step_; 96 CacheStep step_;
97 bool is_read_;
98 int bytes_transferred_;
99 int bytes_to_transfer_;
100 scoped_refptr<net::IOBufferWithSize> read_buf_;
86 CompletionCallback finish_callback_; 101 CompletionCallback finish_callback_;
87 base::ThreadChecker thread_checker_; 102 base::ThreadChecker thread_checker_;
88 DISALLOW_COPY_AND_ASSIGN(PNaClTranslationCacheWriteEntry); 103 DISALLOW_COPY_AND_ASSIGN(PNaClTranslationCacheEntry);
89 }; 104 };
90 105
91 PNaClTranslationCacheWriteEntry::PNaClTranslationCacheWriteEntry( 106 PNaClTranslationCacheEntry::PNaClTranslationCacheEntry(
92 base::WeakPtr<PNaClTranslationCache> cache, 107 base::WeakPtr<PNaClTranslationCache> cache,
93 const std::string& key, 108 const std::string& key,
94 const std::string& nexe, 109 std::string* nexe,
95 const net::CompletionCallback& callback) 110 const CompletionCallback& callback,
111 bool is_read)
96 : cache_(cache), 112 : cache_(cache),
97 key_(key), 113 key_(key),
98 nexe_(nexe), 114 nexe_(nexe),
99 entry_(NULL), 115 entry_(NULL),
100 step_(UNINITIALIZED), 116 step_(UNINITIALIZED),
117 is_read_(is_read),
118 bytes_transferred_(0),
119 bytes_to_transfer_(-1),
101 finish_callback_(callback) {} 120 finish_callback_(callback) {}
102 121
103 PNaClTranslationCacheWriteEntry::~PNaClTranslationCacheWriteEntry() { 122 PNaClTranslationCacheEntry::~PNaClTranslationCacheEntry() {
104 if (entry_) 123 if (entry_)
105 BrowserThread::PostTask( 124 BrowserThread::PostTask(
106 BrowserThread::IO, FROM_HERE, base::Bind(&CloseDiskCacheEntry, entry_)); 125 BrowserThread::IO, FROM_HERE, base::Bind(&CloseDiskCacheEntry, entry_));
107 } 126 }
108 127
109 void PNaClTranslationCacheWriteEntry::Cache() { 128 void PNaClTranslationCacheEntry::Start() {
110 DCHECK(thread_checker_.CalledOnValidThread()); 129 DCHECK(thread_checker_.CalledOnValidThread());
111 step_ = OPEN_ENTRY; 130 step_ = OPEN_ENTRY;
112 OpenEntry(); 131 OpenEntry();
113 } 132 }
114 133
115 // OpenEntry, CreateEntry, WriteEntry, and CloseEntry are only called from 134 // OpenEntry, CreateEntry, WriteEntry, and CloseEntry are only called from
116 // DispatchNext, so they know that cache_ is still valid. 135 // DispatchNext, so they know that cache_ is still valid.
117 void PNaClTranslationCacheWriteEntry::OpenEntry() { 136 void PNaClTranslationCacheEntry::OpenEntry() {
118 int rv = cache_->backend()->OpenEntry( 137 int rv = cache_->backend()
119 key_, 138 ->OpenEntry(key_,
120 &entry_, 139 &entry_,
121 base::Bind(&PNaClTranslationCacheWriteEntry::DispatchNext, this)); 140 base::Bind(&PNaClTranslationCacheEntry::DispatchNext, this));
122 if (rv != net::ERR_IO_PENDING) 141 if (rv != net::ERR_IO_PENDING)
123 DispatchNext(rv); 142 DispatchNext(rv);
124 } 143 }
125 144
126 void PNaClTranslationCacheWriteEntry::CreateEntry() { 145 void PNaClTranslationCacheEntry::CreateEntry() {
127 int rv = cache_->backend()->CreateEntry( 146 int rv = cache_->backend()->CreateEntry(
128 key_, 147 key_,
129 &entry_, 148 &entry_,
130 base::Bind(&PNaClTranslationCacheWriteEntry::DispatchNext, this)); 149 base::Bind(&PNaClTranslationCacheEntry::DispatchNext, this));
131 if (rv != net::ERR_IO_PENDING) 150 if (rv != net::ERR_IO_PENDING)
132 DispatchNext(rv); 151 DispatchNext(rv);
133 } 152 }
134 153
135 void PNaClTranslationCacheWriteEntry::WriteEntry(int bytes_to_skip) { 154 void PNaClTranslationCacheEntry::WriteEntry(int offset, int len) {
136 nexe_ = nexe_.substr(bytes_to_skip); 155 scoped_refptr<net::StringIOBuffer> io_buf =
137 scoped_refptr<net::StringIOBuffer> io_buf = new net::StringIOBuffer(nexe_); 156 new net::StringIOBuffer(nexe_->substr(offset, len));
138 int rv = entry_->WriteData( 157 int rv = entry_->WriteData(
139 1, 158 1,
140 0, 159 offset,
141 io_buf, 160 io_buf,
142 nexe_.length(), 161 len,
143 base::Bind(&PNaClTranslationCacheWriteEntry::DispatchNext, this), 162 base::Bind(&PNaClTranslationCacheEntry::DispatchNext, this),
144 false); 163 false);
145 if (rv != net::ERR_IO_PENDING) 164 if (rv != net::ERR_IO_PENDING)
146 DispatchNext(rv); 165 DispatchNext(rv);
147 } 166 }
148 167
149 void PNaClTranslationCacheWriteEntry::CloseEntry(int rv) { 168 void PNaClTranslationCacheEntry::ReadEntry(int offset, int len) {
150 if (rv < 0) 169 read_buf_ = new net::IOBufferWithSize(len);
170 int rv = entry_->ReadData(
171 1,
172 offset,
173 read_buf_,
174 len,
175 base::Bind(&PNaClTranslationCacheEntry::DispatchNext, this));
176 if (rv != net::ERR_IO_PENDING)
177 DispatchNext(rv);
178 }
179
180 int PNaClTranslationCacheEntry::GetTransferSize() {
181 if (is_read_) {
182 DCHECK(entry_);
183 return entry_->GetDataSize(1);
184 }
185 return nexe_->size();
186 }
187
188 void PNaClTranslationCacheEntry::CloseEntry(int rv) {
189 if (entry_ && rv < 0)
151 entry_->Doom(); 190 entry_->Doom();
152 if (!finish_callback_.is_null()) { 191 if (!finish_callback_.is_null()) {
153 finish_callback_.Run(rv); 192 finish_callback_.Run(rv);
154 finish_callback_.Reset(); 193 finish_callback_.Reset();
155 } 194 }
156 cache_->WriteComplete(this); 195 if (!is_read_)
196 delete nexe_;
jvoung (off chromium) 2013/06/05 00:42:28 Perhaps the nexe shouldn't be the same field for b
Derek Schuff 2013/06/05 05:01:59 Yeah, we need to call CloseEntry anyway. Actually
jvoung (off chromium) 2013/06/05 17:52:29 Well, I think static factories might be better so
197 cache_->OpComplete(this);
157 } 198 }
158 199
159 void PNaClTranslationCacheWriteEntry::DispatchNext(int rv) { 200 void PNaClTranslationCacheEntry::DispatchNext(int rv) {
160 DCHECK(thread_checker_.CalledOnValidThread()); 201 DCHECK(thread_checker_.CalledOnValidThread());
161 if (!cache_) 202 if (!cache_)
162 return; 203 return;
163 204
164 switch (step_) { 205 switch (step_) {
165 case UNINITIALIZED: 206 case UNINITIALIZED:
166 LOG(ERROR) << "Unexpected step in DispatchNext"; 207 LOG(ERROR) << "Unexpected step in DispatchNext";
167 break; 208 break;
168 209
169 case OPEN_ENTRY: 210 case OPEN_ENTRY:
170 if (rv == net::OK) { 211 if (rv == net::OK) {
171 step_ = WRITE_ENTRY; 212 step_ = TRANSFER_ENTRY;
172 WriteEntry(0); 213 bytes_to_transfer_ = GetTransferSize();
214 is_read_ ? ReadEntry(0, bytes_to_transfer_)
215 : WriteEntry(0, bytes_to_transfer_);
173 } else { 216 } else {
217 if (is_read_) {
218 // Just a cache miss, not necessarily an error.
219 entry_ = NULL;
jvoung (off chromium) 2013/06/05 00:42:28 It looks a bit odd to set entry_ to NULL then call
Derek Schuff 2013/06/05 05:01:59 Done.
220 CloseEntry(rv);
221 break;
222 }
174 step_ = CREATE_ENTRY; 223 step_ = CREATE_ENTRY;
175 CreateEntry(); 224 CreateEntry();
176 } 225 }
177 break; 226 break;
178 227
179 case CREATE_ENTRY: 228 case CREATE_ENTRY:
180 if (rv == net::OK) { 229 if (rv == net::OK) {
181 step_ = WRITE_ENTRY; 230 step_ = TRANSFER_ENTRY;
182 WriteEntry(0); 231 bytes_to_transfer_ = GetTransferSize();
232 WriteEntry(bytes_transferred_, bytes_to_transfer_ - bytes_transferred_);
183 } else { 233 } else {
184 LOG(ERROR) << "Failed to Open/Create a PNaCl Translation Cache Entry"; 234 LOG(ERROR) << "Failed to Create a PNaCl Translation Cache Entry";
185 CloseEntry(rv); 235 CloseEntry(rv);
186 } 236 }
187 break; 237 break;
188 238
189 case WRITE_ENTRY: 239 case TRANSFER_ENTRY:
190 if (rv < 0) { 240 if (rv < 0) {
191 // We do not call DispatchNext directly if WriteEntry returns 241 // We do not call DispatchNext directly if WriteEntry returns
jvoung (off chromium) 2013/06/05 00:42:28 WriteEntry or ReadEntry now.
Derek Schuff 2013/06/05 05:01:59 Done.
192 // ERR_IO_PENDING, and the callback should not return that value either. 242 // ERR_IO_PENDING, and the callback should not return that value either.
193 LOG(ERROR) 243 LOG(ERROR)
194 << "Failed to complete write to PNaCl Translation Cache Entry: " 244 << "Failed to complete write to PNaCl Translation Cache Entry: "
195 << rv; 245 << rv;
196 step_ = CLOSE_ENTRY; 246 step_ = CLOSE_ENTRY;
197 CloseEntry(rv); 247 CloseEntry(rv);
198 break; 248 break;
249 } else if (rv > 0) {
250 // For reads, copy the data that was just returned
251 if (is_read_)
252 nexe_->append(read_buf_->data(), rv);
253 bytes_transferred_ += rv;
254 if (bytes_transferred_ < bytes_to_transfer_) {
255 int len = bytes_to_transfer_ - bytes_transferred_;
256 is_read_ ? ReadEntry(bytes_transferred_, len)
257 : WriteEntry(bytes_transferred_, len);
258 break;
259 }
199 } 260 }
200 if (rv == 0) { 261 // rv == 0 or we fell through (i.e. we have transferred all the bytes)
201 step_ = CLOSE_ENTRY; 262 step_ = CLOSE_ENTRY;
202 CloseEntry(rv); 263 CloseEntry(0);
203 break;
204 }
205 // rv bytes were written; call WriteEntry again to skip them and try to
206 // write the rest.
207 WriteEntry(rv);
208 break; 264 break;
209 265
210 case CLOSE_ENTRY: 266 case CLOSE_ENTRY:
211 step_ = UNINITIALIZED; 267 step_ = UNINITIALIZED;
212 break; 268 break;
213 } 269 }
214 } 270 }
215 271
216 ////////////////////////////////////////////////////////////////////// 272 //////////////////////////////////////////////////////////////////////
217 void PNaClTranslationCache::WriteComplete( 273 void PNaClTranslationCache::OpComplete(PNaClTranslationCacheEntry* entry) {
218 PNaClTranslationCacheWriteEntry* entry) {
219 write_entries_.erase(entry); 274 write_entries_.erase(entry);
220 } 275 }
221 276
222 ////////////////////////////////////////////////////////////////////// 277 //////////////////////////////////////////////////////////////////////
223 // Construction and cache backend initialization 278 // Construction and cache backend initialization
224 PNaClTranslationCache::PNaClTranslationCache() 279 PNaClTranslationCache::PNaClTranslationCache()
225 : disk_cache_(NULL), in_memory_(false) {} 280 : disk_cache_(NULL), in_memory_(false) {}
226 281
227 PNaClTranslationCache::~PNaClTranslationCache() { 282 PNaClTranslationCache::~PNaClTranslationCache() { delete disk_cache_; }
228 delete disk_cache_;
229 }
230 283
231 int PNaClTranslationCache::InitWithDiskBackend( 284 int PNaClTranslationCache::InitWithDiskBackend(
232 const base::FilePath& cache_dir, 285 const base::FilePath& cache_dir,
233 int cache_size, 286 int cache_size,
234 const net::CompletionCallback& callback) { 287 const CompletionCallback& callback) {
235 return Init(net::DISK_CACHE, cache_dir, cache_size, callback); 288 return Init(net::DISK_CACHE, cache_dir, cache_size, callback);
236 } 289 }
237 290
238 int PNaClTranslationCache::InitWithMemBackend( 291 int PNaClTranslationCache::InitWithMemBackend(
239 int cache_size, 292 int cache_size,
240 const net::CompletionCallback& callback) { 293 const CompletionCallback& callback) {
241 return Init(net::MEMORY_CACHE, base::FilePath(), cache_size, callback); 294 return Init(net::MEMORY_CACHE, base::FilePath(), cache_size, callback);
242 } 295 }
243 296
244 int PNaClTranslationCache::Init(net::CacheType cache_type, 297 int PNaClTranslationCache::Init(net::CacheType cache_type,
245 const base::FilePath& cache_dir, 298 const base::FilePath& cache_dir,
246 int cache_size, 299 int cache_size,
247 const net::CompletionCallback& callback) { 300 const CompletionCallback& callback) {
248 int rv = disk_cache::CreateCacheBackend( 301 int rv = disk_cache::CreateCacheBackend(
249 cache_type, 302 cache_type,
250 net::CACHE_BACKEND_DEFAULT, 303 net::CACHE_BACKEND_DEFAULT,
251 cache_dir, 304 cache_dir,
252 cache_size, 305 cache_size,
253 true /* force_initialize */, 306 true /* force_initialize */,
254 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::CACHE), 307 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::CACHE),
255 NULL, /* dummy net log */ 308 NULL, /* dummy net log */
256 &disk_cache_, 309 &disk_cache_,
257 base::Bind(&PNaClTranslationCache::OnCreateBackendComplete, AsWeakPtr())); 310 base::Bind(&PNaClTranslationCache::OnCreateBackendComplete, AsWeakPtr()));
258 init_callback_ = callback; 311 init_callback_ = callback;
259 if (rv != net::ERR_IO_PENDING) { 312 if (rv != net::ERR_IO_PENDING) {
260 OnCreateBackendComplete(rv); 313 OnCreateBackendComplete(rv);
261 } 314 }
262 return rv; 315 return rv;
263 } 316 }
264 317
265 void PNaClTranslationCache::OnCreateBackendComplete(int rv) { 318 void PNaClTranslationCache::OnCreateBackendComplete(int rv) {
266 // Invoke our client's callback function. 319 // Invoke our client's callback function.
267 if (!init_callback_.is_null()) { 320 if (!init_callback_.is_null()) {
268 init_callback_.Run(rv); 321 init_callback_.Run(rv);
269 init_callback_.Reset(); 322 init_callback_.Reset();
270 } 323 }
271 } 324 }
272 325
273 ////////////////////////////////////////////////////////////////////// 326 //////////////////////////////////////////////////////////////////////
274 // High-level API 327 // High-level API
275 328
276 // TODO(dschuff): Surely there must be a way to just create a null callback?
277 static void NullCallback(int ignored) {}
278
279 void PNaClTranslationCache::StoreNexe(const std::string& key, 329 void PNaClTranslationCache::StoreNexe(const std::string& key,
280 const std::string& nexe) { 330 const std::string& nexe) {
281 StoreNexe(key, nexe, base::Bind(NullCallback)); 331 StoreNexe(key, nexe, CompletionCallback());
282 } 332 }
283 333
284 void PNaClTranslationCache::StoreNexe(const std::string& key, 334 void PNaClTranslationCache::StoreNexe(const std::string& key,
285 const std::string& nexe, 335 const std::string& nexe,
286 const net::CompletionCallback& callback) { 336 const CompletionCallback& callback) {
287 PNaClTranslationCacheWriteEntry* entry = 337 PNaClTranslationCacheEntry* entry = new PNaClTranslationCacheEntry(
288 new PNaClTranslationCacheWriteEntry(AsWeakPtr(), key, nexe, callback); 338 AsWeakPtr(), key, new std::string(nexe), callback, false);
289 write_entries_[entry] = entry; 339 write_entries_[entry] = entry;
290 entry->Cache(); 340 entry->Start();
291 } 341 }
292 342
293 int PNaClTranslationCache::GetNexe(const std::string& key, 343 void PNaClTranslationCache::GetNexe(const std::string& key,
294 std::string* nexe, 344 std::string* nexe,
295 const net::CompletionCallback& callback) { 345 const CompletionCallback& callback) {
296 // TODO(dschuff): Actually find the entry, and do the right thing. 346 PNaClTranslationCacheEntry* entry =
297 // Shader cache ended up making a separate ReadHelper, analogous 347 new PNaClTranslationCacheEntry(AsWeakPtr(), key, nexe, callback, true);
298 // to the PNaClTranslationCacheWriteEntry. 348 write_entries_[entry] = entry;
299 return net::OK; 349 entry->Start();
300 } 350 }
301 351
302 int PNaClTranslationCache::InitCache(const base::FilePath& cache_directory, 352 int PNaClTranslationCache::InitCache(const base::FilePath& cache_directory,
303 bool in_memory, 353 bool in_memory,
304 const net::CompletionCallback& callback) { 354 const CompletionCallback& callback) {
305 int rv; 355 int rv;
306 in_memory_ = in_memory; 356 in_memory_ = in_memory;
307 if (in_memory_) { 357 if (in_memory_) {
308 rv = InitWithMemBackend(kMaxMemCacheSize, callback); 358 rv = InitWithMemBackend(kMaxMemCacheSize, callback);
309 } else { 359 } else {
310 rv = InitWithDiskBackend(cache_directory.Append(kDiskCacheDirectoryName), 360 rv = InitWithDiskBackend(cache_directory.Append(kDiskCacheDirectoryName),
311 kMaxDiskCacheSize, 361 kMaxDiskCacheSize,
312 callback); 362 callback);
313 } 363 }
314 364
315 return rv; 365 return rv;
316 } 366 }
317 367
318 int PNaClTranslationCache::Size() { 368 int PNaClTranslationCache::Size() {
319 if (!disk_cache_) 369 if (!disk_cache_)
320 return -1; 370 return -1;
321 return disk_cache_->GetEntryCount(); 371 return disk_cache_->GetEntryCount();
322 } 372 }
323 373
324 } // namespace nacl_cache 374 } // namespace nacl_cache
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698