/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// When CACHE_CHUNKS is defined we always cache unused chunks in mCacheChunks. // When it is not defined, we always release the chunks ASAP, i.e. we cache // unused chunks only when: // - CacheFile is memory-only // - CacheFile is still waiting for the handle // - the chunk is preloaded
NS_IMETHOD OnFileOpened(CacheFileHandle* aHandle, nsresult aResult) override {
MOZ_CRASH("DoomFileHelper::OnFileOpened should not be called!"); return NS_ERROR_UNEXPECTED;
}
NS_IMETHOD OnDataWritten(CacheFileHandle* aHandle, constchar* aBuf,
nsresult aResult) override {
MOZ_CRASH("DoomFileHelper::OnDataWritten should not be called!"); return NS_ERROR_UNEXPECTED;
}
NS_IMETHOD OnDataRead(CacheFileHandle* aHandle, char* aBuf,
nsresult aResult) override {
MOZ_CRASH("DoomFileHelper::OnDataRead should not be called!"); return NS_ERROR_UNEXPECTED;
}
NS_IMETHOD OnEOFSet(CacheFileHandle* aHandle, nsresult aResult) override {
MOZ_CRASH("DoomFileHelper::OnEOFSet should not be called!"); return NS_ERROR_UNEXPECTED;
}
NS_IMETHOD OnFileRenamed(CacheFileHandle* aHandle,
nsresult aResult) override {
MOZ_CRASH("DoomFileHelper::OnFileRenamed should not be called!"); return NS_ERROR_UNEXPECTED;
}
MutexAutoLock lock(mLock->Lock()); if (!mMemoryOnly && mReady && !mKill) { // mReady flag indicates we have metadata plus in a valid state.
WriteMetadataIfNeededLocked(true);
}
}
// Some consumers (at least nsHTTPCompressConv) assume that Read() can read // such amount of data that was announced by Available(). // CacheFileInputStream::Available() uses also preloaded chunks to compute // number of available bytes in the input stream, so we have to make sure the // preloadChunkCount won't change during CacheFile's lifetime since otherwise // we could potentially release some cached chunks that was used to calculate // available bytes but would not be available later during call to // CacheFileInputStream::Read().
mPreloadChunkCount = CacheObserver::PreloadChunkCount();
// make sure we can use this entry immediately
mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, mPinned, mKey,
WrapNotNull(mLock));
mReady = true;
mDataSize = mMetadata->Offset();
} else {
flags = CacheFileIOManager::CREATE;
}
if (mPriority) {
flags |= CacheFileIOManager::PRIORITY;
}
if (mPinned) {
flags |= CacheFileIOManager::PINNED;
}
if (mPinned) {
LOG(
("CacheFile::Init() - CacheFileIOManager::OpenFile() failed " "but we want to pin, fail the file opening. [this=%p]", this)); return NS_ERROR_NOT_AVAILABLE;
}
if (aCreateNew) {
NS_WARNING("Forcing memory-only entry since OpenFile failed");
LOG(
("CacheFile::Init() - CacheFileIOManager::OpenFile() failed " "synchronously. We can continue in memory-only mode since " "aCreateNew == true. [this=%p]", this));
if (aChunk->mDiscardedChunk) { // We discard only unused chunks, so it must be still unused when reading // data finishes.
MOZ_ASSERT(aChunk->mRefCnt == 2);
aChunk->mActiveChunk = false;
ReleaseOutsideLock(
RefPtr<CacheFileChunkListener>(std::move(aChunk->mFile)));
nsresult CacheFile::OnChunkWritten(nsresult aResult, CacheFileChunk* aChunk) { // In case the chunk was reused, made dirty and released between calls to // CacheFileChunk::Write() and CacheFile::OnChunkWritten(), we must write // the chunk to the disk again. When the chunk is unused and is dirty simply // addref and release (outside the lock) the chunk which ensures that // CacheFile::DeactivateChunk() will be called again.
RefPtr<CacheFileChunk> deactivateChunkAgain;
if (aChunk->mDiscardedChunk) { // We discard only unused chunks, so it must be still unused when writing // data finishes.
MOZ_ASSERT(aChunk->mRefCnt == 2);
aChunk->mActiveChunk = false;
ReleaseOutsideLock(
RefPtr<CacheFileChunkListener>(std::move(aChunk->mFile)));
if (NS_SUCCEEDED(aResult) && !aChunk->IsDirty()) { // update hash value in metadata
mMetadata->SetHash(aChunk->Index(), aChunk->Hash());
}
// notify listeners if there is any if (HaveChunkListeners(aChunk->Index())) { // don't release the chunk since there are some listeners queued
rv = NotifyChunkListeners(aChunk->Index(), aResult, aChunk); if (NS_SUCCEEDED(rv)) {
MOZ_ASSERT(aChunk->mRefCnt != 2); return NS_OK;
}
}
if (aChunk->mRefCnt != 2) {
LOG(
("CacheFile::OnChunkWritten() - Chunk is still used [this=%p, chunk=%p," " refcnt=%" PRIuPTR "]", this, aChunk, aChunk->mRefCnt.get()));
return NS_OK;
}
if (aChunk->IsDirty()) {
LOG(
("CacheFile::OnChunkWritten() - Unused chunk is dirty. We must go " "through deactivation again. [this=%p, chunk=%p]", this, aChunk));
nsresult CacheFile::OnChunkAvailable(nsresult aResult, uint32_t aChunkIdx,
CacheFileChunk* aChunk) {
MOZ_CRASH("CacheFile::OnChunkAvailable should not be called!"); return NS_ERROR_UNEXPECTED;
}
nsresult CacheFile::OnChunkUpdated(CacheFileChunk* aChunk) {
MOZ_CRASH("CacheFile::OnChunkUpdated should not be called!"); return NS_ERROR_UNEXPECTED;
}
nsresult CacheFile::OnFileOpened(CacheFileHandle* aHandle, nsresult aResult) { // Using an 'auto' class to perform doom or fail the listener // outside the CacheFile's lock. class AutoFailDoomListener { public: explicit AutoFailDoomListener(CacheFileHandle* aHandle)
: mHandle(aHandle), mAlreadyDoomed(false) {}
~AutoFailDoomListener() { if (!mListener) return;
if (mMemoryOnly) { // We can be here only in case the entry was initilized as createNew and // SetMemoryOnly() was called.
// Just don't store the handle into mHandle and exit
autoDoom.mAlreadyDoomed = true; return NS_OK;
}
if (NS_FAILED(aResult)) { if (mMetadata) { // This entry was initialized as createNew, just switch to memory-only // mode.
NS_WARNING("Forcing memory-only entry since OpenFile failed");
LOG(
("CacheFile::OnFileOpened() - CacheFileIOManager::OpenFile() " "failed asynchronously. We can continue in memory-only mode since " "aCreateNew == true. [this=%p]", this));
mMemoryOnly = true; return NS_OK;
}
if (aResult == NS_ERROR_FILE_INVALID_PATH) { // CacheFileIOManager doesn't have mCacheDirectory, switch to // memory-only mode.
NS_WARNING( "Forcing memory-only entry since CacheFileIOManager doesn't " "have mCacheDirectory.");
LOG(
("CacheFile::OnFileOpened() - CacheFileIOManager doesn't have " "mCacheDirectory, initializing entry as memory-only. [this=%p]", this));
// mMetaData is protected by a lock, but ReadMetaData has to be called // without the lock. Alternatively we could make a // "ReadMetaDataLocked", and temporarily unlock to call OnFileReady
metadata = mMetadata = new CacheFileMetadata(mHandle, mKey, WrapNotNull(mLock));
}
metadata->ReadMetadata(this); return NS_OK;
}
nsresult CacheFile::OnDataWritten(CacheFileHandle* aHandle, constchar* aBuf,
nsresult aResult) {
MOZ_CRASH("CacheFile::OnDataWritten should not be called!"); return NS_ERROR_UNEXPECTED;
}
nsresult CacheFile::OnDataRead(CacheFileHandle* aHandle, char* aBuf,
nsresult aResult) {
MOZ_CRASH("CacheFile::OnDataRead should not be called!"); return NS_ERROR_UNEXPECTED;
}
nsresult CacheFile::OnEOFSet(CacheFileHandle* aHandle, nsresult aResult) {
MOZ_CRASH("CacheFile::OnEOFSet should not be called!"); return NS_ERROR_UNEXPECTED;
}
nsresult CacheFile::OnFileRenamed(CacheFileHandle* aHandle, nsresult aResult) {
MOZ_CRASH("CacheFile::OnFileRenamed should not be called!"); return NS_ERROR_UNEXPECTED;
}
bool CacheFile::IsKilled() { bool killed = mKill; if (killed) {
LOG(("CacheFile is killed, this=%p", this));
}
if (!mReady) {
LOG(("CacheFile::OpenInputStream() - CacheFile is not ready [this=%p]", this));
return NS_ERROR_NOT_AVAILABLE;
}
if (NS_FAILED(mStatus)) {
LOG(
("CacheFile::OpenInputStream() - CacheFile is in a failure state " "[this=%p, status=0x%08" PRIx32 "]", this, static_cast<uint32_t>(mStatus)));
// Don't allow opening the input stream when this CacheFile is in // a failed state. This is the only way to protect consumers correctly // from reading a broken entry. When the file is in the failed state, // it's also doomed, so reopening the entry won't make any difference - // data will still be inaccessible anymore. Note that for just doomed // files, we must allow reading the data. return mStatus;
}
// Once we open input stream we no longer allow preloading of chunks without // input stream, i.e. we will no longer keep first few chunks preloaded when // the last input stream is closed.
mPreloadWithoutInputStreams = false;
CacheFileInputStream* input = new CacheFileInputStream(this, aEntryHandle, false);
LOG(("CacheFile::OpenInputStream() - Creating new input stream %p [this=%p]",
input, this));
if (NS_WARN_IF(!mReady)) {
LOG(
("CacheFile::OpenAlternativeInputStream() - CacheFile is not ready " "[this=%p]", this)); return NS_ERROR_NOT_AVAILABLE;
}
if (mAltDataOffset == -1) {
LOG(
("CacheFile::OpenAlternativeInputStream() - Alternative data is not " "available [this=%p]", this)); return NS_ERROR_NOT_AVAILABLE;
}
if (NS_FAILED(mStatus)) {
LOG(
("CacheFile::OpenAlternativeInputStream() - CacheFile is in a failure " "state [this=%p, status=0x%08" PRIx32 "]", this, static_cast<uint32_t>(mStatus)));
// Don't allow opening the input stream when this CacheFile is in // a failed state. This is the only way to protect consumers correctly // from reading a broken entry. When the file is in the failed state, // it's also doomed, so reopening the entry won't make any difference - // data will still be inaccessible anymore. Note that for just doomed // files, we must allow reading the data. return mStatus;
}
if (mAltDataType != aAltDataType) {
LOG(
("CacheFile::OpenAlternativeInputStream() - Alternative data is of a " "different type than requested [this=%p, availableType=%s, " "requestedType=%s]", this, mAltDataType.get(), aAltDataType)); return NS_ERROR_NOT_AVAILABLE;
}
// Once we open input stream we no longer allow preloading of chunks without // input stream, i.e. we will no longer keep first few chunks preloaded when // the last input stream is closed.
mPreloadWithoutInputStreams = false;
CacheFileInputStream* input = new CacheFileInputStream(this, aEntryHandle, true);
if (!mReady) {
LOG(("CacheFile::OpenOutputStream() - CacheFile is not ready [this=%p]", this));
return NS_ERROR_NOT_AVAILABLE;
}
if (mOutput) {
LOG(
("CacheFile::OpenOutputStream() - We already have output stream %p " "[this=%p]",
mOutput, this));
return NS_ERROR_NOT_AVAILABLE;
}
if (NS_FAILED(mStatus)) {
LOG(
("CacheFile::OpenOutputStream() - CacheFile is in a failure state " "[this=%p, status=0x%08" PRIx32 "]", this, static_cast<uint32_t>(mStatus)));
// The CacheFile is already doomed. It make no sense to allow to write any // data to such entry. return mStatus;
}
// Fail if there is any input stream opened for alternative data for (uint32_t i = 0; i < mInputs.Length(); ++i) { if (mInputs[i]->IsAlternativeData()) { return NS_ERROR_NOT_AVAILABLE;
}
}
// Once we open output stream we no longer allow preloading of chunks without // input stream. There is no reason to believe that some input stream will be // opened soon. Otherwise we would cache unused chunks of all newly created // entries until the CacheFile is destroyed.
mPreloadWithoutInputStreams = false;
mOutput = new CacheFileOutputStream(this, aCloseListener, false);
if (!mReady) {
LOG(
("CacheFile::OpenAlternativeOutputStream() - CacheFile is not ready " "[this=%p]", this));
return NS_ERROR_NOT_AVAILABLE;
}
if (mOutput) {
LOG(
("CacheFile::OpenAlternativeOutputStream() - We already have output " "stream %p [this=%p]",
mOutput, this));
return NS_ERROR_NOT_AVAILABLE;
}
if (NS_FAILED(mStatus)) {
LOG(
("CacheFile::OpenAlternativeOutputStream() - CacheFile is in a failure " "state [this=%p, status=0x%08" PRIx32 "]", this, static_cast<uint32_t>(mStatus)));
// The CacheFile is already doomed. It make no sense to allow to write any // data to such entry. return mStatus;
}
// Fail if there is any input stream opened for alternative data for (uint32_t i = 0; i < mInputs.Length(); ++i) { if (mInputs[i]->IsAlternativeData()) { return NS_ERROR_NOT_AVAILABLE;
}
}
nsresult rv;
if (mAltDataOffset != -1) { // Truncate old alt-data
rv = Truncate(mAltDataOffset); if (NS_FAILED(rv)) {
LOG(
("CacheFile::OpenAlternativeOutputStream() - Truncating old alt-data " "failed [rv=0x%08" PRIx32 "]", static_cast<uint32_t>(rv))); return rv;
}
} else {
mAltDataOffset = mDataSize;
}
nsAutoCString altMetadata;
CacheFileUtils::BuildAlternativeDataInfo(aAltDataType, mAltDataOffset,
altMetadata);
rv = SetAltMetadata(altMetadata.get()); if (NS_FAILED(rv)) {
LOG(
("CacheFile::OpenAlternativeOutputStream() - Set Metadata for alt-data" "failed [rv=0x%08" PRIx32 "]", static_cast<uint32_t>(rv))); return rv;
}
// Once we open output stream we no longer allow preloading of chunks without // input stream. There is no reason to believe that some input stream will be // opened soon. Otherwise we would cache unused chunks of all newly created // entries until the CacheFile is destroyed.
mPreloadWithoutInputStreams = false;
mOutput = new CacheFileOutputStream(this, aCloseListener, true);
if (mMemoryOnly) { // This method should not be called when the CacheFile was initialized as // memory-only, but it can be called when CacheFile end up as memory-only // due to e.g. IO failure since CacheEntry doesn't know it.
LOG(
("CacheFile::ThrowMemoryCachedData() - Ignoring request because the " "entry is memory-only. [this=%p]", this));
return NS_ERROR_NOT_AVAILABLE;
}
if (mOpeningFile) { // mayhemer, note: we shouldn't get here, since CacheEntry prevents loading // entries from being purged.
LOG(
("CacheFile::ThrowMemoryCachedData() - Ignoring request because the " "entry is still opening the file [this=%p]", this));
return NS_ERROR_ABORT;
}
// We cannot release all cached chunks since we need to keep preloaded chunks // in memory. See initialization of mPreloadChunkCount for explanation.
CleanUpCachedChunks();
if (!strcmp(aKey, CacheFileUtils::kAltDataKey)) {
NS_ERROR( "alt-data element is reserved for internal use and must not be " "changed via CacheFile::SetElement()"); return NS_ERROR_FAILURE;
}
// Save the content type to metadata for case we need to rebuild the index.
nsAutoCString contentType;
contentType.AppendInt(aContentType);
nsresult rv = mMetadata->SetElement("ctid", contentType.get()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv;
}
if (NS_FAILED(rv)) { // Removing element shouldn't fail because it doesn't allocate memory.
mMetadata->SetElement(CacheFileUtils::kAltDataKey, nullptr);
// Preload chunks from disk when this is disk backed entry and the listener // is reader. bool preload = !mMemoryOnly && (aCaller == READER);
nsresult rv;
RefPtr<CacheFileChunk> chunk; if (mChunks.Get(aIndex, getter_AddRefs(chunk))) {
LOG(("CacheFile::GetChunkLocked() - Found chunk %p in mChunks [this=%p]",
chunk.get(), this));
// Preloader calls this method to preload only non-loaded chunks.
MOZ_ASSERT(aCaller != PRELOADER, "Unexpected!");
// We might get failed chunk between releasing the lock in // CacheFileChunk::OnDataWritten/Read and CacheFile::OnChunkWritten/Read
rv = chunk->GetStatus(); if (NS_FAILED(rv)) {
SetError(rv);
LOG(
("CacheFile::GetChunkLocked() - Found failed chunk in mChunks " "[this=%p]", this)); return rv;
}
int64_t off = aIndex * static_cast<int64_t>(kChunkSize);
if (off < mDataSize) { // We cannot be here if this is memory only entry since the chunk must exist
MOZ_ASSERT(!mMemoryOnly); if (mMemoryOnly) { // If this ever really happen it is better to fail rather than crashing on // a null handle.
LOG(
("CacheFile::GetChunkLocked() - Unexpected state! Offset < mDataSize " "for memory-only entry. [this=%p, off=%" PRId64 ", mDataSize=%" PRId64 "]", this, off, mDataSize));
LOG(
("CacheFile::GetChunkLocked() - Reading newly created chunk %p from " "the disk [this=%p]",
chunk.get(), this));
// Read the chunk from the disk
rv = chunk->Read(mHandle,
std::min(static_cast<uint32_t>(mDataSize - off), static_cast<uint32_t>(kChunkSize)),
mMetadata->GetHash(aIndex), this); if (NS_WARN_IF(NS_FAILED(rv))) {
RemoveChunkInternal(chunk, false); return rv;
}
return NS_OK;
} if (off == mDataSize) { if (aCaller == WRITER) { // this listener is going to write to the chunk
chunk = new CacheFileChunk(this, aIndex, true);
mChunks.InsertOrUpdate(aIndex, RefPtr{chunk});
chunk->mActiveChunk = true;
LOG(("CacheFile::GetChunkLocked() - Created new empty chunk %p [this=%p]",
chunk.get(), this));
chunk.swap(*_retval); return NS_OK;
}
} else { if (aCaller == WRITER) { // this chunk was requested by writer, but we need to fill the gap first
// Fill with zero the last chunk if it is incomplete if (mDataSize % kChunkSize) {
rv = PadChunkWithZeroes(mDataSize / kChunkSize);
NS_ENSURE_SUCCESS(rv, rv);
MOZ_ASSERT(!(mDataSize % kChunkSize));
}
uint32_t startChunk = mDataSize / kChunkSize;
if (mMemoryOnly) { // We need to create all missing CacheFileChunks if this is memory-only // entry for (uint32_t i = startChunk; i < aIndex; i++) {
rv = PadChunkWithZeroes(i);
NS_ENSURE_SUCCESS(rv, rv);
}
} else { // We don't need to create CacheFileChunk for other empty chunks unless // there is some input stream waiting for this chunk.
if (startChunk != aIndex) { // Make sure the file contains zeroes at the end of the file
rv = CacheFileIOManager::TruncateSeekSetEOF(
mHandle, startChunk * kChunkSize, aIndex * kChunkSize, nullptr);
NS_ENSURE_SUCCESS(rv, rv);
}
for (uint32_t i = startChunk; i < aIndex; i++) { if (HaveChunkListeners(i)) {
rv = PadChunkWithZeroes(i);
NS_ENSURE_SUCCESS(rv, rv);
} else {
mMetadata->SetHash(i, kEmptyChunkHash);
mDataSize = (i + 1) * kChunkSize;
}
}
}
// We can be here only if the caller is reader since writer always create a // new chunk above and preloader calls this method to preload only chunks that // are not loaded but that do exist.
MOZ_ASSERT(aCaller == READER, "Unexpected!");
if (mOutput) { // the chunk doesn't exist but mOutput may create it
QueueChunkListener(aIndex, aCallback);
} else { return NS_ERROR_NOT_AVAILABLE;
}
RefPtr<CacheFileChunk> chunk;
GetChunkLocked(i, PRELOADER, nullptr, getter_AddRefs(chunk)); // We've checked that we don't have this chunk, so no chunk must be // returned.
MOZ_ASSERT(!chunk);
}
}
#ifdef CACHE_CHUNKS // We cache all chunks. returntrue; #else
if (mPreloadChunkCount != 0 && mInputs.Length() == 0 &&
mPreloadWithoutInputStreams && aIndex < mPreloadChunkCount) { // We don't have any input stream yet, but it is likely that some will be // opened soon. Keep first mPreloadChunkCount chunks in memory. The // condition is here instead of in MustKeepCachedChunk() since these // chunks should be preloaded and can be kept in memory as an optimization, // but they can be released at any time until they are considered as // preloaded chunks for any input stream. returntrue;
}
// Cache only chunks that we really need to keep. return MustKeepCachedChunk(aIndex); #endif
}
// We must keep the chunk when this is memory only entry or we don't have // a handle yet. if (mMemoryOnly || mOpeningFile) { returntrue;
}
if (mPreloadChunkCount == 0) { // Preloading of chunks is disabled returnfalse;
}
// Check whether this chunk should be considered as preloaded chunk for any // existing input stream.
// maxPos is the position of the last byte in the given chunk
int64_t maxPos = static_cast<int64_t>(aIndex + 1) * kChunkSize - 1;
// minPos is the position of the first byte in a chunk that precedes the given // chunk by mPreloadChunkCount chunks
int64_t minPos; if (mPreloadChunkCount >= aIndex) {
minPos = 0;
} else {
minPos = static_cast<int64_t>(aIndex - mPreloadChunkCount) * kChunkSize;
}
for (uint32_t i = 0; i < mInputs.Length(); ++i) {
int64_t inputPos = mInputs[i]->GetPosition(); if (inputPos >= minPos && inputPos <= maxPos) { returntrue;
}
}
#ifdef DEBUG
{ // We can be here iff the chunk is in the hash table
RefPtr<CacheFileChunk> chunkCheck;
mChunks.Get(chunk->Index(), getter_AddRefs(chunkCheck));
MOZ_ASSERT(chunkCheck == chunk);
// We also shouldn't have any queued listener for this chunk
ChunkListeners* listeners;
mChunkListeners.Get(chunk->Index(), &listeners);
MOZ_ASSERT(!listeners);
} #endif
if (NS_FAILED(chunk->GetStatus())) {
SetError(chunk->GetStatus());
}
if (NS_FAILED(mStatus)) { // Don't write any chunk to disk since this entry will be doomed
LOG(
("CacheFile::DeactivateChunk() - Releasing chunk because of status " "[this=%p, chunk=%p, mStatus=0x%08" PRIx32 "]", this, chunk.get(), static_cast<uint32_t>(mStatus)));
// Chunk will be removed in OnChunkWritten if it is still unused
// chunk needs to be released under the lock to be able to rely on // CacheFileChunk::mRefCnt in CacheFile::OnChunkWritten()
chunk = nullptr; return NS_OK;
}
// Index of the last existing chunk.
uint32_t lastChunk = (dataSize - 1) / kChunkSize; if (aIndex > lastChunk) { return 0;
}
// We can use only preloaded chunks for the given stream to calculate // available bytes if this is an entry stored on disk, since only those // chunks are guaranteed not to be released.
uint32_t maxPreloadedChunk; if (mMemoryOnly) {
maxPreloadedChunk = lastChunk;
} else {
maxPreloadedChunk = std::min(aIndex + mPreloadChunkCount, lastChunk);
}
uint32_t i; for (i = aIndex; i <= maxPreloadedChunk; ++i) {
CacheFileChunk* chunk;
chunk = mChunks.GetWeak(i); if (chunk) {
MOZ_ASSERT(i == lastChunk || chunk->DataSize() == kChunkSize); if (chunk->IsReady()) { continue;
}
// theoretic bytes in advance
int64_t advance = int64_t(i - aIndex) * kChunkSize; // real bytes till the end of the file
int64_t tail = dataSize - (aIndex * kChunkSize);
// If we ever need to truncate on non alt-data boundary, we need to handle // existing input streams.
MOZ_ASSERT(aOffset == mAltDataOffset, "Truncating normal data not implemented");
MOZ_ASSERT(mReady);
MOZ_ASSERT(!mOutput);
// Remove all truncated chunks from mCachedChunks for (auto iter = mCachedChunks.Iter(); !iter.Done(); iter.Next()) {
uint32_t idx = iter.Key();
if (idx > newLastChunk) { // This is unused chunk, simply remove it.
LOG(("CacheFile::Truncate() - removing cached chunk [idx=%u]", idx));
iter.Remove();
}
}
// We need to make sure no input stream holds a reference to a chunk we're // going to discard. In theory, if alt-data begins at chunk boundary, input // stream for normal data can get the chunk containing only alt-data via // EnsureCorrectChunk() call. The input stream won't read the data from such // chunk, but it will keep the reference until the stream is closed and we // cannot simply discard this chunk.
int64_t maxInputChunk = -1; for (uint32_t i = 0; i < mInputs.Length(); ++i) {
int64_t inputChunk = mInputs[i]->GetChunkIdx();
if (maxInputChunk < inputChunk) {
maxInputChunk = inputChunk;
}
MOZ_RELEASE_ASSERT(maxInputChunk <= newLastChunk + 1); if (maxInputChunk == newLastChunk + 1) { // Truncating must be done at chunk boundary
MOZ_RELEASE_ASSERT(bytesInNewLastChunk == kChunkSize);
newLastChunk++;
bytesInNewLastChunk = 0;
LOG(
("CacheFile::Truncate() - chunk %p is still in use, using " "newLastChunk=%u and bytesInNewLastChunk=%u",
mChunks.GetWeak(newLastChunk), newLastChunk, bytesInNewLastChunk));
}
// Discard all truncated chunks in mChunks for (auto iter = mChunks.Iter(); !iter.Done(); iter.Next()) {
uint32_t idx = iter.Key();
// Remove hashes of all removed chunks from the metadata for (uint32_t i = lastChunk; i > newLastChunk; --i) {
mMetadata->RemoveHash(i);
}
// Truncate new last chunk if (bytesInNewLastChunk == kChunkSize) {
LOG(("CacheFile::Truncate() - not truncating last chunk."));
} else {
RefPtr<CacheFileChunk> chunk; if (mChunks.Get(newLastChunk, getter_AddRefs(chunk))) {
LOG(("CacheFile::Truncate() - New last chunk %p got from mChunks.",
chunk.get()));
} elseif (mCachedChunks.Get(newLastChunk, getter_AddRefs(chunk))) {
LOG(("CacheFile::Truncate() - New last chunk %p got from mCachedChunks.",
chunk.get()));
} else { // New last chunk isn't loaded but we need to update the hash.
MOZ_ASSERT(!mMemoryOnly);
MOZ_ASSERT(mHandle);
rv = GetChunkLocked(newLastChunk, PRELOADER, nullptr,
getter_AddRefs(chunk)); if (NS_FAILED(rv)) { return rv;
} // We've checked that we don't have this chunk, so no chunk must be // returned.
MOZ_ASSERT(!chunk);
if (!mChunks.Get(newLastChunk, getter_AddRefs(chunk))) { return NS_ERROR_UNEXPECTED;
}
LOG(("CacheFile::Truncate() - New last chunk %p got from preloader.",
chunk.get()));
}
rv = chunk->GetStatus(); if (NS_FAILED(rv)) {
LOG(
("CacheFile::Truncate() - New last chunk is failed " "[status=0x%08" PRIx32 "]", static_cast<uint32_t>(rv))); return rv;
}
chunk->Truncate(bytesInNewLastChunk);
// If the chunk is ready set the new hash now. If it's still being loaded // CacheChunk::Truncate() made the chunk dirty and the hash will be updated // in OnChunkWritten(). if (chunk->IsReady()) {
mMetadata->SetHash(newLastChunk, chunk->Hash());
}
}
if (mHandle) {
rv = CacheFileIOManager::TruncateSeekSetEOF(mHandle, aOffset, aOffset,
nullptr); if (NS_FAILED(rv)) { return rv;
}
}
switch (aStatus) { case NS_BASE_STREAM_CLOSED: return 0; // Log this as a success case NS_ERROR_OUT_OF_MEMORY: return 2; case NS_ERROR_FILE_NO_DEVICE_SPACE: return 3; case NS_ERROR_FILE_CORRUPTED: return 4; case NS_ERROR_FILE_NOT_FOUND: return 5; case NS_BINDING_ABORTED: return 6; default: return 1; // other error
}
MOZ_ASSERT_UNREACHABLE("We should never get here");
}
if (mOutput != aOutput) {
LOG(
("CacheFile::RemoveOutput() - This output was already removed, ignoring" " call [this=%p]", this)); return;
}
mOutput = nullptr;
// Cancel all queued chunk and update listeners that cannot be satisfied
NotifyListenersAboutOutputRemoval();
if (!mMemoryOnly) WriteMetadataIfNeededLocked();
// Make sure the CacheFile status is set to a failure when the output stream // is closed with a fatal error. This way we propagate correctly and w/o any // windows the failure state of this entry to end consumers. if (NS_SUCCEEDED(mStatus) && NS_FAILED(aStatus) &&
aStatus != NS_BASE_STREAM_CLOSED) { if (aOutput->IsAlternativeData()) {
MOZ_ASSERT(mAltDataOffset != -1); // If there is no alt-data input stream truncate only alt-data, otherwise // doom the entry. bool altDataInputExists = false; for (uint32_t i = 0; i < mInputs.Length(); ++i) { if (mInputs[i]->IsAlternativeData()) {
altDataInputExists = true; break;
}
} if (altDataInputExists) {
SetError(aStatus);
} else {
rv = Truncate(mAltDataOffset); if (NS_FAILED(rv)) {
LOG(
("CacheFile::RemoveOutput() - Truncating alt-data failed " "[rv=0x%08" PRIx32 "]", static_cast<uint32_t>(rv)));
SetError(aStatus);
} else {
SetAltMetadata(nullptr);
mAltDataOffset = -1;
mAltDataType.Truncate();
}
}
} else {
SetError(aStatus);
}
}
// Notify close listener as the last action
aOutput->NotifyCloseListener();
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.46Bemerkung:
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.