void RPCChannel::Incall(const Message& call, size_t stackDepth) { AssertWorkerThread(); mMonitor->AssertNotCurrentThreadOwns(); RPC_ASSERT(call.is_rpc() && !call.is_reply(), "wrong message type"); // Race detection: see the long comment near // mRemoteStackDepthGuess in RPCChannel.h. "Remote" stack depth // means our side, and "local" means other side. if (call.rpc_remote_stack_depth_guess() != RemoteViewOfStackDepth(stackDepth)) { // RPC in-calls have raced. // the "winner", if there is one, gets to defer processing of // the other side's in-call bool defer; const char* winner; switch (Listener()->MediateRPCRace(mChild ? call : mStack.top(), mChild ? mStack.top() : call)) { case RRPChildWins: winner = "child"; defer = mChild; break; case RRPParentWins: winner = "parent"; defer = !mChild; break; case RRPError: NS_RUNTIMEABORT("NYI: 'Error' RPC race policy"); return; default: NS_RUNTIMEABORT("not reached"); return; } if (LoggingEnabled()) { printf_stderr(" (%s: %s won, so we're%sdeferring)\n", mChild ? "child" : "parent", winner, defer ? " " : " not "); } if (defer) { // we now know the other side's stack has one more frame // than we thought ++mRemoteStackDepthGuess; // decremented in MaybeProcessDeferred() mDeferred.push(call); return; } // we "lost" and need to process the other side's in-call. // don't need to fix up the mRemoteStackDepthGuess here, // because we're just about to increment it in DispatchCall(), // which will make it correct again } #ifdef OS_WIN SyncStackFrame frame(this, true); #endif DispatchIncall(call); }
void AsyncChannel::DispatchOnChannelConnected(int32 peer_pid) { AssertWorkerThread(); if (mListener) mListener->OnChannelConnected(peer_pid); }
void RPCChannel::DispatchIncall(const Message& call) { AssertWorkerThread(); mMonitor->AssertNotCurrentThreadOwns(); RPC_ASSERT(call.is_rpc() && !call.is_reply(), "wrong message type"); Message* reply = nullptr; ++mRemoteStackDepthGuess; Result rv = Listener()->OnCallReceived(call, reply); --mRemoteStackDepthGuess; if (!MaybeHandleError(rv, "RPCChannel")) { delete reply; reply = new Message(); reply->set_rpc(); reply->set_reply(); reply->set_reply_error(); } reply->set_seqno(call.seqno()); { MonitorAutoLock lock(*mMonitor); if (ChannelConnected == mChannelState) mLink->SendMessage(reply); } }
void SyncChannel::OnDispatchMessage(const Message& msg) { AssertWorkerThread(); NS_ABORT_IF_FALSE(msg.is_sync(), "only sync messages here"); NS_ABORT_IF_FALSE(!msg.is_reply(), "wasn't awaiting reply"); Message* reply = 0; mProcessingSyncMessage = true; Result rv = static_cast<SyncListener*>(mListener)->OnMessageReceived(msg, reply); mProcessingSyncMessage = false; if (!MaybeHandleError(rv, "SyncChannel")) { // FIXME/cjones: error handling; OnError()? delete reply; reply = new Message(); reply->set_sync(); reply->set_reply(); reply->set_reply_error(); } reply->set_seqno(msg.seqno()); { MonitorAutoLock lock(mMonitor); if (ChannelConnected == mChannelState) SendThroughTransport(reply); } }
void RPCChannel::MaybeUndeferIncall() { AssertWorkerThread(); mMonitor->AssertCurrentThreadOwns(); if (mDeferred.empty()) return; size_t stackDepth = StackDepth(); // the other side can only *under*-estimate our actual stack depth RPC_ASSERT(mDeferred.top().rpc_remote_stack_depth_guess() <= stackDepth, "fatal logic error"); if (mDeferred.top().rpc_remote_stack_depth_guess() < RemoteViewOfStackDepth(stackDepth)) return; // maybe time to process this message Message call = mDeferred.top(); mDeferred.pop(); // fix up fudge factor we added to account for race RPC_ASSERT(0 < mRemoteStackDepthGuess, "fatal logic error"); --mRemoteStackDepthGuess; mPending.push_back(call); }
bool SyncChannel::ShouldContinueFromTimeout() { AssertWorkerThread(); mMonitor.AssertCurrentThreadOwns(); bool cont; { MonitorAutoUnlock unlock(mMonitor); cont = static_cast<SyncListener*>(mListener)->OnReplyTimeout(); } if (!cont) { // NB: there's a sublety here. If parents were allowed to // send sync messages to children, then it would be possible // for this synchronous close-on-timeout to race with async // |OnMessageReceived| tasks arriving from the child, posted // to the worker thread's event loop. This would complicate // cleanup of the *Channel. But since IPDL forbids this (and // since it doesn't support children timing out on parents), // the parent can only block on RPC messages to the child, and // in that case arriving async messages are enqueued to the // RPC channel's special queue. They're then ignored because // the channel state changes to ChannelTimeout // (i.e. !Connected). SynchronouslyClose(); mChannelState = ChannelTimeout; } return cont; }
void AsyncChannel::OnNotifyMaybeChannelError() { AssertWorkerThread(); mMonitor->AssertNotCurrentThreadOwns(); // OnChannelError holds mMonitor when it posts this task and this // task cannot be allowed to run until OnChannelError has // exited. We enforce that order by grabbing the mutex here which // should only continue once OnChannelError has completed. { MonitorAutoLock lock(*mMonitor); // nothing to do here } if (ShouldDeferNotifyMaybeError()) { mChannelErrorTask = NewRunnableMethod(this, &AsyncChannel::OnNotifyMaybeChannelError); // 10 ms delay is completely arbitrary mWorkerLoop->PostDelayedTask(FROM_HERE, mChannelErrorTask, 10); return; } NotifyMaybeChannelError(); }
bool SyncChannel::Send(Message* msg, Message* reply) { AssertWorkerThread(); mMonitor.AssertNotCurrentThreadOwns(); NS_ABORT_IF_FALSE(!ProcessingSyncMessage(), "violation of sync handler invariant"); NS_ABORT_IF_FALSE(msg->is_sync(), "can only Send() sync messages here"); #ifdef OS_WIN SyncStackFrame frame(this, false); #endif msg->set_seqno(NextSeqno()); MonitorAutoLock lock(mMonitor); if (!Connected()) { ReportConnectionError("SyncChannel"); return false; } mPendingReply = msg->type() + 1; int32 msgSeqno = msg->seqno(); SendThroughTransport(msg); while (1) { bool maybeTimedOut = !SyncChannel::WaitForNotify(); if (EventOccurred()) break; if (maybeTimedOut && !ShouldContinueFromTimeout()) return false; } if (!Connected()) { ReportConnectionError("SyncChannel"); return false; } // we just received a synchronous message from the other side. // If it's not the reply we were awaiting, there's a serious // error: either a mistimed/malformed message or a sync in-message // that raced with our sync out-message. // (NB: IPDL prevents the latter from occuring in actor code) // FIXME/cjones: real error handling NS_ABORT_IF_FALSE(mRecvd.is_sync() && mRecvd.is_reply() && (mRecvd.is_reply_error() || (mPendingReply == mRecvd.type() && msgSeqno == mRecvd.seqno())), "unexpected sync message"); mPendingReply = 0; *reply = mRecvd; mRecvd = Message(); return true; }
void AsyncChannel::SynchronouslyClose() { AssertWorkerThread(); mMonitor->AssertCurrentThreadOwns(); mLink->SendClose(); while (ChannelClosed != mChannelState) mMonitor->Wait(); }
bool SyncChannel::EventOccurred() { AssertWorkerThread(); mMonitor.AssertCurrentThreadOwns(); NS_ABORT_IF_FALSE(AwaitingSyncReply(), "not in wait loop"); return (!Connected() || 0 != mRecvd.type()); }
void AsyncChannel::Close() { AssertWorkerThread(); { // n.b.: We increase the ref count of monitor temporarily // for the duration of this block. Otherwise, the // function NotifyMaybeChannelError() will call // ::Clear() which can free the monitor. nsRefPtr<RefCountedMonitor> monitor(mMonitor); MonitorAutoLock lock(*monitor); if (ChannelError == mChannelState || ChannelTimeout == mChannelState) { // See bug 538586: if the listener gets deleted while the // IO thread's NotifyChannelError event is still enqueued // and subsequently deletes us, then the error event will // also be deleted and the listener will never be notified // of the channel error. if (mListener) { MonitorAutoUnlock unlock(*monitor); NotifyMaybeChannelError(); } return; } if (ChannelConnected != mChannelState) // XXX be strict about this until there's a compelling reason // to relax NS_RUNTIMEABORT("Close() called on closed channel!"); AssertWorkerThread(); // notify the other side that we're about to close our socket SendSpecialMessage(new GoodbyeMessage()); SynchronouslyClose(); } NotifyChannelClosed(); }
void AsyncChannel::CloseWithError() { AssertWorkerThread(); MonitorAutoLock lock(*mMonitor); if (ChannelConnected != mChannelState) { return; } SynchronouslyClose(); mChannelState = ChannelError; PostErrorNotifyTask(); }
bool RPCChannel::OnMaybeDequeueOne() { // XXX performance tuning knob: could process all or k pending // messages here AssertWorkerThread(); mMonitor->AssertNotCurrentThreadOwns(); Message recvd; { MonitorAutoLock lock(*mMonitor); if (!Connected()) { ReportConnectionError("RPCChannel"); return false; } if (!mDeferred.empty()) MaybeUndeferIncall(); MessageQueue *queue = mUrgent.empty() ? mNonUrgentDeferred.empty() ? &mPending : &mNonUrgentDeferred : &mUrgent; if (queue->empty()) return false; recvd = queue->front(); queue->pop_front(); } if (IsOnCxxStack() && recvd.is_rpc() && recvd.is_reply()) { // We probably just received a reply in a nested loop for an // RPC call sent before entering that loop. mOutOfTurnReplies[recvd.seqno()] = recvd; return false; } CxxStackFrame f(*this, IN_MESSAGE, &recvd); if (recvd.is_rpc()) Incall(recvd, 0); else if (recvd.is_sync()) SyncChannel::OnDispatchMessage(recvd); else AsyncChannel::OnDispatchMessage(recvd); return true; }
bool RPCChannel::EventOccurred() const { AssertWorkerThread(); mMonitor->AssertCurrentThreadOwns(); RPC_ASSERT(StackDepth() > 0, "not in wait loop"); return (!Connected() || !mPending.empty() || !mUrgent.empty() || (!mOutOfTurnReplies.empty() && mOutOfTurnReplies.find(mStack.top().seqno()) != mOutOfTurnReplies.end())); }
void AsyncChannel::OnDispatchMessage(const Message& msg) { AssertWorkerThread(); NS_ASSERTION(!msg.is_reply(), "can't process replies here"); NS_ASSERTION(!(msg.is_sync() || msg.is_rpc()), "async dispatch only"); if (MSG_ROUTING_NONE == msg.routing_id()) { if (!OnSpecialMessage(msg.type(), msg)) // XXX real error handling NS_RUNTIMEABORT("unhandled special message!"); return; } // it's OK to dispatch messages if the channel is closed/error'd, // since we don't have a reply to send back (void)MaybeHandleError(mListener->OnMessageReceived(msg), "AsyncChannel"); }
void RPCChannel::EnqueuePendingMessages() { AssertWorkerThread(); mMonitor->AssertCurrentThreadOwns(); MaybeUndeferIncall(); for (size_t i = 0; i < mDeferred.size(); ++i) { mWorkerLoop->PostTask(FROM_HERE, new DequeueTask(mDequeueOneTask)); } // XXX performance tuning knob: could process all or k pending // messages here, rather than enqueuing for later processing size_t total = mPending.size() + mUrgent.size() + mNonUrgentDeferred.size(); for (size_t i = 0; i < total; ++i) { mWorkerLoop->PostTask(FROM_HERE, new DequeueTask(mDequeueOneTask)); } }
bool AsyncChannel::Echo(Message* _msg) { nsAutoPtr<Message> msg(_msg); AssertWorkerThread(); mMonitor->AssertNotCurrentThreadOwns(); NS_ABORT_IF_FALSE(MSG_ROUTING_NONE != msg->routing_id(), "need a route"); { MonitorAutoLock lock(*mMonitor); if (!Connected()) { ReportConnectionError("AsyncChannel"); return false; } mLink->EchoMessage(msg.forget()); } return true; }
void RPCChannel::FlushPendingRPCQueue() { AssertWorkerThread(); mMonitor->AssertNotCurrentThreadOwns(); { MonitorAutoLock lock(*mMonitor); if (mDeferred.empty()) { if (mPending.empty()) return; const Message& last = mPending.back(); if (!last.is_rpc() || last.is_reply()) return; } } while (OnMaybeDequeueOne()); }
void AsyncChannel::SendSpecialMessage(Message* msg) const { AssertWorkerThread(); mLink->SendMessage(msg); }
bool RPCChannel::Call(Message* _msg, Message* reply) { RPC_ASSERT(!mPendingReply, "should not be waiting for a reply"); nsAutoPtr<Message> msg(_msg); AssertWorkerThread(); mMonitor->AssertNotCurrentThreadOwns(); RPC_ASSERT(!ProcessingSyncMessage() || msg->priority() == IPC::Message::PRIORITY_HIGH, "violation of sync handler invariant"); RPC_ASSERT(msg->is_rpc(), "can only Call() RPC messages here"); #ifdef OS_WIN SyncStackFrame frame(this, true); #endif Message copy = *msg; CxxStackFrame f(*this, OUT_MESSAGE, ©); MonitorAutoLock lock(*mMonitor); if (!Connected()) { ReportConnectionError("RPCChannel"); return false; } bool urgent = (copy.priority() == IPC::Message::PRIORITY_HIGH); msg->set_seqno(NextSeqno()); msg->set_rpc_remote_stack_depth_guess(mRemoteStackDepthGuess); msg->set_rpc_local_stack_depth(1 + StackDepth()); mStack.push(*msg); mLink->SendMessage(msg.forget()); while (1) { // if a handler invoked by *Dispatch*() spun a nested event // loop, and the connection was broken during that loop, we // might have already processed the OnError event. if so, // trying another loop iteration will be futile because // channel state will have been cleared if (!Connected()) { ReportConnectionError("RPCChannel"); return false; } // now might be the time to process a message deferred because // of race resolution MaybeUndeferIncall(); // here we're waiting for something to happen. see long // comment about the queue in RPCChannel.h while (!EventOccurred()) { bool maybeTimedOut = !RPCChannel::WaitForNotify(); if (EventOccurred() || // we might have received a "subtly deferred" message // in a nested loop that it's now time to process (!maybeTimedOut && (!mDeferred.empty() || !mOutOfTurnReplies.empty()))) break; if (maybeTimedOut && !ShouldContinueFromTimeout()) return false; } if (!Connected()) { ReportConnectionError("RPCChannel"); return false; } Message recvd; MessageMap::iterator it; if (!mOutOfTurnReplies.empty() && ((it = mOutOfTurnReplies.find(mStack.top().seqno())) != mOutOfTurnReplies.end())) { recvd = it->second; mOutOfTurnReplies.erase(it); } else if (!mUrgent.empty()) { recvd = mUrgent.front(); mUrgent.pop_front(); } else if (!mPending.empty()) { recvd = mPending.front(); mPending.pop_front(); } else { // because of subtleties with nested event loops, it's // possible that we got here and nothing happened. or, we // might have a deferred in-call that needs to be // processed. either way, we won't break the inner while // loop again until something new happens. continue; } if (!recvd.is_rpc()) { if (urgent && recvd.priority() != IPC::Message::PRIORITY_HIGH) { // If we're waiting for an urgent reply, don't process any // messages yet. mNonUrgentDeferred.push_back(recvd); } else if (recvd.is_sync()) { RPC_ASSERT(mPending.empty(), "other side should have been blocked"); MonitorAutoUnlock unlock(*mMonitor); CxxStackFrame f(*this, IN_MESSAGE, &recvd); SyncChannel::OnDispatchMessage(recvd); } else { MonitorAutoUnlock unlock(*mMonitor); CxxStackFrame f(*this, IN_MESSAGE, &recvd); AsyncChannel::OnDispatchMessage(recvd); } continue; } RPC_ASSERT(recvd.is_rpc(), "wtf???"); if (recvd.is_reply()) { RPC_ASSERT(0 < mStack.size(), "invalid RPC stack"); const Message& outcall = mStack.top(); // in the parent, seqno's increase from 0, and in the // child, they decrease from 0 if ((!mChild && recvd.seqno() < outcall.seqno()) || (mChild && recvd.seqno() > outcall.seqno())) { mOutOfTurnReplies[recvd.seqno()] = recvd; continue; } // FIXME/cjones: handle error RPC_ASSERT( recvd.is_reply_error() || (recvd.type() == (outcall.type()+1) && recvd.seqno() == outcall.seqno()), "somebody's misbehavin'", "rpc", true); // we received a reply to our most recent outstanding // call. pop this frame and return the reply mStack.pop(); bool isError = recvd.is_reply_error(); if (!isError) { *reply = recvd; } if (0 == StackDepth()) { RPC_ASSERT( mOutOfTurnReplies.empty(), "still have pending replies with no pending out-calls", "rpc", true); } // finished with this RPC stack frame return !isError; } // in-call. process in a new stack frame. // "snapshot" the current stack depth while we own the Monitor size_t stackDepth = StackDepth(); { MonitorAutoUnlock unlock(*mMonitor); // someone called in to us from the other side. handle the call CxxStackFrame f(*this, IN_MESSAGE, &recvd); Incall(recvd, stackDepth); // FIXME/cjones: error handling } } return true; }
size_t RPCChannel::RemoteViewOfStackDepth(size_t stackDepth) const { AssertWorkerThread(); return stackDepth - mOutOfTurnReplies.size(); }