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