1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 : * vim: sw=4 ts=4 et :
3 : * ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is Mozilla Plugin App.
17 : *
18 : * The Initial Developer of the Original Code is
19 : * Chris Jones <jones.chris.g@gmail.com>
20 : * Portions created by the Initial Developer are Copyright (C) 2009
21 : * the Initial Developer. All Rights Reserved.
22 : *
23 : * Contributor(s):
24 : *
25 : * Alternatively, the contents of this file may be used under the terms of
26 : * either the GNU General Public License Version 2 or later (the "GPL"), or
27 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 : * in which case the provisions of the GPL or the LGPL are applicable instead
29 : * of those above. If you wish to allow use of your version of this file only
30 : * under the terms of either the GPL or the LGPL, and not to allow others to
31 : * use your version of this file under the terms of the MPL, indicate your
32 : * decision by deleting the provisions above and replace them with the notice
33 : * and other provisions required by the GPL or the LGPL. If you do not delete
34 : * the provisions above, a recipient may use your version of this file under
35 : * the terms of any one of the MPL, the GPL or the LGPL.
36 : *
37 : * ***** END LICENSE BLOCK ***** */
38 :
39 : #ifndef ipc_glue_RPCChannel_h
40 : #define ipc_glue_RPCChannel_h 1
41 :
42 : #include <stdio.h>
43 :
44 : // FIXME/cjones probably shouldn't depend on STL
45 : #include <queue>
46 : #include <stack>
47 : #include <vector>
48 :
49 : #include "base/basictypes.h"
50 :
51 : #include "nsAtomicRefcnt.h"
52 :
53 : #include "mozilla/ipc/SyncChannel.h"
54 : #include "nsAutoPtr.h"
55 :
56 : namespace mozilla {
57 : namespace ipc {
58 : //-----------------------------------------------------------------------------
59 :
60 : class RPCChannel : public SyncChannel
61 : {
62 : friend class CxxStackFrame;
63 :
64 : public:
65 : // What happens if RPC calls race?
66 : enum RacyRPCPolicy {
67 : RRPError,
68 : RRPChildWins,
69 : RRPParentWins
70 : };
71 :
72 : class /*NS_INTERFACE_CLASS*/ RPCListener :
73 : public SyncChannel::SyncListener
74 2 : {
75 : public:
76 2 : virtual ~RPCListener() { }
77 :
78 : virtual void OnChannelClose() = 0;
79 : virtual void OnChannelError() = 0;
80 : virtual Result OnMessageReceived(const Message& aMessage) = 0;
81 : virtual void OnProcessingError(Result aError) = 0;
82 : virtual bool OnReplyTimeout() = 0;
83 : virtual Result OnMessageReceived(const Message& aMessage,
84 : Message*& aReply) = 0;
85 : virtual Result OnCallReceived(const Message& aMessage,
86 : Message*& aReply) = 0;
87 0 : virtual void OnChannelConnected(int32 peer_pid) {};
88 :
89 0 : virtual void OnEnteredCxxStack()
90 : {
91 0 : NS_RUNTIMEABORT("default impl shouldn't be invoked");
92 0 : }
93 :
94 0 : virtual void OnExitedCxxStack()
95 : {
96 0 : NS_RUNTIMEABORT("default impl shouldn't be invoked");
97 0 : }
98 :
99 0 : virtual void OnEnteredCall()
100 : {
101 0 : NS_RUNTIMEABORT("default impl shouldn't be invoked");
102 0 : }
103 :
104 0 : virtual void OnExitedCall()
105 : {
106 0 : NS_RUNTIMEABORT("default impl shouldn't be invoked");
107 0 : }
108 :
109 0 : virtual RacyRPCPolicy MediateRPCRace(const Message& parent,
110 : const Message& child)
111 : {
112 0 : return RRPChildWins;
113 : }
114 0 : virtual void ProcessRemoteNativeEventsInRPCCall() {};
115 : };
116 :
117 : RPCChannel(RPCListener* aListener);
118 :
119 : virtual ~RPCChannel();
120 :
121 : NS_OVERRIDE
122 : void Clear();
123 :
124 : // Make an RPC to the other side of the channel
125 : bool Call(Message* msg, Message* reply);
126 :
127 : // RPCChannel overrides these so that the async and sync messages
128 : // can be counted against mStackFrames
129 : NS_OVERRIDE
130 : virtual bool Send(Message* msg);
131 : NS_OVERRIDE
132 : virtual bool Send(Message* msg, Message* reply);
133 :
134 : // Asynchronously, send the child a message that puts it in such a
135 : // state that it can't send messages to the parent unless the
136 : // parent sends a message to it first. The child stays in this
137 : // state until the parent calls |UnblockChild()|.
138 : //
139 : // It is an error to
140 : // - call this on the child side of the channel.
141 : // - nest |BlockChild()| calls
142 : // - call this when the child is already blocked on a sync or RPC
143 : // in-/out- message/call
144 : //
145 : // Return true iff successful.
146 : bool BlockChild();
147 :
148 : // Asynchronously undo |BlockChild()|.
149 : //
150 : // It is an error to
151 : // - call this on the child side of the channel
152 : // - call this without a matching |BlockChild()|
153 : //
154 : // Return true iff successful.
155 : bool UnblockChild();
156 :
157 : // Return true iff this has code on the C++ stack.
158 0 : bool IsOnCxxStack() const {
159 0 : return !mCxxStackFrames.empty();
160 : }
161 :
162 : NS_OVERRIDE
163 : virtual bool OnSpecialMessage(uint16 id, const Message& msg);
164 :
165 :
166 : /**
167 : * If there is a pending RPC message, process all pending messages.
168 : *
169 : * @note This method is used on Windows when we detect that an outbound
170 : * OLE RPC call is being made to unblock the parent.
171 : */
172 : void FlushPendingRPCQueue();
173 :
174 : #ifdef OS_WIN
175 : void ProcessNativeEventsInRPCCall();
176 : static void NotifyGeckoEventDispatch();
177 :
178 : protected:
179 : bool WaitForNotify();
180 : void SpinInternalEventLoop();
181 : #endif
182 :
183 : protected:
184 : NS_OVERRIDE virtual void OnMessageReceivedFromLink(const Message& msg);
185 : NS_OVERRIDE virtual void OnChannelErrorFromLink();
186 :
187 : private:
188 : // Called on worker thread only
189 :
190 0 : RPCListener* Listener() const {
191 0 : return static_cast<RPCListener*>(mListener);
192 : }
193 :
194 : NS_OVERRIDE
195 0 : virtual bool ShouldDeferNotifyMaybeError() const {
196 0 : return IsOnCxxStack();
197 : }
198 :
199 : bool EventOccurred() const;
200 :
201 : void MaybeUndeferIncall();
202 : void EnqueuePendingMessages();
203 :
204 : /**
205 : * Process one deferred or pending message.
206 : * @return true if a message was processed
207 : */
208 : bool OnMaybeDequeueOne();
209 :
210 : /**
211 : * The "remote view of stack depth" can be different than the
212 : * actual stack depth when there are out-of-turn replies. When we
213 : * receive one, our actual RPC stack depth doesn't decrease, but
214 : * the other side (that sent the reply) thinks it has. So, the
215 : * "view" returned here is |stackDepth| minus the number of
216 : * out-of-turn replies.
217 : *
218 : * Only called from the worker thread.
219 : */
220 : size_t RemoteViewOfStackDepth(size_t stackDepth) const;
221 :
222 : void Incall(const Message& call, size_t stackDepth);
223 : void DispatchIncall(const Message& call);
224 :
225 : void BlockOnParent();
226 : void UnblockFromParent();
227 :
228 : // This helper class managed RPCChannel.mCxxStackDepth on behalf
229 : // of RPCChannel. When the stack depth is incremented from zero
230 : // to non-zero, it invokes an RPCChannel callback, and similarly
231 : // for when the depth goes from non-zero to zero;
232 0 : void EnteredCxxStack()
233 : {
234 0 : Listener()->OnEnteredCxxStack();
235 0 : }
236 :
237 : void ExitedCxxStack();
238 :
239 0 : void EnteredCall()
240 : {
241 0 : Listener()->OnEnteredCall();
242 0 : }
243 :
244 0 : void ExitedCall()
245 : {
246 0 : Listener()->OnExitedCall();
247 0 : }
248 :
249 : enum Direction { IN_MESSAGE, OUT_MESSAGE };
250 0 : struct RPCFrame {
251 0 : RPCFrame(Direction direction, const Message* msg) :
252 0 : mDirection(direction), mMsg(msg)
253 0 : { }
254 :
255 0 : bool IsRPCIncall() const
256 : {
257 0 : return mMsg->is_rpc() && IN_MESSAGE == mDirection;
258 : }
259 :
260 0 : bool IsRPCOutcall() const
261 : {
262 0 : return mMsg->is_rpc() && OUT_MESSAGE == mDirection;
263 : }
264 :
265 0 : void Describe(int32* id, const char** dir, const char** sems,
266 : const char** name) const
267 : {
268 0 : *id = mMsg->routing_id();
269 0 : *dir = (IN_MESSAGE == mDirection) ? "in" : "out";
270 0 : *sems = mMsg->is_rpc() ? "rpc" : mMsg->is_sync() ? "sync" : "async";
271 0 : *name = mMsg->name();
272 0 : }
273 :
274 : Direction mDirection;
275 : const Message* mMsg;
276 : };
277 :
278 : class NS_STACK_CLASS CxxStackFrame
279 : {
280 : public:
281 :
282 0 : CxxStackFrame(RPCChannel& that, Direction direction,
283 0 : const Message* msg) : mThat(that) {
284 0 : mThat.AssertWorkerThread();
285 :
286 0 : if (mThat.mCxxStackFrames.empty())
287 0 : mThat.EnteredCxxStack();
288 :
289 0 : mThat.mCxxStackFrames.push_back(RPCFrame(direction, msg));
290 0 : const RPCFrame& frame = mThat.mCxxStackFrames.back();
291 :
292 0 : if (frame.IsRPCIncall())
293 0 : mThat.EnteredCall();
294 :
295 0 : mThat.mSawRPCOutMsg |= frame.IsRPCOutcall();
296 0 : }
297 :
298 0 : ~CxxStackFrame() {
299 0 : bool exitingCall = mThat.mCxxStackFrames.back().IsRPCIncall();
300 0 : mThat.mCxxStackFrames.pop_back();
301 0 : bool exitingStack = mThat.mCxxStackFrames.empty();
302 :
303 : // mListener could have gone away if Close() was called while
304 : // RPCChannel code was still on the stack
305 0 : if (!mThat.mListener)
306 0 : return;
307 :
308 0 : mThat.AssertWorkerThread();
309 0 : if (exitingCall)
310 0 : mThat.ExitedCall();
311 :
312 0 : if (exitingStack)
313 0 : mThat.ExitedCxxStack();
314 0 : }
315 : private:
316 : RPCChannel& mThat;
317 :
318 : // disable harmful methods
319 : CxxStackFrame();
320 : CxxStackFrame(const CxxStackFrame&);
321 : CxxStackFrame& operator=(const CxxStackFrame&);
322 : };
323 :
324 : // Called from both threads
325 0 : size_t StackDepth() const {
326 0 : mMonitor->AssertCurrentThreadOwns();
327 0 : return mStack.size();
328 : }
329 :
330 : void DebugAbort(const char* file, int line, const char* cond,
331 : const char* why,
332 : const char* type="rpc", bool reply=false) const;
333 :
334 : // This method is only safe to call on the worker thread, or in a
335 : // debugger with all threads paused. |outfile| defaults to stdout.
336 : void DumpRPCStack(FILE* outfile=NULL, const char* const pfx="") const;
337 :
338 : //
339 : // Queue of all incoming messages, except for replies to sync
340 : // messages, which are delivered directly to the SyncChannel
341 : // through its mRecvd member.
342 : //
343 : // If both this side and the other side are functioning correctly,
344 : // the queue can only be in certain configurations. Let
345 : //
346 : // |A<| be an async in-message,
347 : // |S<| be a sync in-message,
348 : // |C<| be an RPC in-call,
349 : // |R<| be an RPC reply.
350 : //
351 : // The queue can only match this configuration
352 : //
353 : // A<* (S< | C< | R< (?{mStack.size() == 1} A<* (S< | C<)))
354 : //
355 : // The other side can send as many async messages |A<*| as it
356 : // wants before sending us a blocking message.
357 : //
358 : // The first case is |S<|, a sync in-msg. The other side must be
359 : // blocked, and thus can't send us any more messages until we
360 : // process the sync in-msg.
361 : //
362 : // The second case is |C<|, an RPC in-call; the other side must be
363 : // blocked. (There's a subtlety here: this in-call might have
364 : // raced with an out-call, but we detect that with the mechanism
365 : // below, |mRemoteStackDepth|, and races don't matter to the
366 : // queue.)
367 : //
368 : // Final case, the other side replied to our most recent out-call
369 : // |R<|. If that was the *only* out-call on our stack,
370 : // |?{mStack.size() == 1}|, then other side "finished with us,"
371 : // and went back to its own business. That business might have
372 : // included sending any number of async message |A<*| until
373 : // sending a blocking message |(S< | C<)|. If we had more than
374 : // one RPC call on our stack, the other side *better* not have
375 : // sent us another blocking message, because it's blocked on a
376 : // reply from us.
377 : //
378 : typedef std::queue<Message> MessageQueue;
379 : MessageQueue mPending;
380 :
381 : //
382 : // Stack of all the RPC out-calls on which this RPCChannel is
383 : // awaiting a response.
384 : //
385 : std::stack<Message> mStack;
386 :
387 : //
388 : // Map of replies received "out of turn", because of RPC
389 : // in-calls racing with replies to outstanding in-calls. See
390 : // https://bugzilla.mozilla.org/show_bug.cgi?id=521929.
391 : //
392 : typedef std::map<size_t, Message> MessageMap;
393 : MessageMap mOutOfTurnReplies;
394 :
395 : //
396 : // Stack of RPC in-calls that were deferred because of race
397 : // conditions.
398 : //
399 : std::stack<Message> mDeferred;
400 :
401 : //
402 : // This is what we think the RPC stack depth is on the "other
403 : // side" of this RPC channel. We maintain this variable so that
404 : // we can detect racy RPC calls. With each RPC out-call sent, we
405 : // send along what *we* think the stack depth of the remote side
406 : // is *before* it will receive the RPC call.
407 : //
408 : // After sending the out-call, our stack depth is "incremented"
409 : // by pushing that pending message onto mPending.
410 : //
411 : // Then when processing an in-call |c|, it must be true that
412 : //
413 : // mStack.size() == c.remoteDepth
414 : //
415 : // i.e., my depth is actually the same as what the other side
416 : // thought it was when it sent in-call |c|. If this fails to
417 : // hold, we have detected racy RPC calls.
418 : //
419 : // We then increment mRemoteStackDepth *just before* processing
420 : // the in-call, since we know the other side is waiting on it, and
421 : // decrement it *just after* finishing processing that in-call,
422 : // since our response will pop the top of the other side's
423 : // |mPending|.
424 : //
425 : // One nice aspect of this race detection is that it is symmetric;
426 : // if one side detects a race, then the other side must also
427 : // detect the same race.
428 : //
429 : size_t mRemoteStackDepthGuess;
430 :
431 : // True iff the parent has put us in a |BlockChild()| state.
432 : bool mBlockedOnParent;
433 :
434 : // Approximation of Sync/RPCChannel-code frames on the C++ stack.
435 : // It can only be interpreted as the implication
436 : //
437 : // !mCxxStackFrames.empty() => RPCChannel code on C++ stack
438 : //
439 : // This member is only accessed on the worker thread, and so is
440 : // not protected by mMonitor. It is managed exclusively by the
441 : // helper |class CxxStackFrame|.
442 : std::vector<RPCFrame> mCxxStackFrames;
443 :
444 : // Did we process an RPC out-call during this stack? Only
445 : // meaningful in ExitedCxxStack(), from which this variable is
446 : // reset.
447 : bool mSawRPCOutMsg;
448 :
449 : private:
450 :
451 : //
452 : // All dequeuing tasks require a single point of cancellation,
453 : // which is handled via a reference-counted task.
454 : //
455 : class RefCountedTask
456 : {
457 : public:
458 1 : RefCountedTask(CancelableTask* aTask)
459 : : mTask(aTask)
460 1 : , mRefCnt(0) {}
461 0 : ~RefCountedTask() { delete mTask; }
462 0 : void Run() { mTask->Run(); }
463 0 : void Cancel() { mTask->Cancel(); }
464 1 : void AddRef() {
465 1 : NS_AtomicIncrementRefcnt(mRefCnt);
466 1 : }
467 0 : void Release() {
468 0 : if (NS_AtomicDecrementRefcnt(mRefCnt) == 0)
469 0 : delete this;
470 0 : }
471 :
472 : private:
473 : CancelableTask* mTask;
474 : nsrefcnt mRefCnt;
475 : };
476 :
477 : //
478 : // Wrap an existing task which can be cancelled at any time
479 : // without the wrapper's knowledge.
480 : //
481 : class DequeueTask : public Task
482 0 : {
483 : public:
484 0 : DequeueTask(RefCountedTask* aTask) : mTask(aTask) {}
485 0 : void Run() { mTask->Run(); }
486 :
487 : private:
488 : nsRefPtr<RefCountedTask> mTask;
489 : };
490 :
491 : // A task encapsulating dequeuing one pending task
492 : nsRefPtr<RefCountedTask> mDequeueOneTask;
493 : };
494 :
495 :
496 : } // namespace ipc
497 : } // namespace mozilla
498 : #endif // ifndef ipc_glue_RPCChannel_h
|