/src/mozilla-central/netwerk/base/PollableEvent.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim:set ts=2 sw=2 sts=2 et cindent: */ |
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 "nsSocketTransportService2.h" |
8 | | #include "PollableEvent.h" |
9 | | #include "mozilla/Assertions.h" |
10 | | #include "mozilla/DebugOnly.h" |
11 | | #include "mozilla/Logging.h" |
12 | | #include "prerror.h" |
13 | | #include "prio.h" |
14 | | #include "private/pprio.h" |
15 | | #include "prnetdb.h" |
16 | | |
17 | | #ifdef XP_WIN |
18 | | #include "ShutdownLayer.h" |
19 | | #else |
20 | | #include <fcntl.h> |
21 | | #define USEPIPE 1 |
22 | | #endif |
23 | | |
24 | | namespace mozilla { |
25 | | namespace net { |
26 | | |
27 | | #ifndef USEPIPE |
28 | | static PRDescIdentity sPollableEventLayerIdentity; |
29 | | static PRIOMethods sPollableEventLayerMethods; |
30 | | static PRIOMethods *sPollableEventLayerMethodsPtr = nullptr; |
31 | | |
32 | | static void LazyInitSocket() |
33 | | { |
34 | | MOZ_ASSERT(OnSocketThread(), "not on socket thread"); |
35 | | if (sPollableEventLayerMethodsPtr) { |
36 | | return; |
37 | | } |
38 | | sPollableEventLayerIdentity = PR_GetUniqueIdentity("PollableEvent Layer"); |
39 | | sPollableEventLayerMethods = *PR_GetDefaultIOMethods(); |
40 | | sPollableEventLayerMethodsPtr = &sPollableEventLayerMethods; |
41 | | } |
42 | | |
43 | | static bool NewTCPSocketPair(PRFileDesc *fd[], bool aSetRecvBuff) |
44 | | { |
45 | | // this is a replacement for PR_NewTCPSocketPair that manually |
46 | | // sets the recv buffer to 64K. A windows bug (1248358) |
47 | | // can result in using an incompatible rwin and window |
48 | | // scale option on localhost pipes if not set before connect. |
49 | | |
50 | | SOCKET_LOG(("NewTCPSocketPair %s a recv buffer tuning\n", aSetRecvBuff ? "with" : "without")); |
51 | | |
52 | | PRFileDesc *listener = nullptr; |
53 | | PRFileDesc *writer = nullptr; |
54 | | PRFileDesc *reader = nullptr; |
55 | | PRSocketOptionData recvBufferOpt; |
56 | | recvBufferOpt.option = PR_SockOpt_RecvBufferSize; |
57 | | recvBufferOpt.value.recv_buffer_size = 65535; |
58 | | |
59 | | PRSocketOptionData nodelayOpt; |
60 | | nodelayOpt.option = PR_SockOpt_NoDelay; |
61 | | nodelayOpt.value.no_delay = true; |
62 | | |
63 | | PRSocketOptionData noblockOpt; |
64 | | noblockOpt.option = PR_SockOpt_Nonblocking; |
65 | | noblockOpt.value.non_blocking = true; |
66 | | |
67 | | listener = PR_OpenTCPSocket(PR_AF_INET); |
68 | | if (!listener) { |
69 | | goto failed; |
70 | | } |
71 | | |
72 | | if (aSetRecvBuff) { |
73 | | PR_SetSocketOption(listener, &recvBufferOpt); |
74 | | } |
75 | | PR_SetSocketOption(listener, &nodelayOpt); |
76 | | |
77 | | PRNetAddr listenAddr; |
78 | | memset(&listenAddr, 0, sizeof(listenAddr)); |
79 | | if ((PR_InitializeNetAddr(PR_IpAddrLoopback, 0, &listenAddr) == PR_FAILURE) || |
80 | | (PR_Bind(listener, &listenAddr) == PR_FAILURE) || |
81 | | (PR_GetSockName(listener, &listenAddr) == PR_FAILURE) || // learn the dynamic port |
82 | | (PR_Listen(listener, 5) == PR_FAILURE)) { |
83 | | goto failed; |
84 | | } |
85 | | |
86 | | writer = PR_OpenTCPSocket(PR_AF_INET); |
87 | | if (!writer) { |
88 | | goto failed; |
89 | | } |
90 | | if (aSetRecvBuff) { |
91 | | PR_SetSocketOption(writer, &recvBufferOpt); |
92 | | } |
93 | | PR_SetSocketOption(writer, &nodelayOpt); |
94 | | PR_SetSocketOption(writer, &noblockOpt); |
95 | | PRNetAddr writerAddr; |
96 | | if (PR_InitializeNetAddr(PR_IpAddrLoopback, ntohs(listenAddr.inet.port), &writerAddr) == PR_FAILURE) { |
97 | | goto failed; |
98 | | } |
99 | | |
100 | | if (PR_Connect(writer, &writerAddr, PR_INTERVAL_NO_TIMEOUT) == PR_FAILURE) { |
101 | | if ((PR_GetError() != PR_IN_PROGRESS_ERROR) || |
102 | | (PR_ConnectContinue(writer, PR_POLL_WRITE) == PR_FAILURE)) { |
103 | | goto failed; |
104 | | } |
105 | | } |
106 | | PR_SetFDInheritable(writer, false); |
107 | | |
108 | | reader = PR_Accept(listener, &listenAddr, PR_MillisecondsToInterval(200)); |
109 | | if (!reader) { |
110 | | goto failed; |
111 | | } |
112 | | PR_SetFDInheritable(reader, false); |
113 | | if (aSetRecvBuff) { |
114 | | PR_SetSocketOption(reader, &recvBufferOpt); |
115 | | } |
116 | | PR_SetSocketOption(reader, &nodelayOpt); |
117 | | PR_SetSocketOption(reader, &noblockOpt); |
118 | | PR_Close(listener); |
119 | | |
120 | | fd[0] = reader; |
121 | | fd[1] = writer; |
122 | | return true; |
123 | | |
124 | | failed: |
125 | | if (listener) { |
126 | | PR_Close(listener); |
127 | | } |
128 | | if (reader) { |
129 | | PR_Close(reader); |
130 | | } |
131 | | if (writer) { |
132 | | PR_Close(writer); |
133 | | } |
134 | | return false; |
135 | | } |
136 | | |
137 | | #endif |
138 | | |
139 | | PollableEvent::PollableEvent() |
140 | | : mWriteFD(nullptr) |
141 | | , mReadFD(nullptr) |
142 | | , mSignaled(false) |
143 | | , mWriteFailed(false) |
144 | | , mSignalTimestampAdjusted(false) |
145 | 3 | { |
146 | 3 | MOZ_COUNT_CTOR(PollableEvent); |
147 | 3 | MOZ_ASSERT(OnSocketThread(), "not on socket thread"); |
148 | 3 | // create pair of prfiledesc that can be used as a poll()ble |
149 | 3 | // signal. on windows use a localhost socket pair, and on |
150 | 3 | // unix use a pipe. |
151 | 3 | #ifdef USEPIPE |
152 | 3 | SOCKET_LOG(("PollableEvent() using pipe\n")); |
153 | 3 | if (PR_CreatePipe(&mReadFD, &mWriteFD) == PR_SUCCESS) { |
154 | 3 | // make the pipe non blocking. NSPR asserts at |
155 | 3 | // trying to use SockOpt here |
156 | 3 | PROsfd fd = PR_FileDesc2NativeHandle(mReadFD); |
157 | 3 | int flags = fcntl(fd, F_GETFL, 0); |
158 | 3 | (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK); |
159 | 3 | fd = PR_FileDesc2NativeHandle(mWriteFD); |
160 | 3 | flags = fcntl(fd, F_GETFL, 0); |
161 | 3 | (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK); |
162 | 3 | } else { |
163 | 0 | mReadFD = nullptr; |
164 | 0 | mWriteFD = nullptr; |
165 | 0 | SOCKET_LOG(("PollableEvent() pipe failed\n")); |
166 | 0 | } |
167 | | #else |
168 | | SOCKET_LOG(("PollableEvent() using socket pair\n")); |
169 | | PRFileDesc *fd[2]; |
170 | | LazyInitSocket(); |
171 | | |
172 | | // Try with a increased recv buffer first (bug 1248358). |
173 | | if (NewTCPSocketPair(fd, true)) { |
174 | | mReadFD = fd[0]; |
175 | | mWriteFD = fd[1]; |
176 | | // If the previous fails try without recv buffer increase (bug 1305436). |
177 | | } else if (NewTCPSocketPair(fd, false)) { |
178 | | mReadFD = fd[0]; |
179 | | mWriteFD = fd[1]; |
180 | | // If both fail, try the old version. |
181 | | } else if (PR_NewTCPSocketPair(fd) == PR_SUCCESS) { |
182 | | mReadFD = fd[0]; |
183 | | mWriteFD = fd[1]; |
184 | | |
185 | | PRSocketOptionData socket_opt; |
186 | | DebugOnly<PRStatus> status; |
187 | | socket_opt.option = PR_SockOpt_NoDelay; |
188 | | socket_opt.value.no_delay = true; |
189 | | PR_SetSocketOption(mWriteFD, &socket_opt); |
190 | | PR_SetSocketOption(mReadFD, &socket_opt); |
191 | | socket_opt.option = PR_SockOpt_Nonblocking; |
192 | | socket_opt.value.non_blocking = true; |
193 | | status = PR_SetSocketOption(mWriteFD, &socket_opt); |
194 | | MOZ_ASSERT(status == PR_SUCCESS); |
195 | | status = PR_SetSocketOption(mReadFD, &socket_opt); |
196 | | MOZ_ASSERT(status == PR_SUCCESS); |
197 | | } |
198 | | |
199 | | if (mReadFD && mWriteFD) { |
200 | | // compatibility with LSPs such as McAfee that assume a NSPR |
201 | | // layer for read ala the nspr Pollable Event - Bug 698882. This layer is a nop. |
202 | | PRFileDesc *topLayer = |
203 | | PR_CreateIOLayerStub(sPollableEventLayerIdentity, |
204 | | sPollableEventLayerMethodsPtr); |
205 | | if (topLayer) { |
206 | | if (PR_PushIOLayer(fd[0], PR_TOP_IO_LAYER, topLayer) == PR_FAILURE) { |
207 | | topLayer->dtor(topLayer); |
208 | | } else { |
209 | | SOCKET_LOG(("PollableEvent() nspr layer ok\n")); |
210 | | mReadFD = topLayer; |
211 | | } |
212 | | } |
213 | | |
214 | | } else { |
215 | | SOCKET_LOG(("PollableEvent() socketpair failed\n")); |
216 | | } |
217 | | #endif |
218 | | |
219 | 3 | if (mReadFD && mWriteFD) { |
220 | 3 | // prime the system to deal with races invovled in [dc]tor cycle |
221 | 3 | SOCKET_LOG(("PollableEvent() ctor ok\n")); |
222 | 3 | mSignaled = true; |
223 | 3 | MarkFirstSignalTimestamp(); |
224 | 3 | PR_Write(mWriteFD, "I", 1); |
225 | 3 | } |
226 | 3 | } |
227 | | |
228 | | PollableEvent::~PollableEvent() |
229 | 0 | { |
230 | 0 | MOZ_COUNT_DTOR(PollableEvent); |
231 | 0 | if (mWriteFD) { |
232 | | #if defined(XP_WIN) |
233 | | AttachShutdownLayer(mWriteFD); |
234 | | #endif |
235 | | PR_Close(mWriteFD); |
236 | 0 | } |
237 | 0 | if (mReadFD) { |
238 | | #if defined(XP_WIN) |
239 | | AttachShutdownLayer(mReadFD); |
240 | | #endif |
241 | | PR_Close(mReadFD); |
242 | 0 | } |
243 | 0 | } |
244 | | |
245 | | // we do not record signals on the socket thread |
246 | | // because the socket thread can reliably look at its |
247 | | // own runnable queue before selecting a poll time |
248 | | // this is the "service the network without blocking" comment in |
249 | | // nsSocketTransportService2.cpp |
250 | | bool |
251 | | PollableEvent::Signal() |
252 | 1 | { |
253 | 1 | SOCKET_LOG(("PollableEvent::Signal\n")); |
254 | 1 | |
255 | 1 | if (!mWriteFD) { |
256 | 0 | SOCKET_LOG(("PollableEvent::Signal Failed on no FD\n")); |
257 | 0 | return false; |
258 | 0 | } |
259 | 1 | #ifndef XP_WIN |
260 | 1 | // On windows poll can hang and this became worse when we introduced the |
261 | 1 | // patch for bug 698882 (see also bug 1292181), therefore we reverted the |
262 | 1 | // behavior on windows to be as before bug 698882, e.g. write to the socket |
263 | 1 | // also if an event dispatch is on the socket thread and writing to the |
264 | 1 | // socket for each event. See bug 1292181. |
265 | 1 | if (OnSocketThread()) { |
266 | 0 | SOCKET_LOG(("PollableEvent::Signal OnSocketThread nop\n")); |
267 | 0 | return true; |
268 | 0 | } |
269 | 1 | #endif |
270 | 1 | |
271 | 1 | #ifndef XP_WIN |
272 | 1 | // To wake up the poll writing once is enough, but for Windows that can cause |
273 | 1 | // hangs so we will write for every event. |
274 | 1 | // For non-Windows systems it is enough to write just once. |
275 | 1 | if (mSignaled) { |
276 | 0 | return true; |
277 | 0 | } |
278 | 1 | #endif |
279 | 1 | |
280 | 1 | if (!mSignaled) { |
281 | 1 | mSignaled = true; |
282 | 1 | MarkFirstSignalTimestamp(); |
283 | 1 | } |
284 | 1 | |
285 | 1 | int32_t status = PR_Write(mWriteFD, "M", 1); |
286 | 1 | SOCKET_LOG(("PollableEvent::Signal PR_Write %d\n", status)); |
287 | 1 | if (status != 1) { |
288 | 0 | NS_WARNING("PollableEvent::Signal Failed\n"); |
289 | 0 | SOCKET_LOG(("PollableEvent::Signal Failed\n")); |
290 | 0 | mSignaled = false; |
291 | 0 | mWriteFailed = true; |
292 | 1 | } else { |
293 | 1 | mWriteFailed = false; |
294 | 1 | } |
295 | 1 | return (status == 1); |
296 | 1 | } |
297 | | |
298 | | bool |
299 | | PollableEvent::Clear() |
300 | 4 | { |
301 | 4 | // necessary because of the "dont signal on socket thread" optimization |
302 | 4 | MOZ_ASSERT(OnSocketThread(), "not on socket thread"); |
303 | 4 | |
304 | 4 | SOCKET_LOG(("PollableEvent::Clear\n")); |
305 | 4 | |
306 | 4 | if (!mFirstSignalAfterClear.IsNull()) { |
307 | 4 | SOCKET_LOG(("PollableEvent::Clear time to signal %ums", |
308 | 4 | (uint32_t)(TimeStamp::NowLoRes() - mFirstSignalAfterClear).ToMilliseconds())); |
309 | 4 | } |
310 | 4 | |
311 | 4 | mFirstSignalAfterClear = TimeStamp(); |
312 | 4 | mSignalTimestampAdjusted = false; |
313 | 4 | mSignaled = false; |
314 | 4 | |
315 | 4 | if (!mReadFD) { |
316 | 0 | SOCKET_LOG(("PollableEvent::Clear mReadFD is null\n")); |
317 | 0 | return false; |
318 | 0 | } |
319 | 4 | |
320 | 4 | char buf[2048]; |
321 | 4 | int32_t status; |
322 | | #ifdef XP_WIN |
323 | | // On Windows we are writing to the socket for each event, to be sure that we |
324 | | // do not have any deadlock read from the socket as much as we can. |
325 | | while (true) { |
326 | | status = PR_Read(mReadFD, buf, 2048); |
327 | | SOCKET_LOG(("PollableEvent::Clear PR_Read %d\n", status)); |
328 | | if (status == 0) { |
329 | | SOCKET_LOG(("PollableEvent::Clear EOF!\n")); |
330 | | return false; |
331 | | } |
332 | | if (status < 0) { |
333 | | PRErrorCode code = PR_GetError(); |
334 | | if (code == PR_WOULD_BLOCK_ERROR) { |
335 | | return true; |
336 | | } else { |
337 | | SOCKET_LOG(("PollableEvent::Clear unexpected error %d\n", code)); |
338 | | return false; |
339 | | } |
340 | | } |
341 | | } |
342 | | #else |
343 | | status = PR_Read(mReadFD, buf, 2048); |
344 | 4 | SOCKET_LOG(("PollableEvent::Clear PR_Read %d\n", status)); |
345 | 4 | |
346 | 4 | if (status == 1) { |
347 | 4 | return true; |
348 | 4 | } |
349 | 0 | if (status == 0) { |
350 | 0 | SOCKET_LOG(("PollableEvent::Clear EOF!\n")); |
351 | 0 | return false; |
352 | 0 | } |
353 | 0 | if (status > 1) { |
354 | 0 | MOZ_ASSERT(false); |
355 | 0 | SOCKET_LOG(("PollableEvent::Clear Unexpected events\n")); |
356 | 0 | Clear(); |
357 | 0 | return true; |
358 | 0 | } |
359 | 0 | PRErrorCode code = PR_GetError(); |
360 | 0 | if (code == PR_WOULD_BLOCK_ERROR) { |
361 | 0 | return true; |
362 | 0 | } |
363 | 0 | SOCKET_LOG(("PollableEvent::Clear unexpected error %d\n", code)); |
364 | 0 | return false; |
365 | 0 | #endif //XP_WIN |
366 | 0 |
|
367 | 0 | } |
368 | | |
369 | | void |
370 | | PollableEvent::MarkFirstSignalTimestamp() |
371 | 4 | { |
372 | 4 | if (mFirstSignalAfterClear.IsNull()) { |
373 | 4 | SOCKET_LOG(("PollableEvent::MarkFirstSignalTimestamp")); |
374 | 4 | mFirstSignalAfterClear = TimeStamp::NowLoRes(); |
375 | 4 | } |
376 | 4 | } |
377 | | |
378 | | void |
379 | | PollableEvent::AdjustFirstSignalTimestamp() |
380 | 7 | { |
381 | 7 | if (!mSignalTimestampAdjusted && !mFirstSignalAfterClear.IsNull()) { |
382 | 3 | SOCKET_LOG(("PollableEvent::AdjustFirstSignalTimestamp")); |
383 | 3 | mFirstSignalAfterClear = TimeStamp::NowLoRes(); |
384 | 3 | mSignalTimestampAdjusted = true; |
385 | 3 | } |
386 | 7 | } |
387 | | |
388 | | bool |
389 | | PollableEvent::IsSignallingAlive(TimeDuration const& timeout) |
390 | 4 | { |
391 | 4 | if (mWriteFailed) { |
392 | 0 | return false; |
393 | 0 | } |
394 | 4 | |
395 | | #ifdef DEBUG |
396 | | // The timeout would be just a disturbance in a debug build. |
397 | | return true; |
398 | | #else |
399 | 4 | if (!mSignaled || mFirstSignalAfterClear.IsNull() || timeout == TimeDuration()) { |
400 | 4 | return true; |
401 | 4 | } |
402 | 0 | |
403 | 0 | TimeDuration delay = (TimeStamp::NowLoRes() - mFirstSignalAfterClear); |
404 | 0 | bool timedOut = delay > timeout; |
405 | 0 |
|
406 | 0 | return !timedOut; |
407 | 0 | #endif // DEBUG |
408 | 0 | } |
409 | | |
410 | | } // namespace net |
411 | | } // namespace mozilla |