Bug 1866777 - Disable test_race_cache_with_network.js on windows opt for frequent...
[gecko.git] / netwerk / cache2 / CacheFileOutputStream.cpp
blob985082efbaf09026a7730221a9cc9083a388d57d
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/. */
5 #include "CacheLog.h"
6 #include "CacheFileOutputStream.h"
8 #include "CacheFile.h"
9 #include "CacheEntry.h"
10 #include "nsStreamUtils.h"
11 #include "nsThreadUtils.h"
12 #include "mozilla/DebugOnly.h"
13 #include <algorithm>
15 namespace mozilla::net {
17 NS_IMPL_ADDREF(CacheFileOutputStream)
18 NS_IMETHODIMP_(MozExternalRefCountType)
19 CacheFileOutputStream::Release() {
20 MOZ_ASSERT(0 != mRefCnt, "dup release");
21 nsrefcnt count = --mRefCnt;
22 NS_LOG_RELEASE(this, count, "CacheFileOutputStream");
24 if (0 == count) {
25 mRefCnt = 1;
27 CacheFileAutoLock lock(mFile);
28 mFile->RemoveOutput(this, mStatus);
30 delete (this);
31 return 0;
34 return count;
37 NS_INTERFACE_MAP_BEGIN(CacheFileOutputStream)
38 NS_INTERFACE_MAP_ENTRY(nsIOutputStream)
39 NS_INTERFACE_MAP_ENTRY(nsIAsyncOutputStream)
40 NS_INTERFACE_MAP_ENTRY(nsISeekableStream)
41 NS_INTERFACE_MAP_ENTRY(nsITellableStream)
42 NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileChunkListener)
43 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIOutputStream)
44 NS_INTERFACE_MAP_END
46 CacheFileOutputStream::CacheFileOutputStream(
47 CacheFile* aFile, CacheOutputCloseListener* aCloseListener,
48 bool aAlternativeData)
49 : mFile(aFile),
50 mCloseListener(aCloseListener),
51 mPos(0),
52 mClosed(false),
53 mAlternativeData(aAlternativeData),
54 mStatus(NS_OK),
55 mCallbackFlags(0) {
56 LOG(("CacheFileOutputStream::CacheFileOutputStream() [this=%p]", this));
58 if (mAlternativeData) {
59 mPos = mFile->mAltDataOffset;
63 CacheFileOutputStream::~CacheFileOutputStream() {
64 LOG(("CacheFileOutputStream::~CacheFileOutputStream() [this=%p]", this));
67 // nsIOutputStream
68 NS_IMETHODIMP
69 CacheFileOutputStream::Close() {
70 LOG(("CacheFileOutputStream::Close() [this=%p]", this));
71 return CloseWithStatus(NS_OK);
74 NS_IMETHODIMP
75 CacheFileOutputStream::Flush() {
76 // TODO do we need to implement flush ???
77 LOG(("CacheFileOutputStream::Flush() [this=%p]", this));
78 return NS_OK;
81 NS_IMETHODIMP
82 CacheFileOutputStream::StreamStatus() {
83 CacheFileAutoLock lock(mFile);
84 mFile->AssertOwnsLock(); // For thread-safety analysis
86 LOG(("CacheFileOutputStream::Close() [this=%p]", this));
87 if (mClosed) {
88 return NS_FAILED(mStatus) ? mStatus : NS_BASE_STREAM_CLOSED;
90 return NS_OK;
93 NS_IMETHODIMP
94 CacheFileOutputStream::Write(const char* aBuf, uint32_t aCount,
95 uint32_t* _retval) {
96 CacheFileAutoLock lock(mFile);
97 mFile->AssertOwnsLock(); // For thread-safety analysis
99 LOG(("CacheFileOutputStream::Write() [this=%p, count=%d]", this, aCount));
101 if (mClosed) {
102 LOG(
103 ("CacheFileOutputStream::Write() - Stream is closed. [this=%p, "
104 "status=0x%08" PRIx32 "]",
105 this, static_cast<uint32_t>(mStatus)));
107 return NS_FAILED(mStatus) ? mStatus : NS_BASE_STREAM_CLOSED;
110 if (!mFile->mSkipSizeCheck &&
111 CacheObserver::EntryIsTooBig(mPos + aCount, !mFile->mMemoryOnly)) {
112 LOG(("CacheFileOutputStream::Write() - Entry is too big. [this=%p]", this));
114 CloseWithStatusLocked(NS_ERROR_FILE_TOO_BIG);
115 return NS_ERROR_FILE_TOO_BIG;
118 // We use 64-bit offset when accessing the file, unfortunately we use 32-bit
119 // metadata offset, so we cannot handle data bigger than 4GB.
120 if (mPos + aCount > PR_UINT32_MAX) {
121 LOG(("CacheFileOutputStream::Write() - Entry's size exceeds 4GB. [this=%p]",
122 this));
124 CloseWithStatusLocked(NS_ERROR_FILE_TOO_BIG);
125 return NS_ERROR_FILE_TOO_BIG;
128 *_retval = aCount;
130 while (aCount) {
131 EnsureCorrectChunk(false);
132 if (NS_FAILED(mStatus)) {
133 return mStatus;
136 FillHole();
137 if (NS_FAILED(mStatus)) {
138 return mStatus;
141 uint32_t chunkOffset = mPos - (mPos / kChunkSize) * kChunkSize;
142 uint32_t canWrite = kChunkSize - chunkOffset;
143 uint32_t thisWrite = std::min(static_cast<uint32_t>(canWrite), aCount);
145 CacheFileChunkWriteHandle hnd =
146 mChunk->GetWriteHandle(chunkOffset + thisWrite);
147 if (!hnd.Buf()) {
148 CloseWithStatusLocked(NS_ERROR_OUT_OF_MEMORY);
149 return NS_ERROR_OUT_OF_MEMORY;
152 memcpy(hnd.Buf() + chunkOffset, aBuf, thisWrite);
153 hnd.UpdateDataSize(chunkOffset, thisWrite);
155 mPos += thisWrite;
156 aBuf += thisWrite;
157 aCount -= thisWrite;
160 EnsureCorrectChunk(true);
162 LOG(("CacheFileOutputStream::Write() - Wrote %d bytes [this=%p]", *_retval,
163 this));
165 return NS_OK;
168 NS_IMETHODIMP
169 CacheFileOutputStream::WriteFrom(nsIInputStream* aFromStream, uint32_t aCount,
170 uint32_t* _retval) {
171 LOG(
172 ("CacheFileOutputStream::WriteFrom() - NOT_IMPLEMENTED [this=%p, from=%p"
173 ", count=%d]",
174 this, aFromStream, aCount));
176 return NS_ERROR_NOT_IMPLEMENTED;
179 NS_IMETHODIMP
180 CacheFileOutputStream::WriteSegments(nsReadSegmentFun aReader, void* aClosure,
181 uint32_t aCount, uint32_t* _retval) {
182 LOG(
183 ("CacheFileOutputStream::WriteSegments() - NOT_IMPLEMENTED [this=%p, "
184 "count=%d]",
185 this, aCount));
187 return NS_ERROR_NOT_IMPLEMENTED;
190 NS_IMETHODIMP
191 CacheFileOutputStream::IsNonBlocking(bool* _retval) {
192 *_retval = false;
193 return NS_OK;
196 // nsIAsyncOutputStream
197 NS_IMETHODIMP
198 CacheFileOutputStream::CloseWithStatus(nsresult aStatus) {
199 CacheFileAutoLock lock(mFile);
201 LOG(("CacheFileOutputStream::CloseWithStatus() [this=%p, aStatus=0x%08" PRIx32
202 "]",
203 this, static_cast<uint32_t>(aStatus)));
205 return CloseWithStatusLocked(aStatus);
208 nsresult CacheFileOutputStream::CloseWithStatusLocked(nsresult aStatus) {
209 LOG(
210 ("CacheFileOutputStream::CloseWithStatusLocked() [this=%p, "
211 "aStatus=0x%08" PRIx32 "]",
212 this, static_cast<uint32_t>(aStatus)));
214 if (mClosed) {
215 MOZ_ASSERT(!mCallback);
216 return NS_OK;
219 mClosed = true;
220 mStatus = NS_FAILED(aStatus) ? aStatus : NS_BASE_STREAM_CLOSED;
222 if (mChunk) {
223 ReleaseChunk();
226 if (mCallback) {
227 NotifyListener();
230 mFile->RemoveOutput(this, mStatus);
232 return NS_OK;
235 NS_IMETHODIMP
236 CacheFileOutputStream::AsyncWait(nsIOutputStreamCallback* aCallback,
237 uint32_t aFlags, uint32_t aRequestedCount,
238 nsIEventTarget* aEventTarget) {
239 CacheFileAutoLock lock(mFile);
241 LOG(
242 ("CacheFileOutputStream::AsyncWait() [this=%p, callback=%p, flags=%d, "
243 "requestedCount=%d, eventTarget=%p]",
244 this, aCallback, aFlags, aRequestedCount, aEventTarget));
246 mCallback = aCallback;
247 mCallbackFlags = aFlags;
248 mCallbackTarget = aEventTarget;
250 if (!mCallback) return NS_OK;
252 // The stream is blocking so it is writable at any time
253 if (mClosed || !(aFlags & WAIT_CLOSURE_ONLY)) NotifyListener();
255 return NS_OK;
258 // nsISeekableStream
259 NS_IMETHODIMP
260 CacheFileOutputStream::Seek(int32_t whence, int64_t offset) {
261 CacheFileAutoLock lock(mFile);
262 mFile->AssertOwnsLock(); // For thread-safety analysis
264 LOG(("CacheFileOutputStream::Seek() [this=%p, whence=%d, offset=%" PRId64 "]",
265 this, whence, offset));
267 if (mClosed) {
268 LOG(("CacheFileOutputStream::Seek() - Stream is closed. [this=%p]", this));
269 return NS_BASE_STREAM_CLOSED;
272 int64_t newPos = offset;
273 switch (whence) {
274 case NS_SEEK_SET:
275 if (mAlternativeData) {
276 newPos += mFile->mAltDataOffset;
278 break;
279 case NS_SEEK_CUR:
280 newPos += mPos;
281 break;
282 case NS_SEEK_END:
283 if (mAlternativeData) {
284 newPos += mFile->mDataSize;
285 } else {
286 newPos += mFile->mAltDataOffset;
288 break;
289 default:
290 NS_ERROR("invalid whence");
291 return NS_ERROR_INVALID_ARG;
293 mPos = newPos;
294 EnsureCorrectChunk(true);
296 LOG(("CacheFileOutputStream::Seek() [this=%p, pos=%" PRId64 "]", this, mPos));
297 return NS_OK;
300 NS_IMETHODIMP
301 CacheFileOutputStream::SetEOF() {
302 MOZ_ASSERT(false, "CacheFileOutputStream::SetEOF() not implemented");
303 // Right now we don't use SetEOF(). If we ever need this method, we need
304 // to think about what to do with input streams that already points beyond
305 // new EOF.
306 return NS_ERROR_NOT_IMPLEMENTED;
309 // nsITellableStream
310 NS_IMETHODIMP
311 CacheFileOutputStream::Tell(int64_t* _retval) {
312 CacheFileAutoLock lock(mFile);
313 mFile->AssertOwnsLock(); // For thread-safety analysis
315 if (mClosed) {
316 LOG(("CacheFileOutputStream::Tell() - Stream is closed. [this=%p]", this));
317 return NS_BASE_STREAM_CLOSED;
320 *_retval = mPos;
322 if (mAlternativeData) {
323 *_retval -= mFile->mAltDataOffset;
326 LOG(("CacheFileOutputStream::Tell() [this=%p, retval=%" PRId64 "]", this,
327 *_retval));
328 return NS_OK;
331 // CacheFileChunkListener
332 nsresult CacheFileOutputStream::OnChunkRead(nsresult aResult,
333 CacheFileChunk* aChunk) {
334 MOZ_CRASH("CacheFileOutputStream::OnChunkRead should not be called!");
335 return NS_ERROR_UNEXPECTED;
338 nsresult CacheFileOutputStream::OnChunkWritten(nsresult aResult,
339 CacheFileChunk* aChunk) {
340 MOZ_CRASH("CacheFileOutputStream::OnChunkWritten should not be called!");
341 return NS_ERROR_UNEXPECTED;
344 nsresult CacheFileOutputStream::OnChunkAvailable(nsresult aResult,
345 uint32_t aChunkIdx,
346 CacheFileChunk* aChunk) {
347 MOZ_CRASH("CacheFileOutputStream::OnChunkAvailable should not be called!");
348 return NS_ERROR_UNEXPECTED;
351 nsresult CacheFileOutputStream::OnChunkUpdated(CacheFileChunk* aChunk) {
352 MOZ_CRASH("CacheFileOutputStream::OnChunkUpdated should not be called!");
353 return NS_ERROR_UNEXPECTED;
356 void CacheFileOutputStream::NotifyCloseListener() {
357 RefPtr<CacheOutputCloseListener> listener;
358 listener.swap(mCloseListener);
359 if (!listener) return;
361 listener->OnOutputClosed();
364 void CacheFileOutputStream::ReleaseChunk() {
365 mFile->AssertOwnsLock();
367 LOG(("CacheFileOutputStream::ReleaseChunk() [this=%p, idx=%d]", this,
368 mChunk->Index()));
370 // If the chunk didn't write any data we need to remove hash for this chunk
371 // that was added when the chunk was created in CacheFile::GetChunkLocked.
372 if (mChunk->DataSize() == 0) {
373 // It must be due to a failure, we don't create a new chunk when we don't
374 // have data to write.
375 MOZ_ASSERT(NS_FAILED(mChunk->GetStatus()));
376 mFile->mMetadata->RemoveHash(mChunk->Index());
379 mFile->ReleaseOutsideLock(std::move(mChunk));
382 void CacheFileOutputStream::EnsureCorrectChunk(bool aReleaseOnly) {
383 mFile->AssertOwnsLock();
385 LOG(("CacheFileOutputStream::EnsureCorrectChunk() [this=%p, releaseOnly=%d]",
386 this, aReleaseOnly));
388 uint32_t chunkIdx = mPos / kChunkSize;
390 if (mChunk) {
391 if (mChunk->Index() == chunkIdx) {
392 // we have a correct chunk
393 LOG(
394 ("CacheFileOutputStream::EnsureCorrectChunk() - Have correct chunk "
395 "[this=%p, idx=%d]",
396 this, chunkIdx));
398 return;
400 ReleaseChunk();
403 if (aReleaseOnly) return;
405 nsresult rv;
406 rv = mFile->GetChunkLocked(chunkIdx, CacheFile::WRITER, nullptr,
407 getter_AddRefs(mChunk));
408 if (NS_FAILED(rv)) {
409 LOG(
410 ("CacheFileOutputStream::EnsureCorrectChunk() - GetChunkLocked failed. "
411 "[this=%p, idx=%d, rv=0x%08" PRIx32 "]",
412 this, chunkIdx, static_cast<uint32_t>(rv)));
413 CloseWithStatusLocked(rv);
417 void CacheFileOutputStream::FillHole() {
418 mFile->AssertOwnsLock();
420 MOZ_ASSERT(mChunk);
421 MOZ_ASSERT(mPos / kChunkSize == mChunk->Index());
423 uint32_t pos = mPos - (mPos / kChunkSize) * kChunkSize;
424 if (mChunk->DataSize() >= pos) return;
426 LOG(
427 ("CacheFileOutputStream::FillHole() - Zeroing hole in chunk %d, range "
428 "%d-%d [this=%p]",
429 mChunk->Index(), mChunk->DataSize(), pos - 1, this));
431 CacheFileChunkWriteHandle hnd = mChunk->GetWriteHandle(pos);
432 if (!hnd.Buf()) {
433 CloseWithStatusLocked(NS_ERROR_OUT_OF_MEMORY);
434 return;
437 uint32_t offset = hnd.DataSize();
438 memset(hnd.Buf() + offset, 0, pos - offset);
439 hnd.UpdateDataSize(offset, pos - offset);
442 void CacheFileOutputStream::NotifyListener() {
443 mFile->AssertOwnsLock();
445 LOG(("CacheFileOutputStream::NotifyListener() [this=%p]", this));
447 MOZ_ASSERT(mCallback);
449 if (!mCallbackTarget) {
450 mCallbackTarget = CacheFileIOManager::IOTarget();
451 if (!mCallbackTarget) {
452 LOG(
453 ("CacheFileOutputStream::NotifyListener() - Cannot get Cache I/O "
454 "thread! Using main thread for callback."));
455 mCallbackTarget = GetMainThreadSerialEventTarget();
459 nsCOMPtr<nsIOutputStreamCallback> asyncCallback =
460 NS_NewOutputStreamReadyEvent(mCallback, mCallbackTarget);
462 mCallback = nullptr;
463 mCallbackTarget = nullptr;
465 asyncCallback->OnOutputStreamReady(this);
468 // Memory reporting
470 size_t CacheFileOutputStream::SizeOfIncludingThis(
471 mozilla::MallocSizeOf mallocSizeOf) const {
472 // Everything the stream keeps a reference to is already reported somewhere
473 // else.
474 // mFile reports itself.
475 // mChunk reported as part of CacheFile.
476 // mCloseListener is CacheEntry, already reported.
477 // mCallback is usually CacheFile or a class that is reported elsewhere.
478 return mallocSizeOf(this);
481 } // namespace mozilla::net