Bug 1397645 - Optimize IPCBlobInputStream slicing with the introduction of nsICloneableInputStreamWithRange, r=asuth
authorAndrea Marchesini <amarchesini@mozilla.com>
Mon, 11 Sep 2017 17:29:15 +0200
changeset 429573 d285843321b58f1b76afaaad3842cc6e42385df6
parent 429572 435bc55b9c14052b4206dd5734de0049fe70eb53
child 429574 47982714b7b6a72ae252b87b4092d9e526aaa9e7
push id7761
push userjlund@mozilla.com
push dateFri, 15 Sep 2017 00:19:52 +0000
treeherdermozilla-beta@c38455951db4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersasuth
bugs1397645
milestone57.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1397645 - Optimize IPCBlobInputStream slicing with the introduction of nsICloneableInputStreamWithRange, r=asuth
dom/file/StreamBlobImpl.cpp
dom/file/ipc/IPCBlobInputStream.cpp
dom/file/ipc/IPCBlobInputStream.h
dom/file/ipc/IPCBlobInputStreamChild.cpp
dom/file/ipc/IPCBlobInputStreamChild.h
xpcom/io/nsICloneableInputStream.idl
--- a/dom/file/StreamBlobImpl.cpp
+++ b/dom/file/StreamBlobImpl.cpp
@@ -1,16 +1,17 @@
 /* -*- 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 "StreamBlobImpl.h"
 #include "nsStringStream.h"
+#include "nsICloneableInputStream.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_ISUPPORTS_INHERITED(StreamBlobImpl, BlobImpl, nsIMemoryReporter)
 
 /* static */ already_AddRefed<StreamBlobImpl>
 StreamBlobImpl::Create(nsIInputStream* aInputStream,
@@ -100,18 +101,32 @@ already_AddRefed<BlobImpl>
 StreamBlobImpl::CreateSlice(uint64_t aStart, uint64_t aLength,
                             const nsAString& aContentType, ErrorResult& aRv)
 {
   if (!aLength) {
     RefPtr<BlobImpl> impl = new EmptyBlobImpl(aContentType);
     return impl.forget();
   }
 
-  RefPtr<BlobImpl> impl =
-    new StreamBlobImpl(this, aContentType, aStart, aLength);
+  nsCOMPtr<nsICloneableInputStreamWithRange> stream =
+    do_QueryInterface(mInputStream);
+  if (stream) {
+    nsCOMPtr<nsIInputStream> clonedStream;
+    aRv = stream->CloneWithRange(aStart, aLength, getter_AddRefs(clonedStream));
+    if (NS_WARN_IF(aRv.Failed())) {
+      return nullptr;
+    }
+
+    RefPtr<BlobImpl> impl =
+      new StreamBlobImpl(clonedStream, aContentType, aLength);
+    return impl.forget();
+  }
+
+  RefPtr<BlobImpl> impl;
+    impl = new StreamBlobImpl(this, aContentType, aStart, aLength);
   return impl.forget();
 }
 
 void
 StreamBlobImpl::MaybeRegisterMemoryReporter()
 {
   // We report only stringInputStream.
   nsCOMPtr<nsIStringInputStream> stringInputStream =
--- a/dom/file/ipc/IPCBlobInputStream.cpp
+++ b/dom/file/ipc/IPCBlobInputStream.cpp
@@ -7,16 +7,18 @@
 #include "IPCBlobInputStream.h"
 #include "IPCBlobInputStreamChild.h"
 #include "IPCBlobInputStreamStorage.h"
 #include "mozilla/ipc/InputStreamParams.h"
 #include "nsIAsyncInputStream.h"
 #include "nsIStreamTransportService.h"
 #include "nsITransport.h"
 #include "nsNetCID.h"
+#include "nsStringStream.h"
+#include "SlicedInputStream.h"
 
 namespace mozilla {
 namespace dom {
 
 namespace {
 
 static NS_DEFINE_CID(kStreamTransportServiceCID, NS_STREAMTRANSPORTSERVICE_CID);
 
@@ -111,28 +113,33 @@ private:
 NS_IMPL_ADDREF(IPCBlobInputStream);
 NS_IMPL_RELEASE(IPCBlobInputStream);
 
 NS_INTERFACE_MAP_BEGIN(IPCBlobInputStream)
   NS_INTERFACE_MAP_ENTRY(nsIInputStream)
   NS_INTERFACE_MAP_ENTRY(nsIAsyncInputStream)
   NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback)
   NS_INTERFACE_MAP_ENTRY(nsICloneableInputStream)
+  NS_INTERFACE_MAP_ENTRY(nsICloneableInputStreamWithRange)
   NS_INTERFACE_MAP_ENTRY(nsIIPCSerializableInputStream)
   NS_INTERFACE_MAP_ENTRY(nsIFileMetadata)
   NS_INTERFACE_MAP_ENTRY(nsIAsyncFileMetadata)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream)
 NS_INTERFACE_MAP_END
 
 IPCBlobInputStream::IPCBlobInputStream(IPCBlobInputStreamChild* aActor)
   : mActor(aActor)
   , mState(eInit)
+  , mStart(0)
+  , mLength(0)
 {
   MOZ_ASSERT(aActor);
 
+  mLength = aActor->Size();
+
   if (XRE_IsParentProcess()) {
     nsCOMPtr<nsIInputStream> stream;
     IPCBlobInputStreamStorage::Get()->GetStream(mActor->ID(),
                                                 getter_AddRefs(stream));
     if (stream) {
       mState = eRunning;
       mRemoteStream = stream;
     }
@@ -146,28 +153,28 @@ IPCBlobInputStream::~IPCBlobInputStream(
 
 // nsIInputStream interface
 
 NS_IMETHODIMP
 IPCBlobInputStream::Available(uint64_t* aLength)
 {
   // We don't have a remoteStream yet. Let's return the full known size.
   if (mState == eInit || mState == ePending) {
-    *aLength = mActor->Size();
+    *aLength = mLength;
     return NS_OK;
   }
 
   if (mState == eRunning) {
     MOZ_ASSERT(mRemoteStream || mAsyncRemoteStream);
 
     // This will go away eventually: an async input stream can return 0 in
     // Available(), but this is not currently fully supported in the rest of
     // gecko.
     if (!mAsyncRemoteStream) {
-      *aLength = mActor->Size();
+      *aLength = mLength;
       return NS_OK;
     }
 
     nsresult rv = EnsureAsyncRemoteStream();
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
@@ -276,21 +283,62 @@ NS_IMETHODIMP
 IPCBlobInputStream::Clone(nsIInputStream** aResult)
 {
   if (mState == eClosed) {
     return NS_BASE_STREAM_CLOSED;
   }
 
   MOZ_ASSERT(mActor);
 
-  nsCOMPtr<nsIInputStream> stream = mActor->CreateStream();
+  RefPtr<IPCBlobInputStream> stream = mActor->CreateStream();
   if (!stream) {
     return NS_ERROR_FAILURE;
   }
 
+  stream->mStart = mStart;
+  stream->mLength = mLength;
+
+  stream.forget(aResult);
+  return NS_OK;
+}
+
+// nsICloneableInputStreamWithRange interface
+
+NS_IMETHODIMP
+IPCBlobInputStream::CloneWithRange(uint64_t aStart, uint64_t aLength,
+                                   nsIInputStream** aResult)
+{
+  if (mState == eClosed) {
+    return NS_BASE_STREAM_CLOSED;
+  }
+
+  // Too short or out of range.
+  if (aLength == 0 || aStart >= mLength) {
+    return NS_NewCStringInputStream(aResult, EmptyCString());
+  }
+
+  MOZ_ASSERT(mActor);
+
+  RefPtr<IPCBlobInputStream> stream = mActor->CreateStream();
+  if (!stream) {
+    return NS_ERROR_FAILURE;
+  }
+
+  CheckedInt<uint64_t> streamSize = mLength;
+  streamSize -= aStart;
+  if (!streamSize.isValid()) {
+    return NS_ERROR_FAILURE;
+  }
+
+  if (aLength > streamSize.value()) {
+    aLength = streamSize.value();
+  }
+
+  stream->SetRange(aStart + mStart, aLength);
+
   stream.forget(aResult);
   return NS_OK;
 }
 
 // nsIAsyncInputStream interface
 
 NS_IMETHODIMP
 IPCBlobInputStream::CloseWithStatus(nsresult aStatus)
@@ -352,16 +400,21 @@ IPCBlobInputStream::StreamReady(nsIInput
   // If aInputStream is null, it means that the serialization went wrong or the
   // stream is not available anymore. We keep the state as pending just to block
   // any additional operation.
 
   if (!aInputStream) {
     return;
   }
 
+  // Now it's the right time to apply a slice if needed.
+  if (mStart > 0 || mLength < mActor->Size()) {
+    aInputStream = new SlicedInputStream(aInputStream, mStart, mLength);
+  }
+
   mRemoteStream = aInputStream;
 
   MOZ_ASSERT(mState == ePending);
   mState = eRunning;
 
   nsCOMPtr<nsIFileMetadataCallback> fileMetadataCallback;
   fileMetadataCallback.swap(mFileMetadataCallback);
 
@@ -410,16 +463,24 @@ IPCBlobInputStream::MaybeExecuteInputStr
     return rv;
   }
 
   MOZ_ASSERT(mAsyncRemoteStream);
 
   return mAsyncRemoteStream->AsyncWait(this, 0, 0, aCallbackEventTarget);
 }
 
+void
+IPCBlobInputStream::SetRange(uint64_t aStart, uint64_t aLength)
+{
+  MOZ_ASSERT(mActor->Size() >= aStart + aLength);
+  mStart = aStart;
+  mLength = aLength;
+}
+
 // nsIInputStreamCallback
 
 NS_IMETHODIMP
 IPCBlobInputStream::OnInputStreamReady(nsIAsyncInputStream* aStream)
 {
   // We have been closed in the meantime.
   if (mState == eClosed) {
     return NS_OK;
@@ -595,13 +656,12 @@ IPCBlobInputStream::EnsureAsyncRemoteStr
     asyncStream = do_QueryInterface(wrapper);
   }
 
   MOZ_ASSERT(asyncStream);
   mAsyncRemoteStream = asyncStream;
   mRemoteStream = nullptr;
 
   return NS_OK;
-
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/file/ipc/IPCBlobInputStream.h
+++ b/dom/file/ipc/IPCBlobInputStream.h
@@ -15,26 +15,27 @@
 
 namespace mozilla {
 namespace dom {
 
 class IPCBlobInputStreamChild;
 
 class IPCBlobInputStream final : public nsIAsyncInputStream
                                , public nsIInputStreamCallback
-                               , public nsICloneableInputStream
+                               , public nsICloneableInputStreamWithRange
                                , public nsIIPCSerializableInputStream
                                , public nsIAsyncFileMetadata
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIINPUTSTREAM
   NS_DECL_NSIASYNCINPUTSTREAM
   NS_DECL_NSIINPUTSTREAMCALLBACK
   NS_DECL_NSICLONEABLEINPUTSTREAM
+  NS_DECL_NSICLONEABLEINPUTSTREAMWITHRANGE
   NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
   NS_DECL_NSIFILEMETADATA
   NS_DECL_NSIASYNCFILEMETADATA
 
   explicit IPCBlobInputStream(IPCBlobInputStreamChild* aActor);
 
   void
   StreamReady(nsIInputStream* aInputStream);
@@ -44,16 +45,19 @@ private:
 
   nsresult
   MaybeExecuteInputStreamCallback(nsIInputStreamCallback* aCallback,
                                   nsIEventTarget* aEventTarget);
 
   nsresult
   EnsureAsyncRemoteStream();
 
+  void
+  SetRange(uint64_t aStart, uint64_t aLength);
+
   RefPtr<IPCBlobInputStreamChild> mActor;
 
   // This is the list of possible states.
   enum {
     // The initial state. Only ::Available() can be used without receiving an
     // error. The available size is known by the actor.
     eInit,
 
@@ -67,16 +71,19 @@ private:
     eRunning,
 
     // If Close() or CloseWithStatus() is called, we move to this state.
     // mRemoveStream is released and any method will return
     // NS_BASE_STREAM_CLOSED.
     eClosed,
   } mState;
 
+  uint64_t mStart;
+  uint64_t mLength;
+
   nsCOMPtr<nsIInputStream> mRemoteStream;
   nsCOMPtr<nsIAsyncInputStream> mAsyncRemoteStream;
 
   // These 2 values are set only if mState is ePending.
   nsCOMPtr<nsIInputStreamCallback> mInputStreamCallback;
   nsCOMPtr<nsIEventTarget> mInputStreamCallbackEventTarget;
 
   // These 2 values are set only if mState is ePending.
--- a/dom/file/ipc/IPCBlobInputStreamChild.cpp
+++ b/dom/file/ipc/IPCBlobInputStreamChild.cpp
@@ -198,17 +198,17 @@ IPCBlobInputStreamChild::ActorDestroy(IP
 
 IPCBlobInputStreamChild::ActorState
 IPCBlobInputStreamChild::State()
 {
   MutexAutoLock lock(mMutex);
   return mState;
 }
 
-already_AddRefed<nsIInputStream>
+already_AddRefed<IPCBlobInputStream>
 IPCBlobInputStreamChild::CreateStream()
 {
   bool shouldMigrate = false;
 
   RefPtr<IPCBlobInputStream> stream = new IPCBlobInputStream(this);
 
   {
     MutexAutoLock lock(mMutex);
--- a/dom/file/ipc/IPCBlobInputStreamChild.h
+++ b/dom/file/ipc/IPCBlobInputStreamChild.h
@@ -48,17 +48,17 @@ public:
   IPCBlobInputStreamChild(const nsID& aID, uint64_t aSize);
 
   void
   ActorDestroy(IProtocol::ActorDestroyReason aReason) override;
 
   ActorState
   State();
 
-  already_AddRefed<nsIInputStream>
+  already_AddRefed<IPCBlobInputStream>
   CreateStream();
 
   void
   ForgetStream(IPCBlobInputStream* aStream);
 
   const nsID&
   ID() const
   {
--- a/xpcom/io/nsICloneableInputStream.idl
+++ b/xpcom/io/nsICloneableInputStream.idl
@@ -15,8 +15,17 @@ interface nsICloneableInputStream : nsIS
   // Produce a copy of the current stream in the most efficient way possible.
   // In this case "copy" means that both the original and cloned streams
   // should produce the same bytes for all future reads.  Bytes that have
   // already been consumed from the original stream are not copied to the
   // clone.  Operations on the two streams should be completely independent
   // after the clone() occurs.
   nsIInputStream clone();
 };
+
+// This interface implements cloneWithRange() because for some streams
+// (IPCBlobInputStream only, so far) are more efficient to produce a sub
+// stream with range than doing clone + SlicedInputStream().
+[scriptable, builtinclass, uuid(ece853c3-aded-4cef-8f51-0d1493d60bd5)]
+interface nsICloneableInputStreamWithRange : nsICloneableInputStream
+{
+  nsIInputStream cloneWithRange(in uint64_t start, in uint64_t length);
+};