2009-06-29 11:38:29 -07:00
|
|
|
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
|
|
* vim: sw=4 ts=4 et :
|
|
|
|
* ***** BEGIN LICENSE BLOCK *****
|
|
|
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
|
|
*
|
|
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
|
|
* the License. You may obtain a copy of the License at
|
|
|
|
* http://www.mozilla.org/MPL/
|
|
|
|
*
|
|
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
|
|
* for the specific language governing rights and limitations under the
|
|
|
|
* License.
|
|
|
|
*
|
|
|
|
* The Original Code is Mozilla Plugin App.
|
|
|
|
*
|
|
|
|
* The Initial Developer of the Original Code is
|
|
|
|
* Chris Jones <jones.chris.g@gmail.com>
|
|
|
|
* Portions created by the Initial Developer are Copyright (C) 2009
|
|
|
|
* the Initial Developer. All Rights Reserved.
|
|
|
|
*
|
|
|
|
* Contributor(s):
|
|
|
|
*
|
|
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
|
|
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
|
|
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
|
|
* use your version of this file under the terms of the MPL, indicate your
|
|
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
|
|
* the provisions above, a recipient may use your version of this file under
|
|
|
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
|
|
|
*
|
|
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
|
|
|
|
#ifndef ipc_glue_RPCChannel_h
|
|
|
|
#define ipc_glue_RPCChannel_h 1
|
|
|
|
|
2009-09-11 00:28:09 -07:00
|
|
|
// FIXME/cjones probably shouldn't depend on STL
|
|
|
|
#include <queue>
|
2009-06-29 11:38:29 -07:00
|
|
|
#include <stack>
|
|
|
|
|
2009-07-13 14:55:04 -07:00
|
|
|
#include "mozilla/ipc/SyncChannel.h"
|
2009-06-29 11:38:29 -07:00
|
|
|
|
|
|
|
namespace mozilla {
|
|
|
|
namespace ipc {
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
2009-07-13 14:55:04 -07:00
|
|
|
class RPCChannel : public SyncChannel
|
2009-06-29 11:38:29 -07:00
|
|
|
{
|
|
|
|
public:
|
2009-07-15 15:33:37 -07:00
|
|
|
class /*NS_INTERFACE_CLASS*/ RPCListener :
|
|
|
|
public SyncChannel::SyncListener
|
2009-06-29 11:38:29 -07:00
|
|
|
{
|
|
|
|
public:
|
2009-07-15 15:06:30 -07:00
|
|
|
virtual ~RPCListener() { }
|
2009-07-13 14:55:04 -07:00
|
|
|
virtual Result OnMessageReceived(const Message& aMessage) = 0;
|
|
|
|
virtual Result OnMessageReceived(const Message& aMessage,
|
|
|
|
Message*& aReply) = 0;
|
2009-06-29 11:38:29 -07:00
|
|
|
virtual Result OnCallReceived(const Message& aMessage,
|
2009-07-01 22:45:19 -07:00
|
|
|
Message*& aReply) = 0;
|
2009-06-29 11:38:29 -07:00
|
|
|
};
|
|
|
|
|
2009-10-08 14:44:43 -07:00
|
|
|
// What happens if RPC calls race?
|
|
|
|
enum RacyRPCPolicy {
|
|
|
|
RRPError,
|
|
|
|
RRPChildWins,
|
|
|
|
RRPParentWins
|
|
|
|
};
|
|
|
|
|
|
|
|
RPCChannel(RPCListener* aListener, RacyRPCPolicy aPolicy=RRPChildWins) :
|
2009-08-18 22:22:01 -07:00
|
|
|
SyncChannel(aListener),
|
|
|
|
mPending(),
|
2009-10-08 14:44:43 -07:00
|
|
|
mRemoteStackDepthGuess(0),
|
|
|
|
mRacePolicy(aPolicy)
|
2009-06-29 11:38:29 -07:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual ~RPCChannel()
|
|
|
|
{
|
2009-07-13 14:55:04 -07:00
|
|
|
// FIXME/cjones: impl
|
2009-06-29 11:38:29 -07:00
|
|
|
}
|
|
|
|
|
2009-07-13 14:55:04 -07:00
|
|
|
// Make an RPC to the other side of the channel
|
|
|
|
bool Call(Message* msg, Message* reply);
|
2009-06-29 11:38:29 -07:00
|
|
|
|
2009-07-13 14:55:04 -07:00
|
|
|
// Override the SyncChannel handler so we can dispatch RPC messages
|
2009-09-21 19:02:15 -07:00
|
|
|
NS_OVERRIDE virtual void OnMessageReceived(const Message& msg);
|
|
|
|
NS_OVERRIDE virtual void OnChannelError();
|
2009-06-29 11:38:29 -07:00
|
|
|
|
2009-09-11 00:28:09 -07:00
|
|
|
protected:
|
|
|
|
// Only exists because we can't schedule SyncChannel::OnDispatchMessage
|
|
|
|
// or AsyncChannel::OnDispatchMessage from within Call() when we flush
|
|
|
|
// the pending queue
|
|
|
|
void OnDelegate(const Message& msg);
|
|
|
|
|
2009-09-22 08:23:29 -07:00
|
|
|
// There's a fairly subtle race condition that arises between
|
|
|
|
// processing an event on this side that ends up sending an RPC
|
|
|
|
// message, while receiving a sync message from the other side.
|
|
|
|
// See the long comment in RPCChannel.cpp, near line 300.
|
|
|
|
void OnMaybeDequeueOne();
|
|
|
|
|
2009-06-29 11:38:29 -07:00
|
|
|
private:
|
2009-08-18 22:22:01 -07:00
|
|
|
void OnIncall(const Message& msg);
|
|
|
|
void ProcessIncall(const Message& call, size_t stackDepth);
|
2009-06-29 11:38:29 -07:00
|
|
|
|
2009-09-11 00:28:09 -07:00
|
|
|
// Called from both threads
|
2009-08-18 22:22:01 -07:00
|
|
|
size_t StackDepth() {
|
|
|
|
mMutex.AssertCurrentThreadOwns();
|
2009-09-11 00:28:09 -07:00
|
|
|
return mStack.size();
|
2009-08-18 22:22:01 -07:00
|
|
|
}
|
|
|
|
|
2009-10-08 14:44:43 -07:00
|
|
|
#define RPC_DEBUGABORT(...) \
|
|
|
|
DebugAbort(__FILE__, __LINE__,## __VA_ARGS__)
|
|
|
|
|
|
|
|
void DebugAbort(const char* file, int line,
|
|
|
|
const char* why,
|
|
|
|
const char* type="rpc", bool reply=false)
|
|
|
|
{
|
|
|
|
fprintf(stderr,
|
|
|
|
"[RPCChannel][%s][%s:%d] Aborting: %s (triggered by %s%s)\n",
|
|
|
|
mChild ? "Child" : "Parent",
|
|
|
|
file, line,
|
|
|
|
why,
|
|
|
|
type, reply ? "reply" : "");
|
|
|
|
// technically we need the mutex for this, but we're dying anyway
|
|
|
|
fprintf(stderr, " local RPC stack size: %zu\n",
|
|
|
|
mStack.size());
|
|
|
|
fprintf(stderr, " remote RPC stack guess: %zd\n",
|
|
|
|
mRemoteStackDepthGuess);
|
|
|
|
fprintf(stderr, " Pending queue size: %zu, front to back:\n",
|
|
|
|
mPending.size());
|
|
|
|
while (!mPending.empty()) {
|
|
|
|
fprintf(stderr, " [ %s%s ]\n",
|
|
|
|
mPending.front().is_rpc() ? "rpc" :
|
|
|
|
(mPending.front().is_sync() ? "sync" : "async"),
|
|
|
|
mPending.front().is_reply() ? "reply" : "");
|
|
|
|
mPending.pop();
|
|
|
|
}
|
|
|
|
|
|
|
|
NS_RUNTIMEABORT(why);
|
|
|
|
}
|
|
|
|
|
2009-09-11 00:28:09 -07:00
|
|
|
//
|
|
|
|
// Stack of all the RPC out-calls on which this RPCChannel is
|
|
|
|
// awaiting a response.
|
2009-08-18 22:22:01 -07:00
|
|
|
//
|
2009-09-11 00:28:09 -07:00
|
|
|
std::stack<Message> mStack;
|
|
|
|
|
|
|
|
//
|
|
|
|
// After the worker thread is blocked on an RPC out-call
|
|
|
|
// (i.e. awaiting a reply), the IO thread uses this queue to
|
|
|
|
// transfer received messages to the worker thread for processing.
|
|
|
|
// If both this side and the other side are functioning correctly,
|
|
|
|
// the queue is only allowed to have certain configurations. Let
|
|
|
|
//
|
|
|
|
// |A<| be an async in-message,
|
|
|
|
// |S<| be a sync in-message,
|
|
|
|
// |C<| be an RPC in-call,
|
|
|
|
// |R<| be an RPC reply.
|
|
|
|
//
|
|
|
|
// After the worker thread wakes us up to process the queue,
|
|
|
|
// the queue can only match this configuration
|
|
|
|
//
|
|
|
|
// A<* (S< | C< | R< (?{mStack.size() == 1} A<* (S< | C<)))
|
|
|
|
//
|
|
|
|
// After we send an RPC message, the other side can send as many
|
|
|
|
// async messages |A<*| as it wants before sending back any other
|
|
|
|
// message type.
|
|
|
|
//
|
|
|
|
// The first "other message type" case is |S<|, a sync in-msg.
|
|
|
|
// The other side must be blocked, and thus can't send us any more
|
|
|
|
// messages until we process the sync in-msg.
|
|
|
|
//
|
|
|
|
// The second case is |C<|, an RPC in-call; the other side
|
|
|
|
// re-entered us while processing our out-call. It therefore must
|
|
|
|
// be blocked. (There's a subtlety here: this in-call might have
|
|
|
|
// raced with our out-call, but we detect that with the mechanism
|
|
|
|
// below, |mRemoteStackDepth|, and races don't matter to the
|
|
|
|
// queue.)
|
|
|
|
//
|
|
|
|
// Final case, the other side replied to our most recent out-call
|
|
|
|
// |R<|. If that was the *only* out-call on our stack, |{
|
|
|
|
// mStack.size() == 1}|, then other side "finished with us," and
|
|
|
|
// went back to its own business. That business might have
|
|
|
|
// included sending any number of async message |A<*| until
|
|
|
|
// sending a blocking message |(S< | C<)|. We just flush these to
|
|
|
|
// the event loop to process in order, it will do the Right Thing,
|
|
|
|
// since only the last message can be a blocking message.
|
|
|
|
// HOWEVER, if we had more than one RPC call on our stack, the
|
|
|
|
// other side *better* not have sent us another blocking message,
|
|
|
|
// because it's blocked on a reply from us.
|
|
|
|
//
|
|
|
|
std::queue<Message> mPending;
|
2009-08-18 22:22:01 -07:00
|
|
|
|
|
|
|
//
|
|
|
|
// This is what we think the RPC stack depth is on the "other
|
|
|
|
// side" of this RPC channel. We maintain this variable so that
|
|
|
|
// we can detect racy RPC calls. With each RPC out-call sent, we
|
|
|
|
// send along what *we* think the stack depth of the remote side
|
|
|
|
// is *before* it will receive the RPC call.
|
|
|
|
//
|
|
|
|
// After sending the out-call, our stack depth is "incremented"
|
|
|
|
// by pushing that pending message onto mPending.
|
|
|
|
//
|
|
|
|
// Then when processing an in-call |c|, it must be true that
|
|
|
|
//
|
|
|
|
// mPending.size() == c.remoteDepth
|
|
|
|
//
|
|
|
|
// i.e., my depth is actually the same as what the other side
|
|
|
|
// thought it was when it sent in-call |c|. If this fails to
|
|
|
|
// hold, we have detected racy RPC calls.
|
|
|
|
//
|
|
|
|
// We then increment mRemoteStackDepth *just before* processing
|
|
|
|
// the in-call, since we know the other side is waiting on it, and
|
|
|
|
// decrement it *just after* finishing processing that in-call,
|
|
|
|
// since our response will pop the top of the other side's
|
|
|
|
// |mPending|.
|
|
|
|
//
|
|
|
|
// One nice aspect of this race detection is that it is symmetric;
|
|
|
|
// if one side detects a race, then the other side must also
|
|
|
|
// detect the same race.
|
|
|
|
//
|
|
|
|
// TODO: and when we detect a race, what should we actually *do* ... ?
|
|
|
|
//
|
2009-10-08 14:44:43 -07:00
|
|
|
size_t mRemoteStackDepthGuess;
|
|
|
|
|
|
|
|
RacyRPCPolicy mRacePolicy;
|
2009-06-29 11:38:29 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
} // namespace ipc
|
|
|
|
} // namespace mozilla
|
|
|
|
#endif // ifndef ipc_glue_RPCChannel_h
|