/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* 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/. */ #include #include "mozilla/Attributes.h" #include "mozilla/IntegerPrintfMacros.h" #include "mozilla/ReentrantMonitor.h" #include "nsIBufferedStreams.h" #include "nsICloneableInputStream.h" #include "nsIPipe.h" #include "nsIEventTarget.h" #include "nsITellableStream.h" #include "mozilla/RefPtr.h" #include "nsSegmentedBuffer.h" #include "nsStreamUtils.h" #include "nsString.h" #include "nsCOMPtr.h" #include "nsCRT.h" #include "mozilla/Logging.h" #include "nsIClassInfoImpl.h" #include "nsAlgorithm.h" #include "nsPipe.h" #include "nsIAsyncInputStream.h" #include "nsIAsyncOutputStream.h" #include "nsIInputStreamPriority.h" #include "nsThreadUtils.h" using namespace mozilla; #ifdef LOG # undef LOG #endif // // set MOZ_LOG=nsPipe:5 // static LazyLogModule sPipeLog("nsPipe"); #define LOG(args) MOZ_LOG(sPipeLog, mozilla::LogLevel::Debug, args) #define DEFAULT_SEGMENT_SIZE 4096 #define DEFAULT_SEGMENT_COUNT 16 class nsPipe; class nsPipeEvents; class nsPipeInputStream; class nsPipeOutputStream; class AutoReadSegment; namespace { enum MonitorAction { DoNotNotifyMonitor, NotifyMonitor }; enum SegmentChangeResult { SegmentNotChanged, SegmentAdvanceBufferRead }; } // namespace //----------------------------------------------------------------------------- class CallbackHolder { public: CallbackHolder() = default; MOZ_IMPLICIT CallbackHolder(std::nullptr_t) {} CallbackHolder(nsIAsyncInputStream* aStream, nsIInputStreamCallback* aCallback, uint32_t aFlags, nsIEventTarget* aEventTarget) : mRunnable(aCallback ? NS_NewCancelableRunnableFunction( "nsPipeInputStream AsyncWait Callback", [stream = nsCOMPtr{aStream}, callback = nsCOMPtr{aCallback}]() { callback->OnInputStreamReady(stream); }) : nullptr), mEventTarget(aEventTarget), mFlags(aFlags) {} CallbackHolder(nsIAsyncOutputStream* aStream, nsIOutputStreamCallback* aCallback, uint32_t aFlags, nsIEventTarget* aEventTarget) : mRunnable(aCallback ? NS_NewCancelableRunnableFunction( "nsPipeOutputStream AsyncWait Callback", [stream = nsCOMPtr{aStream}, callback = nsCOMPtr{aCallback}]() { callback->OnOutputStreamReady(stream); }) : nullptr), mEventTarget(aEventTarget), mFlags(aFlags) {} CallbackHolder(const CallbackHolder&) = delete; CallbackHolder(CallbackHolder&&) = default; CallbackHolder& operator=(const CallbackHolder&) = delete; CallbackHolder& operator=(CallbackHolder&&) = default; CallbackHolder& operator=(std::nullptr_t) { mRunnable = nullptr; mEventTarget = nullptr; mFlags = 0; return *this; } MOZ_IMPLICIT operator bool() const { return mRunnable; } uint32_t Flags() const { MOZ_ASSERT(mRunnable, "Should only be called when a callback is present"); return mFlags; } void Notify() { nsCOMPtr runnable = mRunnable.forget(); nsCOMPtr eventTarget = mEventTarget.forget(); if (runnable) { if (eventTarget) { eventTarget->Dispatch(runnable.forget()); } else { runnable->Run(); } } } private: nsCOMPtr mRunnable; nsCOMPtr mEventTarget; uint32_t mFlags = 0; }; //----------------------------------------------------------------------------- // this class is used to delay notifications until the end of a particular // scope. it helps avoid the complexity of issuing callbacks while inside // a critical section. class nsPipeEvents { public: nsPipeEvents() = default; ~nsPipeEvents(); inline void NotifyReady(CallbackHolder aCallback) { mCallbacks.AppendElement(std::move(aCallback)); } private: nsTArray mCallbacks; }; //----------------------------------------------------------------------------- // This class is used to maintain input stream state. Its broken out from the // nsPipeInputStream class because generally the nsPipe should be modifying // this state and not the input stream itself. struct nsPipeReadState { nsPipeReadState() : mReadCursor(nullptr), mReadLimit(nullptr), mSegment(0), mAvailable(0), mActiveRead(false), mNeedDrain(false) {} // All members of this type are guarded by the pipe monitor, however it cannot // be named from this type, so the less-reliable MOZ_GUARDED_VAR is used // instead. In the future it would be nice to avoid this, especially as // MOZ_GUARDED_VAR is deprecated. char* mReadCursor MOZ_GUARDED_VAR; char* mReadLimit MOZ_GUARDED_VAR; int32_t mSegment MOZ_GUARDED_VAR; uint32_t mAvailable MOZ_GUARDED_VAR; // This flag is managed using the AutoReadSegment RAII stack class. bool mActiveRead MOZ_GUARDED_VAR; // Set to indicate that the input stream has closed and should be drained, // but that drain has been delayed due to an active read. When the read // completes, this flag indicate the drain should then be performed. bool mNeedDrain MOZ_GUARDED_VAR; }; //----------------------------------------------------------------------------- // an input end of a pipe (maintained as a list of refs within the pipe) class nsPipeInputStream final : public nsIAsyncInputStream, public nsITellableStream, public nsISearchableInputStream, public nsICloneableInputStream, public nsIClassInfo, public nsIBufferedInputStream, public nsIInputStreamPriority { public: NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIINPUTSTREAM NS_DECL_NSIASYNCINPUTSTREAM NS_DECL_NSITELLABLESTREAM NS_DECL_NSISEARCHABLEINPUTSTREAM NS_DECL_NSICLONEABLEINPUTSTREAM NS_DECL_NSICLASSINFO NS_DECL_NSIBUFFEREDINPUTSTREAM NS_DECL_NSIINPUTSTREAMPRIORITY explicit nsPipeInputStream(nsPipe* aPipe) : mPipe(aPipe), mLogicalOffset(0), mInputStatus(NS_OK), mBlocking(true), mBlocked(false), mPriority(nsIRunnablePriority::PRIORITY_NORMAL) {} nsPipeInputStream(const nsPipeInputStream& aOther) : mPipe(aOther.mPipe), mLogicalOffset(aOther.mLogicalOffset), mInputStatus(aOther.mInputStatus), mBlocking(aOther.mBlocking), mBlocked(false), mReadState(aOther.mReadState), mPriority(nsIRunnablePriority::PRIORITY_NORMAL) {} void SetNonBlocking(bool aNonBlocking) { mBlocking = !aNonBlocking; } uint32_t Available() MOZ_REQUIRES(Monitor()); // synchronously wait for the pipe to become readable. nsresult Wait(); // These two don't acquire the monitor themselves. Instead they // expect their caller to have done so and to pass the monitor as // evidence. MonitorAction OnInputReadable(uint32_t aBytesWritten, nsPipeEvents&, const ReentrantMonitorAutoEnter& ev) MOZ_REQUIRES(Monitor()); MonitorAction OnInputException(nsresult, nsPipeEvents&, const ReentrantMonitorAutoEnter& ev) MOZ_REQUIRES(Monitor()); nsPipeReadState& ReadState() { return mReadState; } const nsPipeReadState& ReadState() const { return mReadState; } nsresult Status() const; // A version of Status() that doesn't acquire the monitor. nsresult Status(const ReentrantMonitorAutoEnter& ev) const MOZ_REQUIRES(Monitor()); // The status of this input stream, ignoring the status of the underlying // monitor. If this status is errored, the input stream has either already // been removed from the pipe, or will be removed from the pipe shortly. nsresult InputStatus(const ReentrantMonitorAutoEnter&) const MOZ_REQUIRES(Monitor()) { return mInputStatus; } ReentrantMonitor& Monitor() const; private: virtual ~nsPipeInputStream(); RefPtr mPipe; int64_t mLogicalOffset; // Individual input streams can be closed without effecting the rest of the // pipe. So track individual input stream status separately. |mInputStatus| // is protected by |mPipe->mReentrantMonitor|. nsresult mInputStatus MOZ_GUARDED_BY(Monitor()); bool mBlocking; // these variables can only be accessed while inside the pipe's monitor bool mBlocked MOZ_GUARDED_BY(Monitor()); CallbackHolder mCallback MOZ_GUARDED_BY(Monitor()); // requires pipe's monitor to access members; usually treat as an opaque token // to pass to nsPipe nsPipeReadState mReadState; Atomic mPriority; }; //----------------------------------------------------------------------------- // the output end of a pipe (allocated as a member of the pipe). class nsPipeOutputStream : public nsIAsyncOutputStream, public nsIClassInfo { public: // since this class will be allocated as a member of the pipe, we do not // need our own ref count. instead, we share the lifetime (the ref count) // of the entire pipe. this macro is just convenience since it does not // declare a mRefCount variable; however, don't let the name fool you... // we are not inheriting from nsPipe ;-) NS_DECL_ISUPPORTS_INHERITED NS_DECL_NSIOUTPUTSTREAM NS_DECL_NSIASYNCOUTPUTSTREAM NS_DECL_NSICLASSINFO explicit nsPipeOutputStream(nsPipe* aPipe) : mPipe(aPipe), mWriterRefCnt(0), mLogicalOffset(0), mBlocking(true), mBlocked(false), mWritable(true) {} void SetNonBlocking(bool aNonBlocking) { mBlocking = !aNonBlocking; } void SetWritable(bool aWritable) MOZ_REQUIRES(Monitor()) { mWritable = aWritable; } // synchronously wait for the pipe to become writable. nsresult Wait(); MonitorAction OnOutputWritable(nsPipeEvents&) MOZ_REQUIRES(Monitor()); MonitorAction OnOutputException(nsresult, nsPipeEvents&) MOZ_REQUIRES(Monitor()); ReentrantMonitor& Monitor() const; private: nsPipe* mPipe; // separate refcnt so that we know when to close the producer ThreadSafeAutoRefCnt mWriterRefCnt; int64_t mLogicalOffset; bool mBlocking; // these variables can only be accessed while inside the pipe's monitor bool mBlocked MOZ_GUARDED_BY(Monitor()); bool mWritable MOZ_GUARDED_BY(Monitor()); CallbackHolder mCallback MOZ_GUARDED_BY(Monitor()); }; //----------------------------------------------------------------------------- class nsPipe final { public: friend class nsPipeInputStream; friend class nsPipeOutputStream; friend class AutoReadSegment; NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsPipe) // public constructor friend void NS_NewPipe2(nsIAsyncInputStream**, nsIAsyncOutputStream**, bool, bool, uint32_t, uint32_t); private: nsPipe(uint32_t aSegmentSize, uint32_t aSegmentCount); ~nsPipe(); // // Methods below may only be called while inside the pipe's monitor. Some // of these methods require passing a ReentrantMonitorAutoEnter to prove the // monitor is held. // void PeekSegment(const nsPipeReadState& aReadState, uint32_t aIndex, char*& aCursor, char*& aLimit) MOZ_REQUIRES(mReentrantMonitor); SegmentChangeResult AdvanceReadSegment(nsPipeReadState& aReadState, const ReentrantMonitorAutoEnter& ev) MOZ_REQUIRES(mReentrantMonitor); bool ReadSegmentBeingWritten(nsPipeReadState& aReadState) MOZ_REQUIRES(mReentrantMonitor); uint32_t CountSegmentReferences(int32_t aSegment) MOZ_REQUIRES(mReentrantMonitor); void SetAllNullReadCursors() MOZ_REQUIRES(mReentrantMonitor); bool AllReadCursorsMatchWriteCursor() MOZ_REQUIRES(mReentrantMonitor); void RollBackAllReadCursors(char* aWriteCursor) MOZ_REQUIRES(mReentrantMonitor); void UpdateAllReadCursors(char* aWriteCursor) MOZ_REQUIRES(mReentrantMonitor); void ValidateAllReadCursors() MOZ_REQUIRES(mReentrantMonitor); uint32_t GetBufferSegmentCount(const nsPipeReadState& aReadState, const ReentrantMonitorAutoEnter& ev) const MOZ_REQUIRES(mReentrantMonitor); bool IsAdvanceBufferFull(const ReentrantMonitorAutoEnter& ev) const MOZ_REQUIRES(mReentrantMonitor); // // methods below may be called while outside the pipe's monitor // void DrainInputStream(nsPipeReadState& aReadState, nsPipeEvents& aEvents); nsresult GetWriteSegment(char*& aSegment, uint32_t& aSegmentLen); void AdvanceWriteCursor(uint32_t aCount); void OnInputStreamException(nsPipeInputStream* aStream, nsresult aReason); void OnPipeException(nsresult aReason, bool aOutputOnly = false); nsresult CloneInputStream(nsPipeInputStream* aOriginal, nsIInputStream** aCloneOut); // methods below should only be called by AutoReadSegment nsresult GetReadSegment(nsPipeReadState& aReadState, const char*& aSegment, uint32_t& aLength); void ReleaseReadSegment(nsPipeReadState& aReadState, nsPipeEvents& aEvents); void AdvanceReadCursor(nsPipeReadState& aReadState, uint32_t aCount); // We can't inherit from both nsIInputStream and nsIOutputStream // because they collide on their Close method. Consequently we nest their // implementations to avoid the extra object allocation. nsPipeOutputStream mOutput; // Since the input stream can be cloned, we may have more than one. Use // a weak reference as the streams will clear their entry here in their // destructor. Using a strong reference would create a reference cycle. // Only usable while mReentrantMonitor is locked. nsTArray mInputList MOZ_GUARDED_BY(mReentrantMonitor); ReentrantMonitor mReentrantMonitor; nsSegmentedBuffer mBuffer MOZ_GUARDED_BY(mReentrantMonitor); // The maximum number of segments to allow to be buffered in advance // of the fastest reader. This is collection of segments is called // the "advance buffer". uint32_t mMaxAdvanceBufferSegmentCount MOZ_GUARDED_BY(mReentrantMonitor); int32_t mWriteSegment MOZ_GUARDED_BY(mReentrantMonitor); char* mWriteCursor MOZ_GUARDED_BY(mReentrantMonitor); char* mWriteLimit MOZ_GUARDED_BY(mReentrantMonitor); // |mStatus| is protected by |mReentrantMonitor|. nsresult mStatus MOZ_GUARDED_BY(mReentrantMonitor); }; //----------------------------------------------------------------------------- // Declarations of Monitor() methods on the streams. // // These must be placed early to provide MOZ_RETURN_CAPABILITY annotations for // the thread-safety analysis. This couldn't be done at the declaration due to // nsPipe not yet being defined. ReentrantMonitor& nsPipeOutputStream::Monitor() const MOZ_RETURN_CAPABILITY(mPipe->mReentrantMonitor) { return mPipe->mReentrantMonitor; } ReentrantMonitor& nsPipeInputStream::Monitor() const MOZ_RETURN_CAPABILITY(mPipe->mReentrantMonitor) { return mPipe->mReentrantMonitor; } //----------------------------------------------------------------------------- // RAII class representing an active read segment. When it goes out of scope // it automatically updates the read cursor and releases the read segment. class MOZ_STACK_CLASS AutoReadSegment final { public: AutoReadSegment(nsPipe* aPipe, nsPipeReadState& aReadState, uint32_t aMaxLength) : mPipe(aPipe), mReadState(aReadState), mStatus(NS_ERROR_FAILURE), mSegment(nullptr), mLength(0), mOffset(0) { MOZ_DIAGNOSTIC_ASSERT(mPipe); MOZ_DIAGNOSTIC_ASSERT(!mReadState.mActiveRead); mStatus = mPipe->GetReadSegment(mReadState, mSegment, mLength); if (NS_SUCCEEDED(mStatus)) { MOZ_DIAGNOSTIC_ASSERT(mReadState.mActiveRead); MOZ_DIAGNOSTIC_ASSERT(mSegment); mLength = std::min(mLength, aMaxLength); MOZ_DIAGNOSTIC_ASSERT(mLength); } } ~AutoReadSegment() { if (NS_SUCCEEDED(mStatus)) { if (mOffset) { mPipe->AdvanceReadCursor(mReadState, mOffset); } else { nsPipeEvents events; mPipe->ReleaseReadSegment(mReadState, events); } } MOZ_DIAGNOSTIC_ASSERT(!mReadState.mActiveRead); } nsresult Status() const { return mStatus; } const char* Data() const { MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(mStatus)); MOZ_DIAGNOSTIC_ASSERT(mSegment); return mSegment + mOffset; } uint32_t Length() const { MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(mStatus)); MOZ_DIAGNOSTIC_ASSERT(mLength >= mOffset); return mLength - mOffset; } void Advance(uint32_t aCount) { MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(mStatus)); MOZ_DIAGNOSTIC_ASSERT(aCount <= (mLength - mOffset)); mOffset += aCount; } nsPipeReadState& ReadState() const { return mReadState; } private: // guaranteed to remain alive due to limited stack lifetime of AutoReadSegment nsPipe* mPipe; nsPipeReadState& mReadState; nsresult mStatus; const char* mSegment; uint32_t mLength; uint32_t mOffset; }; // // NOTES on buffer architecture: // // +-----------------+ - - mBuffer.GetSegment(0) // | | // + - - - - - - - - + - - nsPipeReadState.mReadCursor // |/////////////////| // |/////////////////| // |/////////////////| // |/////////////////| // +-----------------+ - - nsPipeReadState.mReadLimit // | // +-----------------+ // |/////////////////| // |/////////////////| // |/////////////////| // |/////////////////| // |/////////////////| // |/////////////////| // +-----------------+ // | // +-----------------+ - - mBuffer.GetSegment(mWriteSegment) // |/////////////////| // |/////////////////| // |/////////////////| // + - - - - - - - - + - - mWriteCursor // | | // | | // +-----------------+ - - mWriteLimit // // (shaded region contains data) // // NOTE: Each input stream produced by the nsPipe contains its own, separate // nsPipeReadState. This means there are multiple mReadCursor and // mReadLimit values in play. The pipe cannot discard old data until // all mReadCursors have moved beyond that point in the stream. // // Likewise, each input stream reader will have it's own amount of // buffered data. The pipe size threshold, however, is only applied // to the input stream that is being read fastest. We call this // the "advance buffer" in that its in advance of all readers. We // allow slower input streams to buffer more data so that we don't // stall processing of the faster input stream. // // NOTE: on some systems (notably OS/2), the heap allocator uses an arena for // small allocations (e.g., 64 byte allocations). this means that buffers may // be allocated back-to-back. in the diagram above, for example, mReadLimit // would actually be pointing at the beginning of the next segment. when // making changes to this file, please keep this fact in mind. // //----------------------------------------------------------------------------- // nsPipe methods: //----------------------------------------------------------------------------- nsPipe::nsPipe(uint32_t aSegmentSize, uint32_t aSegmentCount) : mOutput(this), mReentrantMonitor("nsPipe.mReentrantMonitor"), // protect against overflow mMaxAdvanceBufferSegmentCount( std::min(aSegmentCount, UINT32_MAX / aSegmentSize)), mWriteSegment(-1), mWriteCursor(nullptr), mWriteLimit(nullptr), mStatus(NS_OK) { // The internal buffer is always "infinite" so that we can allow // the size to expand when cloned streams are read at different // rates. We enforce a limit on how much data can be buffered // ahead of the fastest reader in GetWriteSegment(). MOZ_ALWAYS_SUCCEEDS(mBuffer.Init(aSegmentSize, UINT32_MAX)); } nsPipe::~nsPipe() = default; void nsPipe::PeekSegment(const nsPipeReadState& aReadState, uint32_t aIndex, char*& aCursor, char*& aLimit) { if (aIndex == 0) { MOZ_DIAGNOSTIC_ASSERT(!aReadState.mReadCursor || mBuffer.GetSegmentCount()); aCursor = aReadState.mReadCursor; aLimit = aReadState.mReadLimit; } else { uint32_t absoluteIndex = aReadState.mSegment + aIndex; uint32_t numSegments = mBuffer.GetSegmentCount(); if (absoluteIndex >= numSegments) { aCursor = aLimit = nullptr; } else { aCursor = mBuffer.GetSegment(absoluteIndex); if (mWriteSegment == (int32_t)absoluteIndex) { aLimit = mWriteCursor; } else { aLimit = aCursor + mBuffer.GetSegmentSize(); } } } } nsresult nsPipe::GetReadSegment(nsPipeReadState& aReadState, const char*& aSegment, uint32_t& aLength) { ReentrantMonitorAutoEnter mon(mReentrantMonitor); if (aReadState.mReadCursor == aReadState.mReadLimit) { return NS_FAILED(mStatus) ? mStatus : NS_BASE_STREAM_WOULD_BLOCK; } // The input stream locks the pipe while getting the buffer to read from, // but then unlocks while actual data copying is taking place. In // order to avoid deleting the buffer out from under this lockless read // set a flag to indicate a read is active. This flag is only modified // while the lock is held. MOZ_DIAGNOSTIC_ASSERT(!aReadState.mActiveRead); aReadState.mActiveRead = true; aSegment = aReadState.mReadCursor; aLength = aReadState.mReadLimit - aReadState.mReadCursor; MOZ_DIAGNOSTIC_ASSERT(aLength <= aReadState.mAvailable); return NS_OK; } void nsPipe::ReleaseReadSegment(nsPipeReadState& aReadState, nsPipeEvents& aEvents) { ReentrantMonitorAutoEnter mon(mReentrantMonitor); MOZ_DIAGNOSTIC_ASSERT(aReadState.mActiveRead); aReadState.mActiveRead = false; // When a read completes and releases the mActiveRead flag, we may have // blocked a drain from completing. This occurs when the input stream is // closed during the read. In these cases, we need to complete the drain as // soon as the active read completes. if (aReadState.mNeedDrain) { aReadState.mNeedDrain = false; DrainInputStream(aReadState, aEvents); } } void nsPipe::AdvanceReadCursor(nsPipeReadState& aReadState, uint32_t aBytesRead) { MOZ_DIAGNOSTIC_ASSERT(aBytesRead > 0); nsPipeEvents events; { ReentrantMonitorAutoEnter mon(mReentrantMonitor); LOG(("III advancing read cursor by %u\n", aBytesRead)); MOZ_DIAGNOSTIC_ASSERT(aBytesRead <= mBuffer.GetSegmentSize()); aReadState.mReadCursor += aBytesRead; MOZ_DIAGNOSTIC_ASSERT(aReadState.mReadCursor <= aReadState.mReadLimit); MOZ_DIAGNOSTIC_ASSERT(aReadState.mAvailable >= aBytesRead); aReadState.mAvailable -= aBytesRead; // Check to see if we're at the end of the available read data. If we // are, and this segment is not still being written, then we can possibly // free up the segment. if (aReadState.mReadCursor == aReadState.mReadLimit && !ReadSegmentBeingWritten(aReadState)) { // Advance the segment position. If we have read any segments from the // advance buffer then we can potentially notify blocked writers. mOutput.Monitor().AssertCurrentThreadIn(); if (AdvanceReadSegment(aReadState, mon) == SegmentAdvanceBufferRead && mOutput.OnOutputWritable(events) == NotifyMonitor) { mon.NotifyAll(); } } ReleaseReadSegment(aReadState, events); } } SegmentChangeResult nsPipe::AdvanceReadSegment( nsPipeReadState& aReadState, const ReentrantMonitorAutoEnter& ev) { // Calculate how many segments are buffered for this stream to start. uint32_t startBufferSegments = GetBufferSegmentCount(aReadState, ev); int32_t currentSegment = aReadState.mSegment; // Move to the next segment to read aReadState.mSegment += 1; // If this was the last reference to the first segment, then remove it. if (currentSegment == 0 && CountSegmentReferences(currentSegment) == 0) { // shift write and read segment index (-1 indicates an empty buffer). mWriteSegment -= 1; // Directly modify the current read state. If the associated input // stream is closed simultaneous with reading, then it may not be // in the mInputList any more. aReadState.mSegment -= 1; for (uint32_t i = 0; i < mInputList.Length(); ++i) { // Skip the current read state structure since we modify it manually // before entering this loop. if (&mInputList[i]->ReadState() == &aReadState) { continue; } mInputList[i]->ReadState().mSegment -= 1; } // done with this segment mBuffer.DeleteFirstSegment(); LOG(("III deleting first segment\n")); } if (mWriteSegment < aReadState.mSegment) { // read cursor has hit the end of written data, so reset it MOZ_DIAGNOSTIC_ASSERT(mWriteSegment == (aReadState.mSegment - 1)); aReadState.mReadCursor = nullptr; aReadState.mReadLimit = nullptr; // also, the buffer is completely empty, so reset the write cursor if (mWriteSegment == -1) { mWriteCursor = nullptr; mWriteLimit = nullptr; } } else { // advance read cursor and limit to next buffer segment aReadState.mReadCursor = mBuffer.GetSegment(aReadState.mSegment); if (mWriteSegment == aReadState.mSegment) { aReadState.mReadLimit = mWriteCursor; } else { aReadState.mReadLimit = aReadState.mReadCursor + mBuffer.GetSegmentSize(); } } // Calculate how many segments are buffered for the stream after // reading. uint32_t endBufferSegments = GetBufferSegmentCount(aReadState, ev); // If the stream has read a segment out of the set of advanced buffer // segments, then the writer may advance. if (startBufferSegments >= mMaxAdvanceBufferSegmentCount && endBufferSegments < mMaxAdvanceBufferSegmentCount) { return SegmentAdvanceBufferRead; } // Otherwise there are no significant changes to the segment structure. return SegmentNotChanged; } void nsPipe::DrainInputStream(nsPipeReadState& aReadState, nsPipeEvents& aEvents) { ReentrantMonitorAutoEnter mon(mReentrantMonitor); // If a segment is actively being read in ReadSegments() for this input // stream, then we cannot drain the stream. This can happen because // ReadSegments() does not hold the lock while copying from the buffer. // If we detect this condition, simply note that we need a drain once // the read completes and return immediately. if (aReadState.mActiveRead) { MOZ_DIAGNOSTIC_ASSERT(!aReadState.mNeedDrain); aReadState.mNeedDrain = true; return; } while (mWriteSegment >= aReadState.mSegment) { // If the last segment to free is still being written to, we're done // draining. We can't free any more. if (ReadSegmentBeingWritten(aReadState)) { break; } // Don't bother checking if this results in an advance buffer segment // read. Since we are draining the entire stream we will read an // advance buffer segment no matter what. AdvanceReadSegment(aReadState, mon); } // Force the stream into an empty state. Make sure mAvailable, mCursor, and // mReadLimit are consistent with one another. aReadState.mAvailable = 0; aReadState.mReadCursor = nullptr; aReadState.mReadLimit = nullptr; // Remove the input stream from the pipe's list of streams. This will // prevent the pipe from holding the stream alive or trying to update // its read state any further. DebugOnly numRemoved = 0; mInputList.RemoveElementsBy([&](nsPipeInputStream* aEntry) { bool result = &aReadState == &aEntry->ReadState(); numRemoved += result ? 1 : 0; return result; }); MOZ_ASSERT(numRemoved == 1); // If we have read any segments from the advance buffer then we can // potentially notify blocked writers. mOutput.Monitor().AssertCurrentThreadIn(); if (!IsAdvanceBufferFull(mon) && mOutput.OnOutputWritable(aEvents) == NotifyMonitor) { mon.NotifyAll(); } } bool nsPipe::ReadSegmentBeingWritten(nsPipeReadState& aReadState) { mReentrantMonitor.AssertCurrentThreadIn(); bool beingWritten = mWriteSegment == aReadState.mSegment && mWriteLimit > mWriteCursor; MOZ_DIAGNOSTIC_ASSERT(!beingWritten || aReadState.mReadLimit == mWriteCursor); return beingWritten; } nsresult nsPipe::GetWriteSegment(char*& aSegment, uint32_t& aSegmentLen) { ReentrantMonitorAutoEnter mon(mReentrantMonitor); if (NS_FAILED(mStatus)) { return mStatus; } // write cursor and limit may both be null indicating an empty buffer. if (mWriteCursor == mWriteLimit) { // The pipe is full if we have hit our limit on advance data buffering. // This means the fastest reader is still reading slower than data is // being written into the pipe. if (IsAdvanceBufferFull(mon)) { return NS_BASE_STREAM_WOULD_BLOCK; } // The nsSegmentedBuffer is configured to be "infinite", so this // should never return nullptr here. char* seg = mBuffer.AppendNewSegment(); if (!seg) { return NS_ERROR_OUT_OF_MEMORY; } LOG(("OOO appended new segment\n")); mWriteCursor = seg; mWriteLimit = mWriteCursor + mBuffer.GetSegmentSize(); ++mWriteSegment; } // make sure read cursor is initialized SetAllNullReadCursors(); // check to see if we can roll-back our read and write cursors to the // beginning of the current/first segment. this is purely an optimization. if (mWriteSegment == 0 && AllReadCursorsMatchWriteCursor()) { char* head = mBuffer.GetSegment(0); LOG(("OOO rolling back write cursor %" PRId64 " bytes\n", static_cast(mWriteCursor - head))); RollBackAllReadCursors(head); mWriteCursor = head; } aSegment = mWriteCursor; aSegmentLen = mWriteLimit - mWriteCursor; return NS_OK; } void nsPipe::AdvanceWriteCursor(uint32_t aBytesWritten) { MOZ_DIAGNOSTIC_ASSERT(aBytesWritten > 0); nsPipeEvents events; { ReentrantMonitorAutoEnter mon(mReentrantMonitor); LOG(("OOO advancing write cursor by %u\n", aBytesWritten)); char* newWriteCursor = mWriteCursor + aBytesWritten; MOZ_DIAGNOSTIC_ASSERT(newWriteCursor <= mWriteLimit); // update read limit if reading in the same segment UpdateAllReadCursors(newWriteCursor); mWriteCursor = newWriteCursor; ValidateAllReadCursors(); // update the writable flag on the output stream if (mWriteCursor == mWriteLimit) { mOutput.Monitor().AssertCurrentThreadIn(); mOutput.SetWritable(!IsAdvanceBufferFull(mon)); } // notify input stream that pipe now contains additional data bool needNotify = false; for (uint32_t i = 0; i < mInputList.Length(); ++i) { mInputList[i]->Monitor().AssertCurrentThreadIn(); if (mInputList[i]->OnInputReadable(aBytesWritten, events, mon) == NotifyMonitor) { needNotify = true; } } if (needNotify) { mon.NotifyAll(); } } } void nsPipe::OnInputStreamException(nsPipeInputStream* aStream, nsresult aReason) { MOZ_DIAGNOSTIC_ASSERT(NS_FAILED(aReason)); nsPipeEvents events; { ReentrantMonitorAutoEnter mon(mReentrantMonitor); // Its possible to re-enter this method when we call OnPipeException() or // OnInputExection() below. If there is a caller stuck in our synchronous // Wait() method, then they will get woken up with a failure code which // re-enters this method. Therefore, gracefully handle unknown streams // here. // If we only have one stream open and it is the given stream, then shut // down the entire pipe. if (mInputList.Length() == 1) { if (mInputList[0] == aStream) { OnPipeException(aReason); } return; } // Otherwise just close the particular stream that hit an exception. for (uint32_t i = 0; i < mInputList.Length(); ++i) { if (mInputList[i] != aStream) { continue; } mInputList[i]->Monitor().AssertCurrentThreadIn(); MonitorAction action = mInputList[i]->OnInputException(aReason, events, mon); // Notify after element is removed in case we re-enter as a result. if (action == NotifyMonitor) { mon.NotifyAll(); } return; } } } void nsPipe::OnPipeException(nsresult aReason, bool aOutputOnly) { LOG(("PPP nsPipe::OnPipeException [reason=%" PRIx32 " output-only=%d]\n", static_cast(aReason), aOutputOnly)); nsPipeEvents events; { ReentrantMonitorAutoEnter mon(mReentrantMonitor); // if we've already hit an exception, then ignore this one. if (NS_FAILED(mStatus)) { return; } mStatus = aReason; bool needNotify = false; // OnInputException() can drain the stream and remove it from // mInputList. So iterate over a temp list instead. nsTArray list = mInputList.Clone(); for (uint32_t i = 0; i < list.Length(); ++i) { // an output-only exception applies to the input end if the pipe has // zero bytes available. list[i]->Monitor().AssertCurrentThreadIn(); if (aOutputOnly && list[i]->Available()) { continue; } if (list[i]->OnInputException(aReason, events, mon) == NotifyMonitor) { needNotify = true; } } mOutput.Monitor().AssertCurrentThreadIn(); if (mOutput.OnOutputException(aReason, events) == NotifyMonitor) { needNotify = true; } // Notify after we have removed any input streams from mInputList if (needNotify) { mon.NotifyAll(); } } } nsresult nsPipe::CloneInputStream(nsPipeInputStream* aOriginal, nsIInputStream** aCloneOut) { ReentrantMonitorAutoEnter mon(mReentrantMonitor); RefPtr ref = new nsPipeInputStream(*aOriginal); // don't add clones of closed pipes to mInputList. ref->Monitor().AssertCurrentThreadIn(); if (NS_SUCCEEDED(ref->InputStatus(mon))) { mInputList.AppendElement(ref); } nsCOMPtr upcast = std::move(ref); upcast.forget(aCloneOut); return NS_OK; } uint32_t nsPipe::CountSegmentReferences(int32_t aSegment) { mReentrantMonitor.AssertCurrentThreadIn(); uint32_t count = 0; for (uint32_t i = 0; i < mInputList.Length(); ++i) { if (aSegment >= mInputList[i]->ReadState().mSegment) { count += 1; } } return count; } void nsPipe::SetAllNullReadCursors() { mReentrantMonitor.AssertCurrentThreadIn(); for (uint32_t i = 0; i < mInputList.Length(); ++i) { nsPipeReadState& readState = mInputList[i]->ReadState(); if (!readState.mReadCursor) { MOZ_DIAGNOSTIC_ASSERT(mWriteSegment == readState.mSegment); readState.mReadCursor = readState.mReadLimit = mWriteCursor; } } } bool nsPipe::AllReadCursorsMatchWriteCursor() { mReentrantMonitor.AssertCurrentThreadIn(); for (uint32_t i = 0; i < mInputList.Length(); ++i) { const nsPipeReadState& readState = mInputList[i]->ReadState(); if (readState.mSegment != mWriteSegment || readState.mReadCursor != mWriteCursor) { return false; } } return true; } void nsPipe::RollBackAllReadCursors(char* aWriteCursor) { mReentrantMonitor.AssertCurrentThreadIn(); for (uint32_t i = 0; i < mInputList.Length(); ++i) { nsPipeReadState& readState = mInputList[i]->ReadState(); MOZ_DIAGNOSTIC_ASSERT(mWriteSegment == readState.mSegment); MOZ_DIAGNOSTIC_ASSERT(mWriteCursor == readState.mReadCursor); MOZ_DIAGNOSTIC_ASSERT(mWriteCursor == readState.mReadLimit); readState.mReadCursor = aWriteCursor; readState.mReadLimit = aWriteCursor; } } void nsPipe::UpdateAllReadCursors(char* aWriteCursor) { mReentrantMonitor.AssertCurrentThreadIn(); for (uint32_t i = 0; i < mInputList.Length(); ++i) { nsPipeReadState& readState = mInputList[i]->ReadState(); if (mWriteSegment == readState.mSegment && readState.mReadLimit == mWriteCursor) { readState.mReadLimit = aWriteCursor; } } } void nsPipe::ValidateAllReadCursors() { mReentrantMonitor.AssertCurrentThreadIn(); // The only way mReadCursor == mWriteCursor is if: // // - mReadCursor is at the start of a segment (which, based on how // nsSegmentedBuffer works, means that this segment is the "first" // segment) // - mWriteCursor points at the location past the end of the current // write segment (so the current write filled the current write // segment, so we've incremented mWriteCursor to point past the end // of it) // - the segment to which data has just been written is located // exactly one segment's worth of bytes before the first segment // where mReadCursor is located // // Consequently, the byte immediately after the end of the current // write segment is the first byte of the first segment, so // mReadCursor == mWriteCursor. (Another way to think about this is // to consider the buffer architecture diagram above, but consider it // with an arena allocator which allocates from the *end* of the // arena to the *beginning* of the arena.) #ifdef DEBUG for (uint32_t i = 0; i < mInputList.Length(); ++i) { const nsPipeReadState& state = mInputList[i]->ReadState(); MOZ_ASSERT(state.mReadCursor != mWriteCursor || (mBuffer.GetSegment(state.mSegment) == state.mReadCursor && mWriteCursor == mWriteLimit)); } #endif } uint32_t nsPipe::GetBufferSegmentCount( const nsPipeReadState& aReadState, const ReentrantMonitorAutoEnter& ev) const { // The write segment can be smaller than the current reader position // in some cases. For example, when the first write segment has not // been allocated yet mWriteSegment is negative. In these cases // the stream is effectively using zero segments. if (mWriteSegment < aReadState.mSegment) { return 0; } MOZ_DIAGNOSTIC_ASSERT(mWriteSegment >= 0); MOZ_DIAGNOSTIC_ASSERT(aReadState.mSegment >= 0); // Otherwise at least one segment is being used. We add one here // since a single segment is being used when the write and read // segment indices are the same. return 1 + mWriteSegment - aReadState.mSegment; } bool nsPipe::IsAdvanceBufferFull(const ReentrantMonitorAutoEnter& ev) const { // If we have fewer total segments than the limit we can immediately // determine we are not full. Note, we must add one to mWriteSegment // to convert from a index to a count. MOZ_DIAGNOSTIC_ASSERT(mWriteSegment >= -1); MOZ_DIAGNOSTIC_ASSERT(mWriteSegment < INT32_MAX); uint32_t totalWriteSegments = mWriteSegment + 1; if (totalWriteSegments < mMaxAdvanceBufferSegmentCount) { return false; } // Otherwise we must inspect all of our reader streams. We need // to determine the buffer depth of the fastest reader. uint32_t minBufferSegments = UINT32_MAX; for (uint32_t i = 0; i < mInputList.Length(); ++i) { // Only count buffer segments from input streams that are open. mInputList[i]->Monitor().AssertCurrentThreadIn(); if (NS_FAILED(mInputList[i]->Status(ev))) { continue; } const nsPipeReadState& state = mInputList[i]->ReadState(); uint32_t bufferSegments = GetBufferSegmentCount(state, ev); minBufferSegments = std::min(minBufferSegments, bufferSegments); // We only care if any reader has fewer segments buffered than // our threshold. We can stop once we hit that threshold. if (minBufferSegments < mMaxAdvanceBufferSegmentCount) { return false; } } // Note, its possible for minBufferSegments to exceed our // mMaxAdvanceBufferSegmentCount here. This happens when a cloned // reader gets far behind, but then the fastest reader stream is // closed. This leaves us with a single stream that is buffered // beyond our max. Naturally we continue to indicate the pipe // is full at this point. return true; } //----------------------------------------------------------------------------- // nsPipeEvents methods: //----------------------------------------------------------------------------- nsPipeEvents::~nsPipeEvents() { // dispatch any pending events for (auto& callback : mCallbacks) { callback.Notify(); } mCallbacks.Clear(); } //----------------------------------------------------------------------------- // nsPipeInputStream methods: //----------------------------------------------------------------------------- NS_IMPL_ADDREF(nsPipeInputStream); NS_IMPL_RELEASE(nsPipeInputStream); NS_INTERFACE_TABLE_HEAD(nsPipeInputStream) NS_INTERFACE_TABLE_BEGIN NS_INTERFACE_TABLE_ENTRY(nsPipeInputStream, nsIAsyncInputStream) NS_INTERFACE_TABLE_ENTRY(nsPipeInputStream, nsITellableStream) NS_INTERFACE_TABLE_ENTRY(nsPipeInputStream, nsISearchableInputStream) NS_INTERFACE_TABLE_ENTRY(nsPipeInputStream, nsICloneableInputStream) NS_INTERFACE_TABLE_ENTRY(nsPipeInputStream, nsIBufferedInputStream) NS_INTERFACE_TABLE_ENTRY(nsPipeInputStream, nsIClassInfo) NS_INTERFACE_TABLE_ENTRY(nsPipeInputStream, nsIInputStreamPriority) NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsPipeInputStream, nsIInputStream, nsIAsyncInputStream) NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsPipeInputStream, nsISupports, nsIAsyncInputStream) NS_INTERFACE_TABLE_END NS_INTERFACE_TABLE_TAIL NS_IMPL_CI_INTERFACE_GETTER(nsPipeInputStream, nsIInputStream, nsIAsyncInputStream, nsITellableStream, nsISearchableInputStream, nsICloneableInputStream, nsIBufferedInputStream) NS_IMPL_THREADSAFE_CI(nsPipeInputStream) NS_IMETHODIMP nsPipeInputStream::Init(nsIInputStream*, uint32_t) { MOZ_CRASH( "nsPipeInputStream should never be initialized with " "nsIBufferedInputStream::Init!\n"); } NS_IMETHODIMP nsPipeInputStream::GetData(nsIInputStream** aResult) { // as this was not created with init() we are not // wrapping anything return NS_ERROR_NOT_IMPLEMENTED; } uint32_t nsPipeInputStream::Available() { mPipe->mReentrantMonitor.AssertCurrentThreadIn(); return mReadState.mAvailable; } nsresult nsPipeInputStream::Wait() { MOZ_DIAGNOSTIC_ASSERT(mBlocking); ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); while (NS_SUCCEEDED(Status(mon)) && (mReadState.mAvailable == 0)) { LOG(("III pipe input: waiting for data\n")); mBlocked = true; mon.Wait(); mBlocked = false; LOG(("III pipe input: woke up [status=%" PRIx32 " available=%u]\n", static_cast(Status(mon)), mReadState.mAvailable)); } return Status(mon) == NS_BASE_STREAM_CLOSED ? NS_OK : Status(mon); } MonitorAction nsPipeInputStream::OnInputReadable( uint32_t aBytesWritten, nsPipeEvents& aEvents, const ReentrantMonitorAutoEnter& ev) { MonitorAction result = DoNotNotifyMonitor; mPipe->mReentrantMonitor.AssertCurrentThreadIn(); mReadState.mAvailable += aBytesWritten; if (mCallback && !(mCallback.Flags() & WAIT_CLOSURE_ONLY)) { aEvents.NotifyReady(std::move(mCallback)); } else if (mBlocked) { result = NotifyMonitor; } return result; } MonitorAction nsPipeInputStream::OnInputException( nsresult aReason, nsPipeEvents& aEvents, const ReentrantMonitorAutoEnter& ev) { LOG(("nsPipeInputStream::OnInputException [this=%p reason=%" PRIx32 "]\n", this, static_cast(aReason))); MonitorAction result = DoNotNotifyMonitor; MOZ_DIAGNOSTIC_ASSERT(NS_FAILED(aReason)); if (NS_SUCCEEDED(mInputStatus)) { mInputStatus = aReason; } // force count of available bytes to zero. mPipe->DrainInputStream(mReadState, aEvents); if (mCallback) { aEvents.NotifyReady(std::move(mCallback)); } else if (mBlocked) { result = NotifyMonitor; } return result; } NS_IMETHODIMP nsPipeInputStream::CloseWithStatus(nsresult aReason) { LOG(("III CloseWithStatus [this=%p reason=%" PRIx32 "]\n", this, static_cast(aReason))); ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); if (NS_FAILED(mInputStatus)) { return NS_OK; } if (NS_SUCCEEDED(aReason)) { aReason = NS_BASE_STREAM_CLOSED; } mPipe->OnInputStreamException(this, aReason); return NS_OK; } NS_IMETHODIMP nsPipeInputStream::SetPriority(uint32_t priority) { mPriority = priority; return NS_OK; } NS_IMETHODIMP nsPipeInputStream::GetPriority(uint32_t* priority) { *priority = mPriority; return NS_OK; } NS_IMETHODIMP nsPipeInputStream::Close() { return CloseWithStatus(NS_BASE_STREAM_CLOSED); } NS_IMETHODIMP nsPipeInputStream::Available(uint64_t* aResult) { // nsPipeInputStream supports under 4GB stream only ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); // return error if closed if (!mReadState.mAvailable && NS_FAILED(Status(mon))) { return Status(mon); } *aResult = (uint64_t)mReadState.mAvailable; return NS_OK; } NS_IMETHODIMP nsPipeInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount, uint32_t* aReadCount) { LOG(("III ReadSegments [this=%p count=%u]\n", this, aCount)); nsresult rv = NS_OK; *aReadCount = 0; while (aCount) { AutoReadSegment segment(mPipe, mReadState, aCount); rv = segment.Status(); if (NS_FAILED(rv)) { // ignore this error if we've already read something. if (*aReadCount > 0) { rv = NS_OK; break; } if (rv == NS_BASE_STREAM_WOULD_BLOCK) { // pipe is empty if (!mBlocking) { break; } // wait for some data to be written to the pipe rv = Wait(); if (NS_SUCCEEDED(rv)) { continue; } } // ignore this error, just return. if (rv == NS_BASE_STREAM_CLOSED) { rv = NS_OK; break; } mPipe->OnInputStreamException(this, rv); break; } uint32_t writeCount; while (segment.Length()) { writeCount = 0; rv = aWriter(static_cast(this), aClosure, segment.Data(), *aReadCount, segment.Length(), &writeCount); if (NS_FAILED(rv) || writeCount == 0) { aCount = 0; // any errors returned from the writer end here: do not // propagate to the caller of ReadSegments. rv = NS_OK; break; } MOZ_DIAGNOSTIC_ASSERT(writeCount <= segment.Length()); segment.Advance(writeCount); aCount -= writeCount; *aReadCount += writeCount; mLogicalOffset += writeCount; } } return rv; } NS_IMETHODIMP nsPipeInputStream::Read(char* aToBuf, uint32_t aBufLen, uint32_t* aReadCount) { return ReadSegments(NS_CopySegmentToBuffer, aToBuf, aBufLen, aReadCount); } NS_IMETHODIMP nsPipeInputStream::IsNonBlocking(bool* aNonBlocking) { *aNonBlocking = !mBlocking; return NS_OK; } NS_IMETHODIMP nsPipeInputStream::AsyncWait(nsIInputStreamCallback* aCallback, uint32_t aFlags, uint32_t aRequestedCount, nsIEventTarget* aTarget) { LOG(("III AsyncWait [this=%p]\n", this)); nsPipeEvents pipeEvents; { ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); // replace a pending callback mCallback = nullptr; if (!aCallback) { return NS_OK; } CallbackHolder callback(this, aCallback, aFlags, aTarget); if (NS_FAILED(Status(mon)) || (mReadState.mAvailable && !(aFlags & WAIT_CLOSURE_ONLY))) { // stream is already closed or readable; post event. pipeEvents.NotifyReady(std::move(callback)); } else { // queue up callback object to be notified when data becomes available mCallback = std::move(callback); } } return NS_OK; } NS_IMETHODIMP nsPipeInputStream::Tell(int64_t* aOffset) { ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); // return error if closed if (!mReadState.mAvailable && NS_FAILED(Status(mon))) { return Status(mon); } *aOffset = mLogicalOffset; return NS_OK; } static bool strings_equal(bool aIgnoreCase, const char* aS1, const char* aS2, uint32_t aLen) { return aIgnoreCase ? !nsCRT::strncasecmp(aS1, aS2, aLen) : !strncmp(aS1, aS2, aLen); } NS_IMETHODIMP nsPipeInputStream::Search(const char* aForString, bool aIgnoreCase, bool* aFound, uint32_t* aOffsetSearchedTo) { LOG(("III Search [for=%s ic=%u]\n", aForString, aIgnoreCase)); ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); char* cursor1; char* limit1; uint32_t index = 0, offset = 0; uint32_t strLen = strlen(aForString); mPipe->PeekSegment(mReadState, 0, cursor1, limit1); if (cursor1 == limit1) { *aFound = false; *aOffsetSearchedTo = 0; LOG((" result [aFound=%u offset=%u]\n", *aFound, *aOffsetSearchedTo)); return NS_OK; } while (true) { uint32_t i, len1 = limit1 - cursor1; // check if the string is in the buffer segment for (i = 0; i < len1 - strLen + 1; i++) { if (strings_equal(aIgnoreCase, &cursor1[i], aForString, strLen)) { *aFound = true; *aOffsetSearchedTo = offset + i; LOG((" result [aFound=%u offset=%u]\n", *aFound, *aOffsetSearchedTo)); return NS_OK; } } // get the next segment char* cursor2; char* limit2; uint32_t len2; index++; offset += len1; mPipe->PeekSegment(mReadState, index, cursor2, limit2); if (cursor2 == limit2) { *aFound = false; *aOffsetSearchedTo = offset - strLen + 1; LOG((" result [aFound=%u offset=%u]\n", *aFound, *aOffsetSearchedTo)); return NS_OK; } len2 = limit2 - cursor2; // check if the string is straddling the next buffer segment uint32_t lim = XPCOM_MIN(strLen, len2 + 1); for (i = 0; i < lim; ++i) { uint32_t strPart1Len = strLen - i - 1; uint32_t strPart2Len = strLen - strPart1Len; const char* strPart2 = &aForString[strLen - strPart2Len]; uint32_t bufSeg1Offset = len1 - strPart1Len; if (strings_equal(aIgnoreCase, &cursor1[bufSeg1Offset], aForString, strPart1Len) && strings_equal(aIgnoreCase, cursor2, strPart2, strPart2Len)) { *aFound = true; *aOffsetSearchedTo = offset - strPart1Len; LOG((" result [aFound=%u offset=%u]\n", *aFound, *aOffsetSearchedTo)); return NS_OK; } } // finally continue with the next buffer cursor1 = cursor2; limit1 = limit2; } MOZ_ASSERT_UNREACHABLE("can't get here"); return NS_ERROR_UNEXPECTED; // keep compiler happy } NS_IMETHODIMP nsPipeInputStream::GetCloneable(bool* aCloneableOut) { *aCloneableOut = true; return NS_OK; } NS_IMETHODIMP nsPipeInputStream::Clone(nsIInputStream** aCloneOut) { return mPipe->CloneInputStream(this, aCloneOut); } nsresult nsPipeInputStream::Status(const ReentrantMonitorAutoEnter& ev) const { if (NS_FAILED(mInputStatus)) { return mInputStatus; } if (mReadState.mAvailable) { // Still something to read and this input stream state is OK. return NS_OK; } // Nothing to read, just fall through to the pipe's state that // may reflect state of its output stream side (already closed). return mPipe->mStatus; } nsresult nsPipeInputStream::Status() const { ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); return Status(mon); } nsPipeInputStream::~nsPipeInputStream() { Close(); } //----------------------------------------------------------------------------- // nsPipeOutputStream methods: //----------------------------------------------------------------------------- NS_IMPL_QUERY_INTERFACE(nsPipeOutputStream, nsIOutputStream, nsIAsyncOutputStream, nsIClassInfo) NS_IMPL_CI_INTERFACE_GETTER(nsPipeOutputStream, nsIOutputStream, nsIAsyncOutputStream) NS_IMPL_THREADSAFE_CI(nsPipeOutputStream) nsresult nsPipeOutputStream::Wait() { MOZ_DIAGNOSTIC_ASSERT(mBlocking); ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); if (NS_SUCCEEDED(mPipe->mStatus) && !mWritable) { LOG(("OOO pipe output: waiting for space\n")); mBlocked = true; mon.Wait(); mBlocked = false; LOG(("OOO pipe output: woke up [pipe-status=%" PRIx32 " writable=%u]\n", static_cast(mPipe->mStatus), mWritable)); } return mPipe->mStatus == NS_BASE_STREAM_CLOSED ? NS_OK : mPipe->mStatus; } MonitorAction nsPipeOutputStream::OnOutputWritable(nsPipeEvents& aEvents) { MonitorAction result = DoNotNotifyMonitor; mWritable = true; if (mCallback && !(mCallback.Flags() & WAIT_CLOSURE_ONLY)) { aEvents.NotifyReady(std::move(mCallback)); } else if (mBlocked) { result = NotifyMonitor; } return result; } MonitorAction nsPipeOutputStream::OnOutputException(nsresult aReason, nsPipeEvents& aEvents) { LOG(("nsPipeOutputStream::OnOutputException [this=%p reason=%" PRIx32 "]\n", this, static_cast(aReason))); MonitorAction result = DoNotNotifyMonitor; MOZ_DIAGNOSTIC_ASSERT(NS_FAILED(aReason)); mWritable = false; if (mCallback) { aEvents.NotifyReady(std::move(mCallback)); } else if (mBlocked) { result = NotifyMonitor; } return result; } NS_IMETHODIMP_(MozExternalRefCountType) nsPipeOutputStream::AddRef() { ++mWriterRefCnt; return mPipe->AddRef(); } NS_IMETHODIMP_(MozExternalRefCountType) nsPipeOutputStream::Release() { if (--mWriterRefCnt == 0) { Close(); } return mPipe->Release(); } NS_IMETHODIMP nsPipeOutputStream::CloseWithStatus(nsresult aReason) { LOG(("OOO CloseWithStatus [this=%p reason=%" PRIx32 "]\n", this, static_cast(aReason))); if (NS_SUCCEEDED(aReason)) { aReason = NS_BASE_STREAM_CLOSED; } // input stream may remain open mPipe->OnPipeException(aReason, true); return NS_OK; } NS_IMETHODIMP nsPipeOutputStream::Close() { return CloseWithStatus(NS_BASE_STREAM_CLOSED); } NS_IMETHODIMP nsPipeOutputStream::WriteSegments(nsReadSegmentFun aReader, void* aClosure, uint32_t aCount, uint32_t* aWriteCount) { LOG(("OOO WriteSegments [this=%p count=%u]\n", this, aCount)); nsresult rv = NS_OK; char* segment; uint32_t segmentLen; *aWriteCount = 0; while (aCount) { rv = mPipe->GetWriteSegment(segment, segmentLen); if (NS_FAILED(rv)) { if (rv == NS_BASE_STREAM_WOULD_BLOCK) { // pipe is full if (!mBlocking) { // ignore this error if we've already written something if (*aWriteCount > 0) { rv = NS_OK; } break; } // wait for the pipe to have an empty segment. rv = Wait(); if (NS_SUCCEEDED(rv)) { continue; } } mPipe->OnPipeException(rv); break; } // write no more than aCount if (segmentLen > aCount) { segmentLen = aCount; } uint32_t readCount, originalLen = segmentLen; while (segmentLen) { readCount = 0; rv = aReader(this, aClosure, segment, *aWriteCount, segmentLen, &readCount); if (NS_FAILED(rv) || readCount == 0) { aCount = 0; // any errors returned from the aReader end here: do not // propagate to the caller of WriteSegments. rv = NS_OK; break; } MOZ_DIAGNOSTIC_ASSERT(readCount <= segmentLen); segment += readCount; segmentLen -= readCount; aCount -= readCount; *aWriteCount += readCount; mLogicalOffset += readCount; } if (segmentLen < originalLen) { mPipe->AdvanceWriteCursor(originalLen - segmentLen); } } return rv; } NS_IMETHODIMP nsPipeOutputStream::Write(const char* aFromBuf, uint32_t aBufLen, uint32_t* aWriteCount) { return WriteSegments(NS_CopyBufferToSegment, (void*)aFromBuf, aBufLen, aWriteCount); } NS_IMETHODIMP nsPipeOutputStream::Flush(void) { // nothing to do return NS_OK; } NS_IMETHODIMP nsPipeOutputStream::WriteFrom(nsIInputStream* aFromStream, uint32_t aCount, uint32_t* aWriteCount) { return WriteSegments(NS_CopyStreamToSegment, aFromStream, aCount, aWriteCount); } NS_IMETHODIMP nsPipeOutputStream::IsNonBlocking(bool* aNonBlocking) { *aNonBlocking = !mBlocking; return NS_OK; } NS_IMETHODIMP nsPipeOutputStream::AsyncWait(nsIOutputStreamCallback* aCallback, uint32_t aFlags, uint32_t aRequestedCount, nsIEventTarget* aTarget) { LOG(("OOO AsyncWait [this=%p]\n", this)); nsPipeEvents pipeEvents; { ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); // replace a pending callback mCallback = nullptr; if (!aCallback) { return NS_OK; } CallbackHolder callback(this, aCallback, aFlags, aTarget); if (NS_FAILED(mPipe->mStatus) || (mWritable && !(aFlags & WAIT_CLOSURE_ONLY))) { // stream is already closed or writable; post event. pipeEvents.NotifyReady(std::move(callback)); } else { // queue up callback object to be notified when data becomes available mCallback = std::move(callback); } } return NS_OK; } //////////////////////////////////////////////////////////////////////////////// void NS_NewPipe(nsIInputStream** aPipeIn, nsIOutputStream** aPipeOut, uint32_t aSegmentSize, uint32_t aMaxSize, bool aNonBlockingInput, bool aNonBlockingOutput) { if (aSegmentSize == 0) { aSegmentSize = DEFAULT_SEGMENT_SIZE; } // Handle aMaxSize of UINT32_MAX as a special case uint32_t segmentCount; if (aMaxSize == UINT32_MAX) { segmentCount = UINT32_MAX; } else { segmentCount = aMaxSize / aSegmentSize; } nsIAsyncInputStream* in; nsIAsyncOutputStream* out; NS_NewPipe2(&in, &out, aNonBlockingInput, aNonBlockingOutput, aSegmentSize, segmentCount); *aPipeIn = in; *aPipeOut = out; } // Disable thread safety analysis as this is logically a constructor, and no // additional threads can observe these objects yet. void NS_NewPipe2(nsIAsyncInputStream** aPipeIn, nsIAsyncOutputStream** aPipeOut, bool aNonBlockingInput, bool aNonBlockingOutput, uint32_t aSegmentSize, uint32_t aSegmentCount) MOZ_NO_THREAD_SAFETY_ANALYSIS { RefPtr pipe = new nsPipe(aSegmentSize ? aSegmentSize : DEFAULT_SEGMENT_SIZE, aSegmentCount ? aSegmentCount : DEFAULT_SEGMENT_COUNT); RefPtr pipeIn = new nsPipeInputStream(pipe); pipe->mInputList.AppendElement(pipeIn); RefPtr pipeOut = &pipe->mOutput; pipeIn->SetNonBlocking(aNonBlockingInput); pipeOut->SetNonBlocking(aNonBlockingOutput); pipeIn.forget(aPipeIn); pipeOut.forget(aPipeOut); } //////////////////////////////////////////////////////////////////////////////// // Thin nsIPipe implementation for consumers of the component manager interface // for creating pipes. Acts as a thin wrapper around NS_NewPipe2 for JS callers. class nsPipeHolder final : public nsIPipe { public: NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIPIPE private: ~nsPipeHolder() = default; nsCOMPtr mInput; nsCOMPtr mOutput; }; NS_IMPL_ISUPPORTS(nsPipeHolder, nsIPipe) NS_IMETHODIMP nsPipeHolder::Init(bool aNonBlockingInput, bool aNonBlockingOutput, uint32_t aSegmentSize, uint32_t aSegmentCount) { if (mInput || mOutput) { return NS_ERROR_ALREADY_INITIALIZED; } NS_NewPipe2(getter_AddRefs(mInput), getter_AddRefs(mOutput), aNonBlockingInput, aNonBlockingOutput, aSegmentSize, aSegmentCount); return NS_OK; } NS_IMETHODIMP nsPipeHolder::GetInputStream(nsIAsyncInputStream** aInputStream) { if (mInput) { *aInputStream = do_AddRef(mInput).take(); return NS_OK; } return NS_ERROR_NOT_INITIALIZED; } NS_IMETHODIMP nsPipeHolder::GetOutputStream(nsIAsyncOutputStream** aOutputStream) { if (mOutput) { *aOutputStream = do_AddRef(mOutput).take(); return NS_OK; } return NS_ERROR_NOT_INITIALIZED; } nsresult nsPipeConstructor(REFNSIID aIID, void** aResult) { RefPtr pipe = new nsPipeHolder(); nsresult rv = pipe->QueryInterface(aIID, aResult); return rv; } ////////////////////////////////////////////////////////////////////////////////