gecko-dev/xpcom/io/SeekableStreamWrapper.h
Nika Layzell 82b549eebb Bug 1681529 - Part 6: Introduce a SeekableStreamWrapper to make pipes seekable, r=baku
This patch introduces a new SeekableStreamWrapper class which handles adapting
nsIInputStreams which support being cheaply cloned using nsICloneableInputStream
into seekable input streams by operating on a clone of the original stream, and
re-cloning that stream when seeking backwards.

This wrapper is generally intended to be used with nsPipeInputStream as that
type supports both a fairly cheap clone operation, and keeping a large internal
buffer which is fairly cheap to seek using this method, but should also work
with other types such as RemoteLazyInputStream or nsStringStream.

An alternate strategy was considered where nsPipe was given internal support for
a mSeekable flag to be set on creation. This flag would then have a similar
effect, except with additional optimizations due to being visible within the
implementation of the nsPipe, rather than relying on an unadvanced
nsPipeInputStream to keep the buffer alive.

I ended up choosing this approach instead for a few reasons:

 * The seekable adapter can be applied to an already-created nsPipeInputStream,
   such as one received from IPC. With the nsPipe approach, making an IPC stream
   seekable either requires telling IPCStreamDestination to use a seekable pipe
   ahead of time, or performing a NS_AsyncCopy from the IPC-provided pipe into a
   different seekable pipe, which is likely wasted effort and would prevent
   optimizations such as RemoteLazyInputStream and DelayedStart streams.
 * The adapter can support other features of the underlying stream, such as
   nsIInputStreamLength, without resorting to adding additional adapter layers
   on top of the returned nsPipe.
 * The performance is unlikely to be substantially different in the most common
   case, which is using Seek(NS_SEEK_SET, 0) to return to the beginning of the
   stream.
 * Less additional complexity is added to the already-complicated internals of
   nsPipe, and instead it is kept in a separate wrapper stream, which is easier
   to review.

Using nsStorageStream, as is used by EnsureUploadStreamIsCloneable, was also
considered, but was rejected as it has similar problems to the seekable nsPipe
approach and also doesn't implement nsIAsyncStream, meaning that one must wait
for the NS_AsyncCopy to be completed before reading the stream.

It may actually be possible to replace the existing uses of nsStorageStream with
a wrapped nsPipe in the future, but that is left as follow-up material, and may
have memory overhead implications due to nsPipe not resizing the final segment,
unlike nsStorageStream.

Differential Revision: https://phabricator.services.mozilla.com/D101805
2021-02-04 18:13:09 +00:00

92 lines
3.4 KiB
C++

/* -*- 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/. */
#ifndef mozilla_SeekableStreamWrapper_h
#define mozilla_SeekableStreamWrapper_h
#include "mozilla/Assertions.h"
#include "mozilla/Mutex.h"
#include "nsCOMPtr.h"
#include "nsIAsyncInputStream.h"
#include "nsISeekableStream.h"
#include "nsICloneableInputStream.h"
#include "nsIIPCSerializableInputStream.h"
#include "nsIInputStreamLength.h"
#include "nsIBufferedStreams.h"
namespace mozilla {
// Adapter class which converts an input stream implementing
// `nsICloneableInputStream`, such as `nsPipe`, into a stream implementing
// `nsISeekableStream`. This is done by keeping a clone of the original unwound
// input stream to allow rewinding the stream back to its original position.
class SeekableStreamWrapper final : public nsIAsyncInputStream,
public nsISeekableStream,
public nsICloneableInputStream,
public nsIInputStreamLength,
public nsIAsyncInputStreamLength,
public nsIIPCSerializableInputStream,
public nsIBufferedInputStream {
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIINPUTSTREAM
NS_DECL_NSIASYNCINPUTSTREAM
NS_DECL_NSITELLABLESTREAM
NS_DECL_NSISEEKABLESTREAM
NS_DECL_NSICLONEABLEINPUTSTREAM
NS_DECL_NSIINPUTSTREAMLENGTH
NS_DECL_NSIASYNCINPUTSTREAMLENGTH
NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
NS_DECL_NSIBUFFEREDINPUTSTREAM
public:
/**
* This function returns a nsIInputStream which is known to implement
* nsISeekableStream. This will return |aSource| if it already implements
* nsISeekableStream, and will wrap it if it does not.
*
* If the original stream implements `nsICloneableInputStream`, this will be
* used to implement the rewinding functionality necessary for
* `nsISeekableStream`, otherwise it will be copied into a `nsPipe`.
*/
static nsresult MaybeWrap(already_AddRefed<nsIInputStream> aOriginal,
nsIInputStream** aResult);
private:
SeekableStreamWrapper(nsICloneableInputStream* aOriginal,
nsIInputStream* aCurrent)
: mOriginal(aOriginal), mCurrent(aCurrent) {
MOZ_ASSERT(mOriginal->GetCloneable());
}
~SeekableStreamWrapper() = default;
template <typename T>
nsCOMPtr<T> StreamAs();
template <typename M>
void SerializeInternal(mozilla::ipc::InputStreamParams& aParams,
FileDescriptorArray& aFileDescriptors,
bool aDelayedStart, uint32_t aMaxSize,
uint32_t* aSizeUsed, M* aManager);
bool IsAsyncInputStream();
bool IsInputStreamLength();
bool IsAsyncInputStreamLength();
bool IsIPCSerializableInputStream();
bool IsBufferedInputStream();
// mOriginal is immutable after creation, and is not guarded by `mMutex`.
nsCOMPtr<nsICloneableInputStream> mOriginal;
// This mutex guards `mCurrent`.
Mutex mMutex{"SeekableStreamWrapper"};
nsCOMPtr<nsIInputStream> mCurrent;
};
} // namespace mozilla
#endif // mozilla_SeekableStreamWrapper_h