Line data Source code
1 : /* This Source Code Form is subject to the terms of the Mozilla Public
2 : * License, v. 2.0. If a copy of the MPL was not distributed with this
3 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 :
5 : #include "CacheLog.h"
6 : #include "CacheFileChunk.h"
7 :
8 : #include "CacheFile.h"
9 : #include "nsThreadUtils.h"
10 :
11 : #include "mozilla/IntegerPrintfMacros.h"
12 :
13 : namespace mozilla {
14 : namespace net {
15 :
16 : #define kMinBufSize 512
17 :
18 8 : CacheFileChunkBuffer::CacheFileChunkBuffer(CacheFileChunk *aChunk)
19 : : mChunk(aChunk)
20 : , mBuf(nullptr)
21 : , mBufSize(0)
22 : , mDataSize(0)
23 : , mReadHandlesCount(0)
24 8 : , mWriteHandleExists(false)
25 : {
26 8 : }
27 :
28 16 : CacheFileChunkBuffer::~CacheFileChunkBuffer()
29 : {
30 8 : if (mBuf) {
31 4 : CacheFileUtils::FreeBuffer(mBuf);
32 4 : mBuf = nullptr;
33 4 : mChunk->BuffersAllocationChanged(mBufSize, 0);
34 4 : mBufSize = 0;
35 : }
36 8 : }
37 :
38 : void
39 0 : CacheFileChunkBuffer::CopyFrom(CacheFileChunkBuffer *aOther)
40 : {
41 0 : MOZ_RELEASE_ASSERT(mBufSize >= aOther->mDataSize);
42 0 : mDataSize = aOther->mDataSize;
43 0 : memcpy(mBuf, aOther->mBuf, mDataSize);
44 0 : }
45 :
46 : nsresult
47 0 : CacheFileChunkBuffer::FillInvalidRanges(CacheFileChunkBuffer *aOther,
48 : CacheFileUtils::ValidityMap *aMap)
49 : {
50 : nsresult rv;
51 :
52 0 : rv = EnsureBufSize(aOther->mDataSize);
53 0 : if (NS_FAILED(rv)) {
54 0 : return rv;
55 : }
56 :
57 0 : uint32_t invalidOffset = 0;
58 : uint32_t invalidLength;
59 :
60 0 : for (uint32_t i = 0; i < aMap->Length(); ++i) {
61 0 : uint32_t validOffset = (*aMap)[i].Offset();
62 0 : uint32_t validLength = (*aMap)[i].Len();
63 :
64 0 : MOZ_RELEASE_ASSERT(invalidOffset <= validOffset);
65 0 : invalidLength = validOffset - invalidOffset;
66 0 : if (invalidLength > 0) {
67 0 : MOZ_RELEASE_ASSERT(invalidOffset + invalidLength <= aOther->mDataSize);
68 0 : memcpy(mBuf + invalidOffset, aOther->mBuf + invalidOffset, invalidLength);
69 : }
70 0 : invalidOffset = validOffset + validLength;
71 : }
72 :
73 0 : if (invalidOffset < aOther->mDataSize) {
74 0 : invalidLength = aOther->mDataSize - invalidOffset;
75 0 : memcpy(mBuf + invalidOffset, aOther->mBuf + invalidOffset, invalidLength);
76 : }
77 :
78 0 : return NS_OK;
79 : }
80 :
81 : MOZ_MUST_USE nsresult
82 4 : CacheFileChunkBuffer::EnsureBufSize(uint32_t aBufSize)
83 : {
84 4 : AssertOwnsLock();
85 :
86 4 : if (mBufSize >= aBufSize) {
87 0 : return NS_OK;
88 : }
89 :
90 : // find smallest power of 2 greater than or equal to aBufSize
91 4 : aBufSize--;
92 4 : aBufSize |= aBufSize >> 1;
93 4 : aBufSize |= aBufSize >> 2;
94 4 : aBufSize |= aBufSize >> 4;
95 4 : aBufSize |= aBufSize >> 8;
96 4 : aBufSize |= aBufSize >> 16;
97 4 : aBufSize++;
98 :
99 4 : const uint32_t minBufSize = kMinBufSize;
100 4 : const uint32_t maxBufSize = kChunkSize;
101 4 : aBufSize = clamped(aBufSize, minBufSize, maxBufSize);
102 :
103 4 : if (!mChunk->CanAllocate(aBufSize - mBufSize)) {
104 0 : return NS_ERROR_OUT_OF_MEMORY;
105 : }
106 :
107 4 : char *newBuf = static_cast<char *>(realloc(mBuf, aBufSize));
108 4 : if (!newBuf) {
109 0 : return NS_ERROR_OUT_OF_MEMORY;
110 : }
111 :
112 4 : mChunk->BuffersAllocationChanged(mBufSize, aBufSize);
113 4 : mBuf = newBuf;
114 4 : mBufSize = aBufSize;
115 :
116 4 : return NS_OK;
117 : }
118 :
119 : void
120 4 : CacheFileChunkBuffer::SetDataSize(uint32_t aDataSize)
121 : {
122 4 : MOZ_RELEASE_ASSERT(
123 : // EnsureBufSize must be called before SetDataSize, so the new data size
124 : // is guaranteed to be smaller than or equal to mBufSize.
125 : aDataSize <= mBufSize ||
126 : // The only exception is an optimization when we read the data from the
127 : // disk. The data is read to a separate buffer and CacheFileChunk::mBuf is
128 : // empty (see CacheFileChunk::Read). We need to set mBuf::mDataSize
129 : // accordingly so that DataSize() methods return correct value, but we don't
130 : // want to allocate the buffer since it wouldn't be used in most cases.
131 : (mBufSize == 0 && mChunk->mState == CacheFileChunk::READING));
132 :
133 4 : mDataSize = aDataSize;
134 4 : }
135 :
136 : void
137 51 : CacheFileChunkBuffer::AssertOwnsLock() const
138 : {
139 51 : mChunk->AssertOwnsLock();
140 51 : }
141 :
142 : void
143 13 : CacheFileChunkBuffer::RemoveReadHandle()
144 : {
145 13 : AssertOwnsLock();
146 13 : MOZ_RELEASE_ASSERT(mReadHandlesCount);
147 13 : MOZ_RELEASE_ASSERT(!mWriteHandleExists);
148 13 : mReadHandlesCount--;
149 :
150 13 : if (mReadHandlesCount == 0 && mChunk->mBuf != this) {
151 0 : DebugOnly<bool> removed = mChunk->mOldBufs.RemoveElement(this);
152 0 : MOZ_ASSERT(removed);
153 : }
154 13 : }
155 :
156 : void
157 2 : CacheFileChunkBuffer::RemoveWriteHandle()
158 : {
159 2 : AssertOwnsLock();
160 2 : MOZ_RELEASE_ASSERT(mReadHandlesCount == 0);
161 2 : MOZ_RELEASE_ASSERT(mWriteHandleExists);
162 2 : mWriteHandleExists = false;
163 2 : }
164 :
165 : size_t
166 0 : CacheFileChunkBuffer::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const
167 : {
168 0 : size_t n = mallocSizeOf(this);
169 :
170 0 : if (mBuf) {
171 0 : n += mallocSizeOf(mBuf);
172 : }
173 :
174 0 : return n;
175 : }
176 :
177 : uint32_t
178 15 : CacheFileChunkHandle::DataSize()
179 : {
180 15 : MOZ_ASSERT(mBuf, "Unexpected call on dummy handle");
181 15 : mBuf->AssertOwnsLock();
182 15 : return mBuf->mDataSize;
183 : }
184 :
185 : uint32_t
186 17 : CacheFileChunkHandle::Offset()
187 : {
188 17 : MOZ_ASSERT(mBuf, "Unexpected call on dummy handle");
189 17 : mBuf->AssertOwnsLock();
190 17 : return mBuf->mChunk->Index() * kChunkSize;
191 : }
192 :
193 13 : CacheFileChunkReadHandle::CacheFileChunkReadHandle(CacheFileChunkBuffer *aBuf)
194 : {
195 13 : mBuf = aBuf;
196 13 : mBuf->mReadHandlesCount++;
197 13 : }
198 :
199 26 : CacheFileChunkReadHandle::~CacheFileChunkReadHandle()
200 : {
201 13 : mBuf->RemoveReadHandle();
202 13 : }
203 :
204 : const char *
205 8 : CacheFileChunkReadHandle::Buf()
206 : {
207 8 : return mBuf->mBuf;
208 : }
209 :
210 2 : CacheFileChunkWriteHandle::CacheFileChunkWriteHandle(CacheFileChunkBuffer *aBuf)
211 : {
212 2 : mBuf = aBuf;
213 2 : if (mBuf) {
214 2 : MOZ_ASSERT(!mBuf->mWriteHandleExists);
215 2 : mBuf->mWriteHandleExists = true;
216 : }
217 2 : }
218 :
219 4 : CacheFileChunkWriteHandle::~CacheFileChunkWriteHandle()
220 : {
221 2 : if (mBuf) {
222 2 : mBuf->RemoveWriteHandle();
223 : }
224 2 : }
225 :
226 : char *
227 4 : CacheFileChunkWriteHandle::Buf()
228 : {
229 4 : return mBuf ? mBuf->mBuf : nullptr;
230 : }
231 :
232 : void
233 2 : CacheFileChunkWriteHandle::UpdateDataSize(uint32_t aOffset, uint32_t aLen)
234 : {
235 2 : MOZ_ASSERT(mBuf, "Write performed on dummy handle?");
236 2 : MOZ_ASSERT(aOffset <= mBuf->mDataSize);
237 2 : MOZ_ASSERT(aOffset + aLen <= mBuf->mBufSize);
238 :
239 2 : if (aOffset + aLen > mBuf->mDataSize) {
240 2 : mBuf->mDataSize = aOffset + aLen;
241 : }
242 :
243 2 : mBuf->mChunk->UpdateDataSize(aOffset, aLen);
244 2 : }
245 :
246 :
247 : class NotifyUpdateListenerEvent : public Runnable {
248 : public:
249 0 : NotifyUpdateListenerEvent(CacheFileChunkListener* aCallback,
250 : CacheFileChunk* aChunk)
251 0 : : Runnable("net::NotifyUpdateListenerEvent")
252 : , mCallback(aCallback)
253 0 : , mChunk(aChunk)
254 : {
255 0 : LOG(("NotifyUpdateListenerEvent::NotifyUpdateListenerEvent() [this=%p]",
256 : this));
257 0 : }
258 :
259 : protected:
260 0 : ~NotifyUpdateListenerEvent()
261 0 : {
262 0 : LOG(("NotifyUpdateListenerEvent::~NotifyUpdateListenerEvent() [this=%p]",
263 : this));
264 0 : }
265 :
266 : public:
267 0 : NS_IMETHOD Run() override
268 : {
269 0 : LOG(("NotifyUpdateListenerEvent::Run() [this=%p]", this));
270 :
271 0 : mCallback->OnChunkUpdated(mChunk);
272 0 : return NS_OK;
273 : }
274 :
275 : protected:
276 : nsCOMPtr<CacheFileChunkListener> mCallback;
277 : RefPtr<CacheFileChunk> mChunk;
278 : };
279 :
280 : bool
281 53 : CacheFileChunk::DispatchRelease()
282 : {
283 53 : if (NS_IsMainThread()) {
284 38 : return false;
285 : }
286 :
287 30 : NS_DispatchToMainThread(NewNonOwningRunnableMethod(
288 15 : "net::CacheFileChunk::Release", this, &CacheFileChunk::Release));
289 :
290 15 : return true;
291 : }
292 :
293 38 : NS_IMPL_ADDREF(CacheFileChunk)
294 : NS_IMETHODIMP_(MozExternalRefCountType)
295 53 : CacheFileChunk::Release()
296 : {
297 53 : nsrefcnt count = mRefCnt - 1;
298 53 : if (DispatchRelease()) {
299 : // Redispatched to the main thread.
300 15 : return count;
301 : }
302 :
303 38 : NS_PRECONDITION(0 != mRefCnt, "dup release");
304 38 : count = --mRefCnt;
305 38 : NS_LOG_RELEASE(this, count, "CacheFileChunk");
306 :
307 38 : if (0 == count) {
308 4 : mRefCnt = 1;
309 4 : delete (this);
310 4 : return 0;
311 : }
312 :
313 : // We can safely access this chunk after decreasing mRefCnt since we re-post
314 : // all calls to Release() happening off the main thread to the main thread.
315 : // I.e. no other Release() that would delete the object could be run before
316 : // we call CacheFile::DeactivateChunk().
317 : //
318 : // NOTE: we don't grab the CacheFile's lock, so the chunk might be addrefed
319 : // on another thread before CacheFile::DeactivateChunk() grabs the lock on
320 : // this thread. To make sure we won't deactivate chunk that was just returned
321 : // to a new consumer we check mRefCnt once again in
322 : // CacheFile::DeactivateChunk() after we grab the lock.
323 34 : if (mActiveChunk && count == 1) {
324 5 : mFile->DeactivateChunk(this);
325 : }
326 :
327 34 : return count;
328 : }
329 :
330 4 : NS_INTERFACE_MAP_BEGIN(CacheFileChunk)
331 4 : NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileIOListener)
332 0 : NS_INTERFACE_MAP_ENTRY(nsISupports)
333 0 : NS_INTERFACE_MAP_END_THREADSAFE
334 :
335 4 : CacheFileChunk::CacheFileChunk(CacheFile *aFile, uint32_t aIndex,
336 4 : bool aInitByWriter)
337 4 : : CacheMemoryConsumer(aFile->mOpenAsMemoryOnly ? MEMORY_ONLY : DONT_REPORT)
338 : , mIndex(aIndex)
339 : , mState(INITIAL)
340 : , mStatus(NS_OK)
341 : , mActiveChunk(false)
342 : , mIsDirty(false)
343 : , mDiscardedChunk(false)
344 : , mBuffersSize(0)
345 4 : , mLimitAllocation(!aFile->mOpenAsMemoryOnly && aInitByWriter)
346 4 : , mIsPriority(aFile->mPriority)
347 : , mExpectedHash(0)
348 16 : , mFile(aFile)
349 : {
350 4 : LOG(("CacheFileChunk::CacheFileChunk() [this=%p, index=%u, initByWriter=%d]",
351 : this, aIndex, aInitByWriter));
352 4 : mBuf = new CacheFileChunkBuffer(this);
353 4 : }
354 :
355 12 : CacheFileChunk::~CacheFileChunk()
356 : {
357 4 : LOG(("CacheFileChunk::~CacheFileChunk() [this=%p]", this));
358 12 : }
359 :
360 : void
361 92 : CacheFileChunk::AssertOwnsLock() const
362 : {
363 92 : mFile->AssertOwnsLock();
364 92 : }
365 :
366 : void
367 2 : CacheFileChunk::InitNew()
368 : {
369 2 : AssertOwnsLock();
370 :
371 2 : LOG(("CacheFileChunk::InitNew() [this=%p]", this));
372 :
373 2 : MOZ_ASSERT(mState == INITIAL);
374 2 : MOZ_ASSERT(NS_SUCCEEDED(mStatus));
375 2 : MOZ_ASSERT(!mBuf->Buf());
376 2 : MOZ_ASSERT(!mWritingStateHandle);
377 2 : MOZ_ASSERT(!mReadingStateBuf);
378 2 : MOZ_ASSERT(!mIsDirty);
379 :
380 2 : mBuf = new CacheFileChunkBuffer(this);
381 2 : mState = READY;
382 2 : }
383 :
384 : nsresult
385 2 : CacheFileChunk::Read(CacheFileHandle *aHandle, uint32_t aLen,
386 : CacheHash::Hash16_t aHash,
387 : CacheFileChunkListener *aCallback)
388 : {
389 2 : AssertOwnsLock();
390 :
391 2 : LOG(("CacheFileChunk::Read() [this=%p, handle=%p, len=%d, listener=%p]",
392 : this, aHandle, aLen, aCallback));
393 :
394 2 : MOZ_ASSERT(mState == INITIAL);
395 2 : MOZ_ASSERT(NS_SUCCEEDED(mStatus));
396 2 : MOZ_ASSERT(!mBuf->Buf());
397 2 : MOZ_ASSERT(!mWritingStateHandle);
398 2 : MOZ_ASSERT(!mReadingStateBuf);
399 2 : MOZ_ASSERT(aLen);
400 :
401 : nsresult rv;
402 :
403 2 : mState = READING;
404 :
405 4 : RefPtr<CacheFileChunkBuffer> tmpBuf = new CacheFileChunkBuffer(this);
406 2 : rv = tmpBuf->EnsureBufSize(aLen);
407 2 : if (NS_FAILED(rv)) {
408 0 : SetError(rv);
409 0 : return mStatus;
410 : }
411 2 : tmpBuf->SetDataSize(aLen);
412 :
413 2 : rv = CacheFileIOManager::Read(aHandle, mIndex * kChunkSize,
414 : tmpBuf->Buf(), aLen,
415 2 : this);
416 2 : if (NS_WARN_IF(NS_FAILED(rv))) {
417 0 : rv = mIndex ? NS_ERROR_FILE_CORRUPTED : NS_ERROR_FILE_NOT_FOUND;
418 0 : SetError(rv);
419 : } else {
420 2 : mReadingStateBuf.swap(tmpBuf);
421 2 : mListener = aCallback;
422 : // mBuf contains no data but we set datasize to size of the data that will
423 : // be read from the disk. No handle is allowed to access the non-existent
424 : // data until reading finishes, but data can be appended or overwritten.
425 : // These pieces are tracked in mValidityMap and will be merged with the data
426 : // read from disk in OnDataRead().
427 2 : mBuf->SetDataSize(aLen);
428 2 : mExpectedHash = aHash;
429 : }
430 :
431 2 : return rv;
432 : }
433 :
434 : nsresult
435 2 : CacheFileChunk::Write(CacheFileHandle *aHandle,
436 : CacheFileChunkListener *aCallback)
437 : {
438 2 : AssertOwnsLock();
439 :
440 2 : LOG(("CacheFileChunk::Write() [this=%p, handle=%p, listener=%p]",
441 : this, aHandle, aCallback));
442 :
443 2 : MOZ_ASSERT(mState == READY);
444 2 : MOZ_ASSERT(NS_SUCCEEDED(mStatus));
445 2 : MOZ_ASSERT(!mWritingStateHandle);
446 2 : MOZ_ASSERT(mBuf->DataSize()); // Don't write chunk when it is empty
447 2 : MOZ_ASSERT(mBuf->ReadHandlesCount() == 0);
448 2 : MOZ_ASSERT(!mBuf->WriteHandleExists());
449 :
450 : nsresult rv;
451 :
452 2 : mState = WRITING;
453 4 : mWritingStateHandle = new CacheFileChunkReadHandle(mBuf);
454 :
455 4 : rv = CacheFileIOManager::Write(aHandle, mIndex * kChunkSize,
456 : mWritingStateHandle->Buf(),
457 2 : mWritingStateHandle->DataSize(),
458 2 : false, false, this);
459 2 : if (NS_WARN_IF(NS_FAILED(rv))) {
460 0 : mWritingStateHandle = nullptr;
461 0 : SetError(rv);
462 : } else {
463 2 : mListener = aCallback;
464 2 : mIsDirty = false;
465 : }
466 :
467 2 : return rv;
468 : }
469 :
470 : void
471 0 : CacheFileChunk::WaitForUpdate(CacheFileChunkListener *aCallback)
472 : {
473 0 : AssertOwnsLock();
474 :
475 0 : LOG(("CacheFileChunk::WaitForUpdate() [this=%p, listener=%p]",
476 : this, aCallback));
477 :
478 0 : MOZ_ASSERT(mFile->mOutput);
479 0 : MOZ_ASSERT(IsReady());
480 :
481 : #ifdef DEBUG
482 0 : for (uint32_t i = 0 ; i < mUpdateListeners.Length() ; i++) {
483 0 : MOZ_ASSERT(mUpdateListeners[i]->mCallback != aCallback);
484 : }
485 : #endif
486 :
487 0 : ChunkListenerItem *item = new ChunkListenerItem();
488 0 : item->mTarget = CacheFileIOManager::IOTarget();
489 0 : if (!item->mTarget) {
490 0 : LOG(("CacheFileChunk::WaitForUpdate() - Cannot get Cache I/O thread! Using "
491 : "main thread for callback."));
492 0 : item->mTarget = GetMainThreadEventTarget();
493 : }
494 0 : item->mCallback = aCallback;
495 0 : MOZ_ASSERT(item->mTarget);
496 0 : item->mCallback = aCallback;
497 :
498 0 : mUpdateListeners.AppendElement(item);
499 0 : }
500 :
501 : nsresult
502 0 : CacheFileChunk::CancelWait(CacheFileChunkListener *aCallback)
503 : {
504 0 : AssertOwnsLock();
505 :
506 0 : LOG(("CacheFileChunk::CancelWait() [this=%p, listener=%p]", this, aCallback));
507 :
508 0 : MOZ_ASSERT(IsReady());
509 :
510 : uint32_t i;
511 0 : for (i = 0 ; i < mUpdateListeners.Length() ; i++) {
512 0 : ChunkListenerItem *item = mUpdateListeners[i];
513 :
514 0 : if (item->mCallback == aCallback) {
515 0 : mUpdateListeners.RemoveElementAt(i);
516 0 : delete item;
517 0 : break;
518 : }
519 : }
520 :
521 : #ifdef DEBUG
522 0 : for ( ; i < mUpdateListeners.Length() ; i++) {
523 0 : MOZ_ASSERT(mUpdateListeners[i]->mCallback != aCallback);
524 : }
525 : #endif
526 :
527 0 : return NS_OK;
528 : }
529 :
530 : nsresult
531 4 : CacheFileChunk::NotifyUpdateListeners()
532 : {
533 4 : AssertOwnsLock();
534 :
535 4 : LOG(("CacheFileChunk::NotifyUpdateListeners() [this=%p]", this));
536 :
537 4 : MOZ_ASSERT(IsReady());
538 :
539 : nsresult rv, rv2;
540 :
541 4 : rv = NS_OK;
542 4 : for (uint32_t i = 0 ; i < mUpdateListeners.Length() ; i++) {
543 0 : ChunkListenerItem *item = mUpdateListeners[i];
544 :
545 0 : LOG(("CacheFileChunk::NotifyUpdateListeners() - Notifying listener %p "
546 : "[this=%p]", item->mCallback.get(), this));
547 :
548 0 : RefPtr<NotifyUpdateListenerEvent> ev;
549 0 : ev = new NotifyUpdateListenerEvent(item->mCallback, this);
550 0 : rv2 = item->mTarget->Dispatch(ev, NS_DISPATCH_NORMAL);
551 0 : if (NS_FAILED(rv2) && NS_SUCCEEDED(rv))
552 0 : rv = rv2;
553 0 : delete item;
554 : }
555 :
556 4 : mUpdateListeners.Clear();
557 :
558 4 : return rv;
559 : }
560 :
561 : uint32_t
562 96 : CacheFileChunk::Index() const
563 : {
564 96 : return mIndex;
565 : }
566 :
567 : CacheHash::Hash16_t
568 4 : CacheFileChunk::Hash() const
569 : {
570 4 : MOZ_ASSERT(IsReady());
571 :
572 4 : return CacheHash::Hash16(mBuf->Buf(), mBuf->DataSize());
573 : }
574 :
575 : uint32_t
576 4 : CacheFileChunk::DataSize() const
577 : {
578 4 : return mBuf->DataSize();
579 : }
580 :
581 : void
582 2 : CacheFileChunk::UpdateDataSize(uint32_t aOffset, uint32_t aLen)
583 : {
584 2 : AssertOwnsLock();
585 :
586 : // UpdateDataSize() is called only when we've written some data to the chunk
587 : // and we never write data anymore once some error occurs.
588 2 : MOZ_ASSERT(NS_SUCCEEDED(mStatus));
589 :
590 2 : LOG(("CacheFileChunk::UpdateDataSize() [this=%p, offset=%d, len=%d]",
591 : this, aOffset, aLen));
592 :
593 2 : mIsDirty = true;
594 :
595 2 : int64_t fileSize = static_cast<int64_t>(kChunkSize) * mIndex + aOffset + aLen;
596 2 : bool notify = false;
597 :
598 2 : if (fileSize > mFile->mDataSize) {
599 2 : mFile->mDataSize = fileSize;
600 2 : notify = true;
601 : }
602 :
603 2 : if (mState == READY || mState == WRITING) {
604 2 : MOZ_ASSERT(mValidityMap.Length() == 0);
605 :
606 2 : if (notify) {
607 2 : NotifyUpdateListeners();
608 : }
609 :
610 2 : return;
611 : }
612 :
613 : // We're still waiting for data from the disk. This chunk cannot be used by
614 : // input stream, so there must be no update listener. We also need to keep
615 : // track of where the data is written so that we can correctly merge the new
616 : // data with the old one.
617 :
618 0 : MOZ_ASSERT(mUpdateListeners.Length() == 0);
619 0 : MOZ_ASSERT(mState == READING);
620 :
621 0 : mValidityMap.AddPair(aOffset, aLen);
622 0 : mValidityMap.Log();
623 : }
624 :
625 : nsresult
626 0 : CacheFileChunk::Truncate(uint32_t aOffset)
627 : {
628 0 : MOZ_RELEASE_ASSERT(mState == READY || mState == WRITING || mState == READING);
629 :
630 0 : if (mState == READING) {
631 0 : mIsDirty = true;
632 : }
633 :
634 0 : mBuf->SetDataSize(aOffset);
635 0 : return NS_OK;
636 : }
637 :
638 : nsresult
639 0 : CacheFileChunk::OnFileOpened(CacheFileHandle *aHandle, nsresult aResult)
640 : {
641 0 : MOZ_CRASH("CacheFileChunk::OnFileOpened should not be called!");
642 : return NS_ERROR_UNEXPECTED;
643 : }
644 :
645 : nsresult
646 2 : CacheFileChunk::OnDataWritten(CacheFileHandle *aHandle, const char *aBuf,
647 : nsresult aResult)
648 : {
649 2 : LOG(("CacheFileChunk::OnDataWritten() [this=%p, handle=%p, result=0x%08" PRIx32 "]",
650 : this, aHandle, static_cast<uint32_t>(aResult)));
651 :
652 4 : nsCOMPtr<CacheFileChunkListener> listener;
653 :
654 : {
655 4 : CacheFileAutoLock lock(mFile);
656 :
657 2 : MOZ_ASSERT(mState == WRITING);
658 2 : MOZ_ASSERT(mListener);
659 :
660 2 : mWritingStateHandle = nullptr;
661 :
662 2 : if (NS_WARN_IF(NS_FAILED(aResult))) {
663 0 : SetError(aResult);
664 : }
665 :
666 2 : mState = READY;
667 2 : mListener.swap(listener);
668 : }
669 :
670 2 : listener->OnChunkWritten(aResult, this);
671 :
672 4 : return NS_OK;
673 : }
674 :
675 : nsresult
676 2 : CacheFileChunk::OnDataRead(CacheFileHandle *aHandle, char *aBuf,
677 : nsresult aResult)
678 : {
679 2 : LOG(("CacheFileChunk::OnDataRead() [this=%p, handle=%p, result=0x%08" PRIx32 "]",
680 : this, aHandle, static_cast<uint32_t>(aResult)));
681 :
682 4 : nsCOMPtr<CacheFileChunkListener> listener;
683 :
684 : {
685 4 : CacheFileAutoLock lock(mFile);
686 :
687 2 : MOZ_ASSERT(mState == READING);
688 2 : MOZ_ASSERT(mListener);
689 2 : MOZ_ASSERT(mReadingStateBuf);
690 2 : MOZ_RELEASE_ASSERT(mBuf->ReadHandlesCount() == 0);
691 2 : MOZ_RELEASE_ASSERT(!mBuf->WriteHandleExists());
692 :
693 4 : RefPtr<CacheFileChunkBuffer> tmpBuf;
694 2 : tmpBuf.swap(mReadingStateBuf);
695 :
696 2 : if (NS_SUCCEEDED(aResult)) {
697 2 : CacheHash::Hash16_t hash = CacheHash::Hash16(tmpBuf->Buf(),
698 2 : tmpBuf->DataSize());
699 2 : if (hash != mExpectedHash) {
700 0 : LOG(("CacheFileChunk::OnDataRead() - Hash mismatch! Hash of the data is"
701 : " %hx, hash in metadata is %hx. [this=%p, idx=%d]",
702 : hash, mExpectedHash, this, mIndex));
703 0 : aResult = NS_ERROR_FILE_CORRUPTED;
704 : } else {
705 2 : if (mBuf->DataSize() < tmpBuf->DataSize()) {
706 : // Truncate() was called while the data was being read.
707 0 : tmpBuf->SetDataSize(mBuf->DataSize());
708 : }
709 :
710 2 : if (!mBuf->Buf()) {
711 : // Just swap the buffers if mBuf is still empty
712 2 : mBuf.swap(tmpBuf);
713 : } else {
714 0 : LOG(("CacheFileChunk::OnDataRead() - Merging buffers. [this=%p]",
715 : this));
716 :
717 0 : mValidityMap.Log();
718 0 : aResult = mBuf->FillInvalidRanges(tmpBuf, &mValidityMap);
719 0 : mValidityMap.Clear();
720 : }
721 : }
722 : }
723 :
724 2 : if (NS_FAILED(aResult)) {
725 0 : aResult = mIndex ? NS_ERROR_FILE_CORRUPTED : NS_ERROR_FILE_NOT_FOUND;
726 0 : SetError(aResult);
727 0 : mBuf->SetDataSize(0);
728 : }
729 :
730 2 : mState = READY;
731 2 : mListener.swap(listener);
732 : }
733 :
734 2 : listener->OnChunkRead(aResult, this);
735 :
736 4 : return NS_OK;
737 : }
738 :
739 : nsresult
740 0 : CacheFileChunk::OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult)
741 : {
742 0 : MOZ_CRASH("CacheFileChunk::OnFileDoomed should not be called!");
743 : return NS_ERROR_UNEXPECTED;
744 : }
745 :
746 : nsresult
747 0 : CacheFileChunk::OnEOFSet(CacheFileHandle *aHandle, nsresult aResult)
748 : {
749 0 : MOZ_CRASH("CacheFileChunk::OnEOFSet should not be called!");
750 : return NS_ERROR_UNEXPECTED;
751 : }
752 :
753 : nsresult
754 0 : CacheFileChunk::OnFileRenamed(CacheFileHandle *aHandle, nsresult aResult)
755 : {
756 0 : MOZ_CRASH("CacheFileChunk::OnFileRenamed should not be called!");
757 : return NS_ERROR_UNEXPECTED;
758 : }
759 :
760 : bool
761 6 : CacheFileChunk::IsKilled()
762 : {
763 6 : return mFile->IsKilled();
764 : }
765 :
766 : bool
767 21 : CacheFileChunk::IsReady() const
768 : {
769 21 : return (NS_SUCCEEDED(mStatus) && (mState == READY || mState == WRITING));
770 : }
771 :
772 : bool
773 9 : CacheFileChunk::IsDirty() const
774 : {
775 9 : AssertOwnsLock();
776 :
777 9 : return mIsDirty;
778 : }
779 :
780 : nsresult
781 7 : CacheFileChunk::GetStatus()
782 : {
783 7 : AssertOwnsLock();
784 :
785 7 : return mStatus;
786 : }
787 :
788 : void
789 0 : CacheFileChunk::SetError(nsresult aStatus)
790 : {
791 0 : LOG(("CacheFileChunk::SetError() [this=%p, status=0x%08" PRIx32 "]",
792 : this, static_cast<uint32_t>(aStatus)));
793 :
794 0 : MOZ_ASSERT(NS_FAILED(aStatus));
795 :
796 0 : if (NS_FAILED(mStatus)) {
797 : // Remember only the first error code.
798 0 : return;
799 : }
800 :
801 0 : mStatus = aStatus;
802 : }
803 :
804 : CacheFileChunkReadHandle
805 11 : CacheFileChunk::GetReadHandle()
806 : {
807 11 : LOG(("CacheFileChunk::GetReadHandle() [this=%p]", this));
808 :
809 11 : AssertOwnsLock();
810 :
811 11 : MOZ_RELEASE_ASSERT(mState == READY || mState == WRITING);
812 : // We don't release the lock when writing the data and CacheFileOutputStream
813 : // doesn't get the read handle, so there cannot be a write handle when read
814 : // handle is obtained.
815 11 : MOZ_RELEASE_ASSERT(!mBuf->WriteHandleExists());
816 :
817 11 : return CacheFileChunkReadHandle(mBuf);
818 : }
819 :
820 : CacheFileChunkWriteHandle
821 2 : CacheFileChunk::GetWriteHandle(uint32_t aEnsuredBufSize)
822 : {
823 2 : LOG(("CacheFileChunk::GetWriteHandle() [this=%p, ensuredBufSize=%u]",
824 : this, aEnsuredBufSize));
825 :
826 2 : AssertOwnsLock();
827 :
828 2 : if (NS_FAILED(mStatus)) {
829 0 : return CacheFileChunkWriteHandle(nullptr); // dummy handle
830 : }
831 :
832 : nsresult rv;
833 :
834 : // We don't support multiple write handles
835 2 : MOZ_RELEASE_ASSERT(!mBuf->WriteHandleExists());
836 :
837 2 : if (mBuf->ReadHandlesCount()) {
838 0 : LOG(("CacheFileChunk::GetWriteHandle() - cloning buffer because of existing"
839 : " read handle"));
840 :
841 0 : MOZ_RELEASE_ASSERT(mState != READING);
842 0 : RefPtr<CacheFileChunkBuffer> newBuf = new CacheFileChunkBuffer(this);
843 0 : rv = newBuf->EnsureBufSize(std::max(aEnsuredBufSize, mBuf->DataSize()));
844 0 : if (NS_SUCCEEDED(rv)) {
845 0 : newBuf->CopyFrom(mBuf);
846 0 : mOldBufs.AppendElement(mBuf);
847 0 : mBuf = newBuf;
848 : }
849 : } else {
850 2 : rv = mBuf->EnsureBufSize(aEnsuredBufSize);
851 : }
852 :
853 2 : if (NS_FAILED(rv)) {
854 0 : SetError(NS_ERROR_OUT_OF_MEMORY);
855 0 : return CacheFileChunkWriteHandle(nullptr); // dummy handle
856 : }
857 :
858 2 : return CacheFileChunkWriteHandle(mBuf);
859 : }
860 :
861 : // Memory reporting
862 :
863 : size_t
864 0 : CacheFileChunk::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
865 : {
866 0 : size_t n = mBuf->SizeOfIncludingThis(mallocSizeOf);
867 :
868 0 : if (mReadingStateBuf) {
869 0 : n += mReadingStateBuf->SizeOfIncludingThis(mallocSizeOf);
870 : }
871 :
872 0 : for (uint32_t i = 0; i < mOldBufs.Length(); ++i) {
873 0 : n += mOldBufs[i]->SizeOfIncludingThis(mallocSizeOf);
874 : }
875 :
876 0 : n += mValidityMap.SizeOfExcludingThis(mallocSizeOf);
877 :
878 0 : return n;
879 : }
880 :
881 : size_t
882 0 : CacheFileChunk::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const
883 : {
884 0 : return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
885 : }
886 :
887 : bool
888 4 : CacheFileChunk::CanAllocate(uint32_t aSize) const
889 : {
890 4 : if (!mLimitAllocation) {
891 2 : return true;
892 : }
893 :
894 2 : LOG(("CacheFileChunk::CanAllocate() [this=%p, size=%u]", this, aSize));
895 :
896 2 : uint32_t limit = CacheObserver::MaxDiskChunksMemoryUsage(mIsPriority);
897 2 : if (limit == 0) {
898 0 : return true;
899 : }
900 :
901 2 : uint32_t usage = ChunksMemoryUsage();
902 2 : if (usage + aSize > limit) {
903 0 : LOG(("CacheFileChunk::CanAllocate() - Returning false. [this=%p]", this));
904 0 : return false;
905 : }
906 :
907 2 : return true;
908 : }
909 :
910 : void
911 8 : CacheFileChunk::BuffersAllocationChanged(uint32_t aFreed, uint32_t aAllocated)
912 : {
913 8 : uint32_t oldBuffersSize = mBuffersSize;
914 8 : mBuffersSize += aAllocated;
915 8 : mBuffersSize -= aFreed;
916 :
917 8 : DoMemoryReport(sizeof(CacheFileChunk) + mBuffersSize);
918 :
919 8 : if (!mLimitAllocation) {
920 4 : return;
921 : }
922 :
923 4 : ChunksMemoryUsage() -= oldBuffersSize;
924 4 : ChunksMemoryUsage() += mBuffersSize;
925 4 : LOG(("CacheFileChunk::BuffersAllocationChanged() - %s chunks usage %u "
926 : "[this=%p]", mIsPriority ? "Priority" : "Normal",
927 : static_cast<uint32_t>(ChunksMemoryUsage()), this));
928 : }
929 :
930 10 : mozilla::Atomic<uint32_t, ReleaseAcquire>& CacheFileChunk::ChunksMemoryUsage() const
931 : {
932 : static mozilla::Atomic<uint32_t, ReleaseAcquire> chunksMemoryUsage(0);
933 : static mozilla::Atomic<uint32_t, ReleaseAcquire> prioChunksMemoryUsage(0);
934 10 : return mIsPriority ? prioChunksMemoryUsage : chunksMemoryUsage;
935 : }
936 :
937 : } // namespace net
938 : } // namespace mozilla
|