1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 : * vim: sw=4 ts=4 et :
3 : */
4 : /* ***** BEGIN LICENSE BLOCK *****
5 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 : *
7 : * The contents of this file are subject to the Mozilla Public License Version
8 : * 1.1 (the "License"); you may not use this file except in compliance with
9 : * the License. You may obtain a copy of the License at
10 : * http://www.mozilla.org/MPL/
11 : *
12 : * Software distributed under the License is distributed on an "AS IS" basis,
13 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14 : * for the specific language governing rights and limitations under the
15 : * License.
16 : *
17 : * The Original Code is Mozilla Plugin App.
18 : *
19 : * The Initial Developer of the Original Code is
20 : * Chris Jones <jones.chris.g@gmail.com>
21 : * Portions created by the Initial Developer are Copyright (C) 2009
22 : * the Initial Developer. All Rights Reserved.
23 : *
24 : * Contributor(s):
25 : *
26 : * Alternatively, the contents of this file may be used under the terms of
27 : * either the GNU General Public License Version 2 or later (the "GPL"), or
28 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 : * in which case the provisions of the GPL or the LGPL are applicable instead
30 : * of those above. If you wish to allow use of your version of this file only
31 : * under the terms of either the GPL or the LGPL, and not to allow others to
32 : * use your version of this file under the terms of the MPL, indicate your
33 : * decision by deleting the provisions above and replace them with the notice
34 : * and other provisions required by the GPL or the LGPL. If you do not delete
35 : * the provisions above, a recipient may use your version of this file under
36 : * the terms of any one of the MPL, the GPL or the LGPL.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
40 : #include "mozilla/ipc/SyncChannel.h"
41 :
42 : #include "nsDebug.h"
43 : #include "nsTraceRefcnt.h"
44 :
45 : using mozilla::MonitorAutoLock;
46 :
47 : template<>
48 : struct RunnableMethodTraits<mozilla::ipc::SyncChannel>
49 0 : {
50 0 : static void RetainCallee(mozilla::ipc::SyncChannel* obj) { }
51 0 : static void ReleaseCallee(mozilla::ipc::SyncChannel* obj) { }
52 : };
53 :
54 : namespace mozilla {
55 : namespace ipc {
56 :
57 : const int32 SyncChannel::kNoTimeout = PR_INT32_MIN;
58 :
59 1 : SyncChannel::SyncChannel(SyncListener* aListener)
60 : : AsyncChannel(aListener)
61 : , mPendingReply(0)
62 : , mProcessingSyncMessage(false)
63 : , mNextSeqno(0)
64 : , mInTimeoutSecondHalf(false)
65 1 : , mTimeoutMs(kNoTimeout)
66 : #ifdef OS_WIN
67 : , mTopFrame(NULL)
68 : #endif
69 : {
70 1 : MOZ_COUNT_CTOR(SyncChannel);
71 : #ifdef OS_WIN
72 : mEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
73 : NS_ASSERTION(mEvent, "CreateEvent failed! Nothing is going to work!");
74 : #endif
75 1 : }
76 :
77 0 : SyncChannel::~SyncChannel()
78 : {
79 0 : MOZ_COUNT_DTOR(SyncChannel);
80 : #ifdef OS_WIN
81 : CloseHandle(mEvent);
82 : #endif
83 0 : }
84 :
85 : // static
86 : bool SyncChannel::sIsPumpingMessages = false;
87 :
88 : bool
89 0 : SyncChannel::EventOccurred()
90 : {
91 0 : AssertWorkerThread();
92 0 : mMonitor->AssertCurrentThreadOwns();
93 0 : NS_ABORT_IF_FALSE(AwaitingSyncReply(), "not in wait loop");
94 :
95 0 : return (!Connected() || 0 != mRecvd.type() || mRecvd.is_reply_error());
96 : }
97 :
98 : bool
99 0 : SyncChannel::Send(Message* _msg, Message* reply)
100 : {
101 0 : nsAutoPtr<Message> msg(_msg);
102 :
103 0 : AssertWorkerThread();
104 0 : mMonitor->AssertNotCurrentThreadOwns();
105 0 : NS_ABORT_IF_FALSE(!ProcessingSyncMessage(),
106 : "violation of sync handler invariant");
107 0 : NS_ABORT_IF_FALSE(msg->is_sync(), "can only Send() sync messages here");
108 :
109 : #ifdef OS_WIN
110 : SyncStackFrame frame(this, false);
111 : #endif
112 :
113 0 : msg->set_seqno(NextSeqno());
114 :
115 0 : MonitorAutoLock lock(*mMonitor);
116 :
117 0 : if (!Connected()) {
118 0 : ReportConnectionError("SyncChannel");
119 0 : return false;
120 : }
121 :
122 0 : mPendingReply = msg->type() + 1;
123 0 : int32 msgSeqno = msg->seqno();
124 0 : mLink->SendMessage(msg.forget());
125 :
126 0 : while (1) {
127 0 : bool maybeTimedOut = !SyncChannel::WaitForNotify();
128 :
129 0 : if (EventOccurred())
130 : break;
131 :
132 0 : if (maybeTimedOut && !ShouldContinueFromTimeout())
133 0 : return false;
134 : }
135 :
136 0 : if (!Connected()) {
137 0 : ReportConnectionError("SyncChannel");
138 0 : return false;
139 : }
140 :
141 : // we just received a synchronous message from the other side.
142 : // If it's not the reply we were awaiting, there's a serious
143 : // error: either a mistimed/malformed message or a sync in-message
144 : // that raced with our sync out-message.
145 : // (NB: IPDL prevents the latter from occuring in actor code)
146 :
147 : // FIXME/cjones: real error handling
148 0 : bool replyIsError = mRecvd.is_reply_error();
149 0 : NS_ABORT_IF_FALSE(mRecvd.is_sync() && mRecvd.is_reply() &&
150 : (replyIsError ||
151 : (mPendingReply == mRecvd.type() &&
152 : msgSeqno == mRecvd.seqno())),
153 : "unexpected sync message");
154 :
155 0 : mPendingReply = 0;
156 0 : if (!replyIsError) {
157 0 : *reply = mRecvd;
158 : }
159 0 : mRecvd = Message();
160 :
161 0 : return !replyIsError;
162 : }
163 :
164 : void
165 0 : SyncChannel::OnDispatchMessage(const Message& msg)
166 : {
167 0 : AssertWorkerThread();
168 0 : NS_ABORT_IF_FALSE(msg.is_sync(), "only sync messages here");
169 0 : NS_ABORT_IF_FALSE(!msg.is_reply(), "wasn't awaiting reply");
170 :
171 0 : Message* reply = 0;
172 :
173 0 : mProcessingSyncMessage = true;
174 : Result rv =
175 0 : static_cast<SyncListener*>(mListener)->OnMessageReceived(msg, reply);
176 0 : mProcessingSyncMessage = false;
177 :
178 0 : if (!MaybeHandleError(rv, "SyncChannel")) {
179 : // FIXME/cjones: error handling; OnError()?
180 0 : delete reply;
181 0 : reply = new Message();
182 0 : reply->set_sync();
183 0 : reply->set_reply();
184 0 : reply->set_reply_error();
185 : }
186 :
187 0 : reply->set_seqno(msg.seqno());
188 :
189 : {
190 0 : MonitorAutoLock lock(*mMonitor);
191 0 : if (ChannelConnected == mChannelState)
192 0 : mLink->SendMessage(reply);
193 : }
194 0 : }
195 :
196 : //
197 : // The methods below run in the context of the link thread, and can proxy
198 : // back to the methods above
199 : //
200 :
201 : void
202 0 : SyncChannel::OnMessageReceivedFromLink(const Message& msg)
203 : {
204 0 : AssertLinkThread();
205 0 : mMonitor->AssertCurrentThreadOwns();
206 :
207 0 : if (!msg.is_sync()) {
208 0 : AsyncChannel::OnMessageReceivedFromLink(msg);
209 0 : return;
210 : }
211 :
212 0 : if (MaybeInterceptSpecialIOMessage(msg))
213 0 : return;
214 :
215 0 : if (!AwaitingSyncReply()) {
216 : // wake up the worker, there's work to do
217 : mWorkerLoop->PostTask(
218 : FROM_HERE,
219 0 : NewRunnableMethod(this, &SyncChannel::OnDispatchMessage, msg));
220 : }
221 : else {
222 : // let the worker know a new sync message has arrived
223 0 : mRecvd = msg;
224 0 : NotifyWorkerThread();
225 : }
226 : }
227 :
228 : void
229 0 : SyncChannel::OnChannelErrorFromLink()
230 : {
231 0 : AssertLinkThread();
232 0 : mMonitor->AssertCurrentThreadOwns();
233 :
234 0 : if (AwaitingSyncReply())
235 0 : NotifyWorkerThread();
236 :
237 0 : AsyncChannel::OnChannelErrorFromLink();
238 0 : }
239 :
240 : //
241 : // Synchronization between worker and IO threads
242 : //
243 :
244 : namespace {
245 :
246 : bool
247 0 : IsTimeoutExpired(PRIntervalTime aStart, PRIntervalTime aTimeout)
248 : {
249 : return (aTimeout != PR_INTERVAL_NO_TIMEOUT) &&
250 0 : (aTimeout <= (PR_IntervalNow() - aStart));
251 : }
252 :
253 : } // namespace <anon>
254 :
255 : bool
256 0 : SyncChannel::ShouldContinueFromTimeout()
257 : {
258 0 : AssertWorkerThread();
259 0 : mMonitor->AssertCurrentThreadOwns();
260 :
261 : bool cont;
262 : {
263 0 : MonitorAutoUnlock unlock(*mMonitor);
264 0 : cont = static_cast<SyncListener*>(mListener)->OnReplyTimeout();
265 : }
266 :
267 0 : if (!cont) {
268 : // NB: there's a sublety here. If parents were allowed to
269 : // send sync messages to children, then it would be possible
270 : // for this synchronous close-on-timeout to race with async
271 : // |OnMessageReceived| tasks arriving from the child, posted
272 : // to the worker thread's event loop. This would complicate
273 : // cleanup of the *Channel. But since IPDL forbids this (and
274 : // since it doesn't support children timing out on parents),
275 : // the parent can only block on RPC messages to the child, and
276 : // in that case arriving async messages are enqueued to the
277 : // RPC channel's special queue. They're then ignored because
278 : // the channel state changes to ChannelTimeout
279 : // (i.e. !Connected).
280 0 : SynchronouslyClose();
281 0 : mChannelState = ChannelTimeout;
282 : }
283 :
284 0 : return cont;
285 : }
286 :
287 : bool
288 0 : SyncChannel::WaitResponse(bool aWaitTimedOut)
289 : {
290 0 : if (aWaitTimedOut) {
291 0 : if (mInTimeoutSecondHalf) {
292 : // We've really timed out this time
293 0 : return false;
294 : }
295 : // Try a second time
296 0 : mInTimeoutSecondHalf = true;
297 : } else {
298 0 : mInTimeoutSecondHalf = false;
299 : }
300 0 : return true;
301 : }
302 :
303 :
304 : // Windows versions of the following two functions live in
305 : // WindowsMessageLoop.cpp.
306 :
307 : #ifndef OS_WIN
308 :
309 : bool
310 0 : SyncChannel::WaitForNotify()
311 : {
312 : PRIntervalTime timeout = (kNoTimeout == mTimeoutMs) ?
313 : PR_INTERVAL_NO_TIMEOUT :
314 0 : PR_MillisecondsToInterval(mTimeoutMs);
315 : // XXX could optimize away this syscall for "no timeout" case if desired
316 0 : PRIntervalTime waitStart = PR_IntervalNow();
317 :
318 0 : mMonitor->Wait(timeout);
319 :
320 : // if the timeout didn't expire, we know we received an event.
321 : // The converse is not true.
322 0 : return WaitResponse(IsTimeoutExpired(waitStart, timeout));
323 : }
324 :
325 : void
326 0 : SyncChannel::NotifyWorkerThread()
327 : {
328 0 : mMonitor->Notify();
329 0 : }
330 :
331 : #endif // ifndef OS_WIN
332 :
333 :
334 : } // namespace ipc
335 : } // namespace mozilla
|