Bug 1100398 P5 Provide NS_CloneInputStream() factory method in nsStreamUtils.h. r=froydnj
authorBen Kelly <ben@wanderview.com>
Tue, 10 Feb 2015 23:55:43 -0500
changeset 228748 48906d15a28eafe9a84d7607f17bcd6661a540fb
parent 228747 ef97268b8d6a5b55bb3965fe4f96a3b5b995855a
child 228749 67741f46717f89b7605431be87b523fdf2fe1580
push id28271
push usercbook@mozilla.com
push dateThu, 12 Feb 2015 14:33:39 +0000
treeherdermozilla-central@81f979b17fbd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfroydnj
bugs1100398
milestone38.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 1100398 P5 Provide NS_CloneInputStream() factory method in nsStreamUtils.h. r=froydnj
xpcom/io/nsStreamUtils.cpp
xpcom/io/nsStreamUtils.h
xpcom/tests/gtest/TestCloneInputStream.cpp
xpcom/tests/gtest/moz.build
--- a/xpcom/io/nsStreamUtils.cpp
+++ b/xpcom/io/nsStreamUtils.cpp
@@ -12,16 +12,18 @@
 #include "nsIPipe.h"
 #include "nsIEventTarget.h"
 #include "nsIRunnable.h"
 #include "nsISafeOutputStream.h"
 #include "nsString.h"
 #include "nsIAsyncInputStream.h"
 #include "nsIAsyncOutputStream.h"
 #include "nsIBufferedStreams.h"
+#include "nsNetCID.h"
+#include "nsServiceManagerUtils.h"
 
 using namespace mozilla;
 
 //-----------------------------------------------------------------------------
 
 class nsInputStreamReadyEvent MOZ_FINAL
   : public nsIRunnable
   , public nsIInputStreamCallback
@@ -844,8 +846,59 @@ NS_FillArray(FallibleTArray<char>& aDest
   // NOTE: we rely on the fact that the new slots are NOT initialized by
   // SetLengthAndRetainStorage here, see nsTArrayElementTraits::Construct()
   // in nsTArray.h:
   aDest.SetLengthAndRetainStorage(aKeep + *aNewBytes);
 
   MOZ_ASSERT(aDest.Length() <= aDest.Capacity(), "buffer overflow");
   return rv;
 }
+
+nsresult
+NS_CloneInputStream(nsIInputStream* aSource, nsIInputStream** aCloneOut,
+                    nsIInputStream** aReplacementOut)
+{
+  // Attempt to perform the clone directly on the source stream
+  nsCOMPtr<nsICloneableInputStream> cloneable = do_QueryInterface(aSource);
+  if (cloneable && cloneable->GetCloneable()) {
+    if (aReplacementOut) {
+      *aReplacementOut = nullptr;
+    }
+    return cloneable->Clone(aCloneOut);
+  }
+
+  // If we failed the clone and the caller does not want to replace their
+  // original stream, then we are done.  Return error.
+  if (!aReplacementOut) {
+    return NS_ERROR_FAILURE;
+  }
+
+  // The caller has opted-in to the fallback clone support that replaces
+  // the original stream.  Copy the data to a pipe and return two cloned
+  // input streams.
+
+  nsCOMPtr<nsIInputStream> reader;
+  nsCOMPtr<nsIInputStream> readerClone;
+  nsCOMPtr<nsIOutputStream> writer;
+
+  nsresult rv = NS_NewPipe(getter_AddRefs(reader), getter_AddRefs(writer),
+                           0, 0,        // default segment size and max size
+                           true, true); // non-blocking
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  cloneable = do_QueryInterface(reader);
+  MOZ_ASSERT(cloneable && cloneable->GetCloneable());
+
+  rv = cloneable->Clone(getter_AddRefs(readerClone));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  nsCOMPtr<nsIEventTarget> target =
+    do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = NS_AsyncCopy(aSource, writer, target, NS_ASYNCCOPY_VIA_WRITESEGMENTS);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  readerClone.forget(aCloneOut);
+  reader.forget(aReplacementOut);
+
+  return NS_OK;
+}
--- a/xpcom/io/nsStreamUtils.h
+++ b/xpcom/io/nsStreamUtils.h
@@ -259,9 +259,31 @@ struct MOZ_STACK_CLASS nsWriteSegmentThu
  * @param aNewBytes (out) number of bytes read from aInput or zero if Read()
  *        failed
  * @return the result from aInput->Read(...)
  */
 extern NS_METHOD
 NS_FillArray(FallibleTArray<char>& aDest, nsIInputStream* aInput,
              uint32_t aKeep, uint32_t* aNewBytes);
 
+/**
+ * Clone the provided source stream in the most efficient way possible.  This
+ * first attempts to QI to nsICloneableInputStream to use Clone().  If that is
+ * not supported or its cloneable attribute is false, then a fallback clone is
+ * provided by copying the source to a pipe.  In this case the caller must
+ * replace the source stream with the resulting replacement stream.  The clone
+ * and the replacement stream are then cloneable using nsICloneableInputStream
+ * without duplicating memory.  This fallback clone using the pipe is only
+ * performed if a replacement stream parameter is also passed in.
+ * @param aSource         The input stream to clone.
+ * @param aCloneOut       Required out parameter to hold resulting clone.
+ * @param aReplacementOut Optional out parameter to hold stream to replace
+ *                        original source stream after clone.  If not
+ *                        provided then the fallback clone process is not
+ *                        supported and a non-cloneable source will result
+ *                        in failure.  Replacement streams are non-blocking.
+ * @return NS_OK on successful clone.  Error otherwise.
+ */
+extern nsresult
+NS_CloneInputStream(nsIInputStream* aSource, nsIInputStream** aCloneOut,
+                    nsIInputStream** aReplacementOut = nullptr);
+
 #endif // !nsStreamUtils_h__
new file mode 100644
--- /dev/null
+++ b/xpcom/tests/gtest/TestCloneInputStream.cpp
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "gtest/gtest.h"
+#include "Helpers.h"
+#include "mozilla/unused.h"
+#include "nsICloneableInputStream.h"
+#include "nsNetUtil.h"
+#include "nsStreamUtils.h"
+#include "nsStringStream.h"
+
+TEST(CloneInputStream, CloneableInput)
+{
+  nsTArray<char> inputData;
+  testing::CreateData(4 * 1024, inputData);
+  nsDependentCSubstring inputString(inputData.Elements(), inputData.Length());
+
+  nsCOMPtr<nsIInputStream> stream;
+  nsresult rv = NS_NewCStringInputStream(getter_AddRefs(stream), inputString);
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+  nsCOMPtr<nsIInputStream> clone;
+  rv = NS_CloneInputStream(stream, getter_AddRefs(clone));
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+  testing::ConsumeAndValidateStream(stream, inputString);
+  testing::ConsumeAndValidateStream(clone, inputString);
+}
+
+TEST(CloneInputStream, NonCloneableInput_NoFallback)
+{
+  nsTArray<char> inputData;
+  testing::CreateData(4 * 1024, inputData);
+  nsDependentCSubstring inputString(inputData.Elements(), inputData.Length());
+
+  nsCOMPtr<nsIInputStream> base;
+  nsresult rv = NS_NewCStringInputStream(getter_AddRefs(base), inputString);
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+  // Take advantage of nsBufferedInputStream being non-cloneable right
+  // now.  If this changes in the future, then we need a different stream
+  // type in this test.
+  nsCOMPtr<nsIInputStream> stream;
+  rv = NS_NewBufferedInputStream(getter_AddRefs(stream), base, 4096);
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+  nsCOMPtr<nsICloneableInputStream> cloneable = do_QueryInterface(stream);
+  ASSERT_TRUE(cloneable == nullptr);
+
+  nsCOMPtr<nsIInputStream> clone;
+  rv = NS_CloneInputStream(stream, getter_AddRefs(clone));
+  ASSERT_TRUE(NS_FAILED(rv));
+  ASSERT_TRUE(clone == nullptr);
+
+  testing::ConsumeAndValidateStream(stream, inputString);
+}
+
+TEST(CloneInputStream, NonCloneableInput_Fallback)
+{
+  nsTArray<char> inputData;
+  testing::CreateData(4 * 1024, inputData);
+  nsDependentCSubstring inputString(inputData.Elements(), inputData.Length());
+
+  nsCOMPtr<nsIInputStream> base;
+  nsresult rv = NS_NewCStringInputStream(getter_AddRefs(base), inputString);
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+  // Take advantage of nsBufferedInputStream being non-cloneable right
+  // now.  If this changes in the future, then we need a different stream
+  // type in this test.
+  nsCOMPtr<nsIInputStream> stream;
+  rv = NS_NewBufferedInputStream(getter_AddRefs(stream), base, 4096);
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+  nsCOMPtr<nsICloneableInputStream> cloneable = do_QueryInterface(stream);
+  ASSERT_TRUE(cloneable == nullptr);
+
+  nsCOMPtr<nsIInputStream> clone;
+  nsCOMPtr<nsIInputStream> replacement;
+  rv = NS_CloneInputStream(stream, getter_AddRefs(clone),
+                           getter_AddRefs(replacement));
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+  ASSERT_TRUE(clone != nullptr);
+  ASSERT_TRUE(replacement != nullptr);
+  ASSERT_TRUE(stream.get() != replacement.get());
+  ASSERT_TRUE(clone.get() != replacement.get());
+
+  stream = replacement.forget();
+
+  // The stream is being copied asynchronously on the STS event target.  Spin
+  // a yield loop here until the data is available.  Yes, this is a bit hacky,
+  // but AFAICT, gtest does not support async test completion.
+  uint64_t available;
+  do {
+    mozilla::unused << PR_Sleep(PR_INTERVAL_NO_WAIT);
+    rv = stream->Available(&available);
+    ASSERT_TRUE(NS_SUCCEEDED(rv));
+  } while(available < inputString.Length());
+
+  testing::ConsumeAndValidateStream(stream, inputString);
+  testing::ConsumeAndValidateStream(clone, inputString);
+}
--- a/xpcom/tests/gtest/moz.build
+++ b/xpcom/tests/gtest/moz.build
@@ -1,16 +1,17 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 UNIFIED_SOURCES += [
     'Helpers.cpp',
+    'TestCloneInputStream.cpp',
     'TestCRT.cpp',
     'TestEncoding.cpp',
     'TestExpirationTracker.cpp',
     'TestPipes.cpp',
     'TestPriorityQueue.cpp',
     'TestSnappyStreams.cpp',
     'TestStorageStream.cpp',
     'TestStrings.cpp',