1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "IPCStreamSource.h"
8 #include "BackgroundParent.h"  // for AssertIsOnBackgroundThread
9 #include "mozilla/dom/RemoteWorkerService.h"
10 #include "mozilla/webrender/WebRenderTypes.h"
11 #include "nsIAsyncInputStream.h"
12 #include "nsICancelableRunnable.h"
13 #include "nsIRunnable.h"
14 #include "nsISerialEventTarget.h"
15 #include "nsStreamUtils.h"
16 #include "nsThreadUtils.h"
17 
18 using mozilla::wr::ByteBuffer;
19 
20 namespace mozilla {
21 namespace ipc {
22 
23 class IPCStreamSource::Callback final : public nsIInputStreamCallback,
24                                         public nsIRunnable,
25                                         public nsICancelableRunnable {
26  public:
Callback(IPCStreamSource * aSource)27   explicit Callback(IPCStreamSource* aSource)
28       : mSource(aSource),
29         mOwningEventTarget(GetCurrentThreadSerialEventTarget()) {
30     MOZ_ASSERT(mSource);
31   }
32 
33   NS_IMETHOD
OnInputStreamReady(nsIAsyncInputStream * aStream)34   OnInputStreamReady(nsIAsyncInputStream* aStream) override {
35     // any thread
36     if (mOwningEventTarget->IsOnCurrentThread()) {
37       return Run();
38     }
39 
40     // If this fails, then it means the owning thread is a Worker that has
41     // been shutdown.  Its ok to lose the event in this case because the
42     // IPCStreamChild listens for this event through the WorkerRef.
43     nsresult rv =
44         mOwningEventTarget->Dispatch(this, nsIThread::DISPATCH_NORMAL);
45     if (NS_FAILED(rv)) {
46       NS_WARNING("Failed to dispatch stream readable event to owning thread");
47     }
48 
49     return NS_OK;
50   }
51 
52   NS_IMETHOD
Run()53   Run() override {
54     MOZ_ASSERT(mOwningEventTarget->IsOnCurrentThread());
55     if (mSource) {
56       mSource->OnStreamReady(this);
57     }
58     return NS_OK;
59   }
60 
Cancel()61   nsresult Cancel() override {
62     // Cancel() gets called when the Worker thread is being shutdown.  We have
63     // nothing to do here because IPCStreamChild handles this case via
64     // the WorkerRef.
65     return NS_OK;
66   }
67 
ClearSource()68   void ClearSource() {
69     MOZ_ASSERT(mOwningEventTarget->IsOnCurrentThread());
70     MOZ_ASSERT(mSource);
71     mSource = nullptr;
72   }
73 
74  private:
~Callback()75   ~Callback() {
76     // called on any thread
77 
78     // ClearSource() should be called before the Callback is destroyed
79     MOZ_ASSERT(!mSource);
80   }
81 
82   // This is a raw pointer because the source keeps alive the callback and,
83   // before beeing destroyed, it nullifies this pointer (this happens when
84   // ActorDestroyed() is called).
85   IPCStreamSource* mSource;
86 
87   nsCOMPtr<nsISerialEventTarget> mOwningEventTarget;
88 
89   NS_DECL_THREADSAFE_ISUPPORTS
90 };
91 
92 NS_IMPL_ISUPPORTS(IPCStreamSource::Callback, nsIInputStreamCallback,
93                   nsIRunnable, nsICancelableRunnable);
94 
IPCStreamSource(nsIAsyncInputStream * aInputStream)95 IPCStreamSource::IPCStreamSource(nsIAsyncInputStream* aInputStream)
96     : mStream(aInputStream), mState(ePending) {
97   MOZ_ASSERT(aInputStream);
98 }
99 
~IPCStreamSource()100 IPCStreamSource::~IPCStreamSource() {
101   NS_ASSERT_OWNINGTHREAD(IPCStreamSource);
102   MOZ_ASSERT(mState == eClosed);
103   MOZ_ASSERT(!mCallback);
104   MOZ_ASSERT(!mWorkerRef);
105 }
106 
Initialize()107 bool IPCStreamSource::Initialize() {
108   bool nonBlocking = false;
109   MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mStream->IsNonBlocking(&nonBlocking)));
110   // IPCStreamChild reads in the current thread, so it is only supported on
111   // non-blocking, async channels
112   if (!nonBlocking) {
113     return false;
114   }
115 
116   // A source can be used on any thread, but we only support IPCStream on
117   // main thread, Workers, Worker Launcher, and PBackground thread right now.
118   // This is due to the requirement  that the thread be guaranteed to live long
119   // enough to receive messages. We can enforce this guarantee with a
120   // StrongWorkerRef on worker threads, but not other threads. Main-thread,
121   // PBackground, and Worker Launcher threads do not need anything special in
122   // order to be kept alive.
123   if (!NS_IsMainThread()) {
124     if (const auto workerPrivate = dom::GetCurrentThreadWorkerPrivate()) {
125       RefPtr<dom::StrongWorkerRef> workerRef =
126           dom::StrongWorkerRef::CreateForcibly(workerPrivate,
127                                                "IPCStreamSource");
128       if (NS_WARN_IF(!workerRef)) {
129         return false;
130       }
131 
132       mWorkerRef = std::move(workerRef);
133     } else {
134       MOZ_DIAGNOSTIC_ASSERT(
135           IsOnBackgroundThread() ||
136           dom::RemoteWorkerService::Thread()->IsOnCurrentThread());
137     }
138   }
139 
140   return true;
141 }
142 
ActorConstructed()143 void IPCStreamSource::ActorConstructed() {
144   MOZ_ASSERT(mState == ePending);
145   mState = eActorConstructed;
146 }
147 
ActorDestroyed()148 void IPCStreamSource::ActorDestroyed() {
149   NS_ASSERT_OWNINGTHREAD(IPCStreamSource);
150 
151   mState = eClosed;
152 
153   if (mCallback) {
154     mCallback->ClearSource();
155     mCallback = nullptr;
156   }
157 
158   mWorkerRef = nullptr;
159 }
160 
Start()161 void IPCStreamSource::Start() {
162   NS_ASSERT_OWNINGTHREAD(IPCStreamSource);
163   DoRead();
164 }
165 
StartDestroy()166 void IPCStreamSource::StartDestroy() {
167   NS_ASSERT_OWNINGTHREAD(IPCStreamSource);
168   OnEnd(NS_ERROR_ABORT);
169 }
170 
DoRead()171 void IPCStreamSource::DoRead() {
172   NS_ASSERT_OWNINGTHREAD(IPCStreamSource);
173   MOZ_ASSERT(mState == eActorConstructed);
174   MOZ_ASSERT(!mCallback);
175 
176   // The input stream (likely a pipe) probably uses a segment size of
177   // 4kb.  If there is data already buffered it would be nice to aggregate
178   // multiple segments into a single IPC call.  Conversely, don't send too
179   // too large of a buffer in a single call to avoid spiking memory.
180   static const uint64_t kMaxBytesPerMessage = 32 * 1024;
181   static_assert(kMaxBytesPerMessage <= static_cast<uint64_t>(UINT32_MAX),
182                 "kMaxBytesPerMessage must cleanly cast to uint32_t");
183 
184   char buffer[kMaxBytesPerMessage];
185 
186   while (true) {
187     // It should not be possible to transition to closed state without
188     // this loop terminating via a return.
189     MOZ_ASSERT(mState == eActorConstructed);
190 
191     // See if the stream is closed by checking the return of Available.
192     uint64_t dummy;
193     nsresult rv = mStream->Available(&dummy);
194     if (NS_FAILED(rv)) {
195       OnEnd(rv);
196       return;
197     }
198 
199     uint32_t bytesRead = 0;
200     rv = mStream->Read(buffer, kMaxBytesPerMessage, &bytesRead);
201 
202     if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
203       MOZ_ASSERT(bytesRead == 0);
204       Wait();
205       return;
206     }
207 
208     if (NS_FAILED(rv)) {
209       MOZ_ASSERT(bytesRead == 0);
210       OnEnd(rv);
211       return;
212     }
213 
214     // Zero-byte read indicates end-of-stream.
215     if (bytesRead == 0) {
216       OnEnd(NS_BASE_STREAM_CLOSED);
217       return;
218     }
219 
220     // We read some data from the stream, send it across.
221     SendData(ByteBuffer(bytesRead, reinterpret_cast<uint8_t*>(buffer)));
222   }
223 }
224 
Wait()225 void IPCStreamSource::Wait() {
226   NS_ASSERT_OWNINGTHREAD(IPCStreamSource);
227   MOZ_ASSERT(mState == eActorConstructed);
228   MOZ_ASSERT(!mCallback);
229 
230   // Set mCallback immediately instead of waiting for success.  Its possible
231   // AsyncWait() will callback synchronously.
232   mCallback = new Callback(this);
233   nsresult rv = mStream->AsyncWait(mCallback, 0, 0, nullptr);
234   if (NS_FAILED(rv)) {
235     OnEnd(rv);
236     return;
237   }
238 }
239 
OnStreamReady(Callback * aCallback)240 void IPCStreamSource::OnStreamReady(Callback* aCallback) {
241   NS_ASSERT_OWNINGTHREAD(IPCStreamSource);
242   MOZ_ASSERT(mCallback);
243   MOZ_ASSERT(aCallback == mCallback);
244   mCallback->ClearSource();
245   mCallback = nullptr;
246 
247   // Possibly closed if this callback is (indirectly) called by
248   // IPCStreamSourceParent::RecvRequestClose().
249   if (mState == eClosed) {
250     return;
251   }
252 
253   DoRead();
254 }
255 
OnEnd(nsresult aRv)256 void IPCStreamSource::OnEnd(nsresult aRv) {
257   NS_ASSERT_OWNINGTHREAD(IPCStreamSource);
258   MOZ_ASSERT(aRv != NS_BASE_STREAM_WOULD_BLOCK);
259 
260   if (mState == eClosed) {
261     return;
262   }
263 
264   mState = eClosed;
265 
266   mStream->CloseWithStatus(aRv);
267 
268   if (aRv == NS_BASE_STREAM_CLOSED) {
269     aRv = NS_OK;
270   }
271 
272   // This will trigger an ActorDestroy() from the other side
273   Close(aRv);
274 }
275 
276 }  // namespace ipc
277 }  // namespace mozilla
278