Bug 405407: Merge nsDiskCacheStreamIO and nsDiskCacheStreamOutput. Patch originally by Alfred Kayser, updated by Josh Aas, final fix from Michal Novotny. r=michal

This commit is contained in:
Josh Aas 2012-09-06 10:10:08 -04:00
parent c5dab9e8b0
commit da1a55564c
4 changed files with 132 additions and 385 deletions

View File

@ -159,6 +159,7 @@ private:
{
nsCacheServiceAutoLock lock(LOCK_TELEM(NSOUTPUTSTREAMWRAPPER_CLOSE));
mDescriptor->mOutput = nullptr;
mOutput = nullptr;
}
NS_RELEASE(mDescriptor);
}

View File

@ -15,14 +15,16 @@
#include "mozilla/Telemetry.h"
#include "mozilla/TimeStamp.h"
// Assumptions:
// - cache descriptors live for life of streams
// - streams will only be used by FileTransport,
// they will not be directly accessible to clients
// - overlapped I/O is NOT supported
// we pick 16k as the max buffer size because that is the threshold above which
// we are unable to store the data in the cache block files
// see nsDiskCacheMap.[cpp,h]
#define kMaxBufferSize (16 * 1024)
/******************************************************************************
* nsDiskCacheInputStream
@ -178,152 +180,13 @@ nsDiskCacheInputStream::IsNonBlocking(bool * nonBlocking)
return NS_OK;
}
/******************************************************************************
* nsDiskCacheOutputStream
*****************************************************************************/
class nsDiskCacheOutputStream : public nsIOutputStream
, public nsIDiskCacheStreamInternal
{
public:
nsDiskCacheOutputStream( nsDiskCacheStreamIO * parent);
virtual ~nsDiskCacheOutputStream();
NS_DECL_ISUPPORTS
NS_DECL_NSIOUTPUTSTREAM
NS_DECL_NSIDISKCACHESTREAMINTERNAL
void ReleaseStreamIO() { NS_IF_RELEASE(mStreamIO); }
private:
nsDiskCacheStreamIO * mStreamIO; // backpointer to parent
bool mClosed;
};
NS_IMPL_THREADSAFE_ISUPPORTS2(nsDiskCacheOutputStream,
nsIOutputStream,
nsIDiskCacheStreamInternal)
nsDiskCacheOutputStream::nsDiskCacheOutputStream( nsDiskCacheStreamIO * parent)
: mStreamIO(parent)
, mClosed(false)
{
NS_ADDREF(mStreamIO);
}
nsDiskCacheOutputStream::~nsDiskCacheOutputStream()
{
Close();
ReleaseStreamIO();
}
NS_IMETHODIMP
nsDiskCacheOutputStream::Close()
{
nsresult rv = NS_OK;
mozilla::TimeStamp start = mozilla::TimeStamp::Now();
if (!mClosed) {
mClosed = true;
// tell parent streamIO we are closing
rv = mStreamIO->CloseOutputStream(this);
}
mozilla::Telemetry::ID id;
if (NS_IsMainThread())
id = mozilla::Telemetry::NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE_MAIN_THREAD;
else
id = mozilla::Telemetry::NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE;
mozilla::Telemetry::AccumulateTimeDelta(id, start);
return rv;
}
NS_IMETHODIMP
nsDiskCacheOutputStream::CloseInternal()
{
nsresult rv = NS_OK;
mozilla::TimeStamp start = mozilla::TimeStamp::Now();
if (!mClosed) {
mClosed = true;
// tell parent streamIO we are closing
rv = mStreamIO->CloseOutputStreamInternal(this);
}
mozilla::Telemetry::ID id;
if (NS_IsMainThread())
id = mozilla::Telemetry::NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE_INTERNAL_MAIN_THREAD;
else
id = mozilla::Telemetry::NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE_INTERNAL;
mozilla::Telemetry::AccumulateTimeDelta(id, start);
return rv;
}
NS_IMETHODIMP
nsDiskCacheOutputStream::Flush()
{
if (mClosed) return NS_BASE_STREAM_CLOSED;
// yeah, yeah, well get to it...eventually...
return NS_OK;
}
NS_IMETHODIMP
nsDiskCacheOutputStream::Write(const char *buf, uint32_t count, uint32_t *bytesWritten)
{
if (mClosed) return NS_BASE_STREAM_CLOSED;
return mStreamIO->Write(buf, count, bytesWritten);
}
NS_IMETHODIMP
nsDiskCacheOutputStream::WriteFrom(nsIInputStream *inStream, uint32_t count, uint32_t *bytesWritten)
{
NS_NOTREACHED("WriteFrom");
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsDiskCacheOutputStream::WriteSegments( nsReadSegmentFun reader,
void * closure,
uint32_t count,
uint32_t * bytesWritten)
{
NS_NOTREACHED("WriteSegments");
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsDiskCacheOutputStream::IsNonBlocking(bool * nonBlocking)
{
*nonBlocking = false;
return NS_OK;
}
/******************************************************************************
* nsDiskCacheStreamIO
*****************************************************************************/
NS_IMPL_THREADSAFE_ISUPPORTS0(nsDiskCacheStreamIO)
// we pick 16k as the max buffer size because that is the threshold above which
// we are unable to store the data in the cache block files
// see nsDiskCacheMap.[cpp,h]
#define kMaxBufferSize (16 * 1024)
NS_IMPL_THREADSAFE_ISUPPORTS2(nsDiskCacheStreamIO, nsIOutputStream, nsIDiskCacheStreamInternal)
nsDiskCacheStreamIO::nsDiskCacheStreamIO(nsDiskCacheBinding * binding)
: mBinding(binding)
, mOutStream(nullptr)
, mInStreamCount(0)
, mFD(nullptr)
, mStreamPos(0)
@ -332,6 +195,7 @@ nsDiskCacheStreamIO::nsDiskCacheStreamIO(nsDiskCacheBinding * binding)
, mBufEnd(0)
, mBufSize(0)
, mBufDirty(false)
, mOutputStreamIsOpen(false)
, mBuffer(nullptr)
{
mDevice = (nsDiskCacheDevice *)mBinding->mCacheEntry->CacheDevice();
@ -344,7 +208,16 @@ nsDiskCacheStreamIO::nsDiskCacheStreamIO(nsDiskCacheBinding * binding)
nsDiskCacheStreamIO::~nsDiskCacheStreamIO()
{
Close();
if (mOutputStreamIsOpen) {
nsCacheService::AssertOwnsLock();
CloseInternal();
}
NS_ASSERTION(!mOutputStreamIsOpen, "output stream still open");
NS_ASSERTION(mInStreamCount == 0, "input stream still open");
NS_ASSERTION(!mFD, "file descriptor not closed");
DeleteBuffer();
// release "death grip" on cache service
nsCacheService *service = nsCacheService::GlobalInstance();
@ -352,18 +225,38 @@ nsDiskCacheStreamIO::~nsDiskCacheStreamIO()
}
void
NS_IMETHODIMP
nsDiskCacheStreamIO::WriteFrom(nsIInputStream *inStream, uint32_t count, uint32_t *bytesWritten)
{
NS_NOTREACHED("WriteFrom");
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsDiskCacheStreamIO::WriteSegments(nsReadSegmentFun reader,
void * closure,
uint32_t count,
uint32_t * bytesWritten)
{
NS_NOTREACHED("WriteSegments");
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsDiskCacheStreamIO::IsNonBlocking(bool * nonBlocking)
{
*nonBlocking = false;
return NS_OK;
}
NS_IMETHODIMP
nsDiskCacheStreamIO::Close()
{
// this should only be called from our destructor
// no one is interested in us anymore, so we don't need to grab any locks
// assert streams closed
NS_ASSERTION(!mOutStream, "output stream still open");
NS_ASSERTION(mInStreamCount == 0, "input stream still open");
NS_ASSERTION(!mFD, "file descriptor not closed");
DeleteBuffer();
nsCacheServiceAutoLock lock(LOCK_TELEM(NSDISKCACHESTREAMIO_CLOSEOUTPUTSTREAM));
return CloseInternal();
}
@ -378,8 +271,8 @@ nsDiskCacheStreamIO::GetInputStream(uint32_t offset, nsIInputStream ** inputStre
if (!mBinding) return NS_ERROR_NOT_AVAILABLE;
if (mOutStream) {
NS_WARNING("already have an output stream open");
if (mOutputStreamIsOpen) {
NS_WARNING("already have the output stream open");
return NS_ERROR_NOT_AVAILABLE;
}
@ -423,10 +316,10 @@ nsDiskCacheStreamIO::GetOutputStream(uint32_t offset, nsIOutputStream ** outputS
*outputStream = nullptr;
if (!mBinding) return NS_ERROR_NOT_AVAILABLE;
NS_ASSERTION(!mOutStream, "already have an output stream open");
NS_ASSERTION(!mOutputStreamIsOpen, "already have the output stream open");
NS_ASSERTION(mInStreamCount == 0, "we already have input streams open");
if (mOutStream || mInStreamCount) return NS_ERROR_NOT_AVAILABLE;
if (mOutputStreamIsOpen || mInStreamCount) return NS_ERROR_NOT_AVAILABLE;
// mBuffer lazily allocated, but might exist if a previous stream already
// created one.
@ -434,69 +327,97 @@ nsDiskCacheStreamIO::GetOutputStream(uint32_t offset, nsIOutputStream ** outputS
mStreamPos = 0;
mStreamEnd = mBinding->mCacheEntry->DataSize();
nsresult rv;
if (offset) {
rv = Seek(PR_SEEK_SET, offset);
if (NS_FAILED(rv)) return rv;
if (offset > mStreamEnd) {
NS_WARNING("seek offset out of range");
return NS_ERROR_INVALID_ARG;
}
rv = SetEOF();
if (NS_FAILED(rv)) return rv;
// create a new output stream
mOutStream = new nsDiskCacheOutputStream(this);
if (!mOutStream) return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(*outputStream = mOutStream);
nsresult rv;
// Seek and truncate at the desired offset
if (mBinding->mRecord.DataLocationInitialized() &&
(mBinding->mRecord.DataFile() == 0)) {
// File storage, seek in file
rv = OpenCacheFile(PR_WRONLY | PR_CREATE_FILE, &mFD);
NS_ENSURE_SUCCESS(rv, rv);
if (offset) {
int32_t newPos = PR_Seek(mFD, offset, PR_SEEK_SET);
if (newPos == -1) {
return NS_ErrorAccordingToNSPR();
}
}
// Truncate at start position (offset)
rv = nsDiskCache::Truncate(mFD, offset);
NS_ENSURE_SUCCESS(rv, rv);
mStreamPos = mStreamEnd = offset;
UpdateFileSize();
} else if (offset) {
// else, read and seek in mBuffer
rv = ReadCacheBlocks();
NS_ENSURE_SUCCESS(rv, rv);
// Start writing at the provided offset
mBufEnd = mBufPos = offset;
mStreamPos = mStreamEnd = offset;
}
mOutputStreamIsOpen = true;
// return myself as the output stream
NS_ADDREF(*outputStream = this);
return NS_OK;
}
nsresult
nsDiskCacheStreamIO::ClearBinding()
{
nsresult rv = NS_OK;
if (mBinding && mOutStream)
if (mBinding && mOutputStreamIsOpen)
rv = Flush();
mBinding = nullptr;
return rv;
}
nsresult
nsDiskCacheStreamIO::CloseOutputStream(nsDiskCacheOutputStream * outputStream)
{
nsCacheServiceAutoLock lock(LOCK_TELEM(NSDISKCACHESTREAMIO_CLOSEOUTPUTSTREAM)); // grab service lock
return CloseOutputStreamInternal(outputStream);
}
nsresult
nsDiskCacheStreamIO::CloseOutputStreamInternal(
nsDiskCacheOutputStream * outputStream)
NS_IMETHODIMP
nsDiskCacheStreamIO::CloseInternal()
{
nsresult rv;
mozilla::TimeStamp start = mozilla::TimeStamp::Now();
if (outputStream != mOutStream) {
NS_WARNING("mismatched output streams");
return NS_ERROR_UNEXPECTED;
}
// output stream is closing
if (!mBinding) { // if we're severed, just clear member variables
NS_ASSERTION(!mBufDirty, "oops");
mOutStream = nullptr;
outputStream->ReleaseStreamIO();
return NS_ERROR_NOT_AVAILABLE;
if (mOutputStreamIsOpen) {
if (!mBinding) { // if we're severed, just clear member variables
NS_ASSERTION(!mBufDirty, "oops");
} else {
nsresult rv = Flush();
NS_ENSURE_SUCCESS(rv, rv);
}
mOutputStreamIsOpen = PR_FALSE;
}
rv = Flush();
if (NS_FAILED(rv))
NS_WARNING("Flush() failed");
// Make sure to always close the FileDescriptor
if (mFD) {
(void) PR_Close(mFD);
mFD = nullptr;
}
mOutStream = nullptr;
return rv;
mozilla::Telemetry::ID id;
if (NS_IsMainThread())
id = mozilla::Telemetry::NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE_INTERNAL_MAIN_THREAD;
else
id = mozilla::Telemetry::NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE_INTERNAL;
mozilla::Telemetry::AccumulateTimeDelta(id, start);
return NS_OK;
}
nsresult
NS_IMETHODIMP
nsDiskCacheStreamIO::Flush()
{
if (!mOutputStreamIsOpen) return NS_BASE_STREAM_CLOSED;
NS_ASSERTION(mBinding, "oops");
CACHE_LOG_DEBUG(("CACHE: Flush [%x doomed=%u]\n",
@ -589,12 +510,15 @@ nsDiskCacheStreamIO::Flush()
// never have both output and input streams open
// OnDataSizeChanged() will have already been called to update entry->DataSize()
nsresult
NS_IMETHODIMP
nsDiskCacheStreamIO::Write( const char * buffer,
uint32_t count,
uint32_t * bytesWritten)
{
nsresult rv = NS_OK;
if (!mOutputStreamIsOpen) {
return NS_BASE_STREAM_CLOSED;
}
nsCacheServiceAutoLock lock(LOCK_TELEM(NSDISKCACHESTREAMIO_WRITE)); // grab service lock
if (!mBinding) return NS_ERROR_NOT_AVAILABLE;
@ -661,7 +585,7 @@ nsDiskCacheStreamIO::Write( const char * buffer,
}
}
return rv;
return NS_OK;
}
@ -770,7 +694,7 @@ nsDiskCacheStreamIO::FlushBufferToFile()
record->SetDataFileGeneration(mBinding->mGeneration);
// allocate file
rv = OpenCacheFile(PR_RDWR | PR_CREATE_FILE, &mFD);
rv = OpenCacheFile(PR_WRONLY | PR_CREATE_FILE, &mFD);
if (NS_FAILED(rv)) return rv;
int64_t dataSize = mBinding->mCacheEntry->PredictedDataSize();
@ -806,159 +730,3 @@ nsDiskCacheStreamIO::DeleteBuffer()
mBufSize = 0;
}
}
// NOTE: called with service lock held
nsresult
nsDiskCacheStreamIO::Seek(int32_t whence, int32_t offset)
{
int32_t newPos;
if (!mBinding) return NS_ERROR_NOT_AVAILABLE;
if (uint32_t(offset) > mStreamEnd) return NS_ERROR_FAILURE;
if (mBinding->mRecord.DataLocationInitialized()) {
if (mBinding->mRecord.DataFile() == 0) {
if (!mFD) {
// we need an mFD, we better open it now
nsresult rv = OpenCacheFile(PR_RDWR | PR_CREATE_FILE, &mFD);
if (NS_FAILED(rv)) return rv;
}
}
}
if (mFD) {
// do we have data in the buffer that needs to be flushed?
if (mBufDirty) {
// XXX optimization: are we just moving within the current buffer?
nsresult rv = FlushBufferToFile();
if (NS_FAILED(rv)) return rv;
}
newPos = PR_Seek(mFD, offset, (PRSeekWhence)whence);
if (newPos == -1)
return NS_ErrorAccordingToNSPR();
mStreamPos = (uint32_t) newPos;
mBufPos = 0;
mBufEnd = 0;
return NS_OK;
}
// else, seek in mBuffer
switch(whence) {
case PR_SEEK_SET:
newPos = offset;
break;
case PR_SEEK_CUR: // relative from current posistion
newPos = offset + (uint32_t)mStreamPos;
break;
case PR_SEEK_END: // relative from end
newPos = offset + (uint32_t)mBufEnd;
break;
default:
return NS_ERROR_INVALID_ARG;
}
// read data into mBuffer if not read yet.
if (mStreamEnd && !mBufEnd) {
if (newPos > 0) {
nsresult rv = ReadCacheBlocks();
if (NS_FAILED(rv)) return rv;
}
}
// stream buffer sanity checks
NS_ASSERTION(mBufEnd <= kMaxBufferSize, "bad stream");
NS_ASSERTION(mBufPos <= mBufEnd, "bad stream");
NS_ASSERTION(mStreamPos == mBufPos, "bad stream");
NS_ASSERTION(mStreamEnd == mBufEnd, "bad stream");
if ((newPos < 0) || (uint32_t(newPos) > mBufEnd)) {
NS_WARNING("seek offset out of range");
return NS_ERROR_INVALID_ARG;
}
mStreamPos = newPos;
mBufPos = newPos;
return NS_OK;
}
// called only from nsDiskCacheOutputStream::Tell
nsresult
nsDiskCacheStreamIO::Tell(uint32_t * result)
{
NS_ENSURE_ARG_POINTER(result);
*result = mStreamPos;
return NS_OK;
}
// NOTE: called with service lock held
nsresult
nsDiskCacheStreamIO::SetEOF()
{
nsresult rv;
bool needToCloseFD = false;
NS_ASSERTION(mStreamPos <= mStreamEnd, "bad stream");
if (!mBinding) return NS_ERROR_NOT_AVAILABLE;
if (mBinding->mRecord.DataLocationInitialized()) {
if (mBinding->mRecord.DataFile() == 0) {
if (!mFD) {
// we need an mFD, we better open it now
rv = OpenCacheFile(PR_RDWR | PR_CREATE_FILE, &mFD);
if (NS_FAILED(rv)) return rv;
needToCloseFD = true;
}
} else {
// data in cache block files
if ((mStreamPos != 0) && (mStreamPos != mBufPos)) {
// only read data if there will be some left after truncation
rv = ReadCacheBlocks();
if (NS_FAILED(rv)) return rv;
}
// We need to make sure we reflect this change in Flush().
// In particular, if mStreamPos is 0 and we never write to
// the buffer, we want the storage to be deleted.
mBufDirty = true;
}
}
if (mFD) {
rv = nsDiskCache::Truncate(mFD, mStreamPos);
#ifdef DEBUG
uint32_t oldSizeK = (mStreamEnd + 0x03FF) >> 10;
NS_ASSERTION(mBinding->mRecord.DataFileSize() == oldSizeK, "bad disk cache entry size");
} else {
// data stored in buffer.
NS_ASSERTION(mStreamEnd <= kMaxBufferSize, "buffer truncation inadequate");
NS_ASSERTION(mBufPos == mStreamPos, "bad stream");
NS_ASSERTION(mBuffer ? mBufEnd == mStreamEnd : true, "bad stream");
#endif
}
NS_ASSERTION(mStreamEnd == mBinding->mCacheEntry->DataSize(), "cache entry not updated");
// we expect nsCacheEntryDescriptor::TransportWrapper::OpenOutputStream()
// to eventually update the cache entry
mStreamEnd = mStreamPos;
mBufEnd = mBufPos;
if (mFD) {
UpdateFileSize();
if (needToCloseFD) {
(void) PR_Close(mFD);
mFD = nullptr;
}
}
return NS_OK;
}

View File

@ -14,30 +14,25 @@
#include "nsIInputStream.h"
#include "nsIOutputStream.h"
#include "nsIDiskCacheStreamInternal.h"
#include "pratom.h"
class nsDiskCacheInputStream;
class nsDiskCacheOutputStream;
class nsDiskCacheDevice;
class nsDiskCacheStreamIO : public nsISupports {
class nsDiskCacheStreamIO : public nsIOutputStream, nsIDiskCacheStreamInternal {
public:
nsDiskCacheStreamIO(nsDiskCacheBinding * binding);
virtual ~nsDiskCacheStreamIO();
NS_DECL_ISUPPORTS
NS_DECL_NSIOUTPUTSTREAM
NS_DECL_NSIDISKCACHESTREAMINTERNAL
nsresult GetInputStream(uint32_t offset, nsIInputStream ** inputStream);
nsresult GetOutputStream(uint32_t offset, nsIOutputStream ** outputStream);
nsresult CloseOutputStream(nsDiskCacheOutputStream * outputStream);
nsresult CloseOutputStreamInternal(nsDiskCacheOutputStream * outputStream);
nsresult Write( const char * buffer,
uint32_t count,
uint32_t * bytesWritten);
nsresult Seek(int32_t whence, int32_t offset);
nsresult Tell(uint32_t * position);
nsresult SetEOF();
@ -55,20 +50,14 @@ public:
// and OS/2 requires that it not be private
nsDiskCacheStreamIO() { NS_NOTREACHED("oops"); }
private:
void Close();
nsresult OpenCacheFile(int flags, PRFileDesc ** fd);
nsresult ReadCacheBlocks();
nsresult FlushBufferToFile();
void UpdateFileSize();
void DeleteBuffer();
nsresult Flush();
nsDiskCacheBinding * mBinding; // not an owning reference
nsDiskCacheDevice * mDevice;
nsDiskCacheOutputStream * mOutStream; // not an owning reference
int32_t mInStreamCount;
nsCOMPtr<nsIFile> mLocalFile;
PRFileDesc * mFD;
@ -78,7 +67,8 @@ private:
uint32_t mBufPos; // current mark in buffer
uint32_t mBufEnd; // current end of data in buffer
uint32_t mBufSize; // current end of buffer
bool mBufDirty;
bool mBufDirty; // Where there is unflushed data in the buffer
bool mOutputStreamIsOpen; // Whether the output stream is open (for writing...)
char * mBuffer;
};

View File

@ -1602,18 +1602,6 @@
"n_buckets": 10,
"description": "Total Time spent (ms) during disk cache revalidation"
},
"NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE": {
"kind": "exponential",
"high": "10000",
"n_buckets": 10,
"description": "Time spent in nsDiskCacheOutputStream::Close() on non-main thread (ms)"
},
"NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE_MAIN_THREAD": {
"kind": "exponential",
"high": "10000",
"n_buckets": 10,
"description": "Time spent in nsDiskCacheOutputStream::Close() on the main thread (ms)"
},
"NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE_INTERNAL": {
"kind": "exponential",
"high": "10000",