Merge mozilla-central to autoland. CLOSED TREE
[gecko.git] / netwerk / base / PollableEvent.cpp
bloba99d6d88f5f1fb85a068b5170de339b36e6d25d6
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/. */
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 "mozilla/net/DNS.h"
13 #include "prerror.h"
14 #include "prio.h"
15 #include "private/pprio.h"
16 #include "prnetdb.h"
18 #ifdef XP_WIN
19 # include "ShutdownLayer.h"
20 #else
21 # include <fcntl.h>
22 # define USEPIPE 1
23 #endif
25 namespace mozilla {
26 namespace net {
28 #ifndef USEPIPE
29 static PRDescIdentity sPollableEventLayerIdentity;
30 static PRIOMethods sPollableEventLayerMethods;
31 static PRIOMethods* sPollableEventLayerMethodsPtr = nullptr;
33 static void LazyInitSocket() {
34 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
35 if (sPollableEventLayerMethodsPtr) {
36 return;
38 sPollableEventLayerIdentity = PR_GetUniqueIdentity("PollableEvent Layer");
39 sPollableEventLayerMethods = *PR_GetDefaultIOMethods();
40 sPollableEventLayerMethodsPtr = &sPollableEventLayerMethods;
43 static bool NewTCPSocketPair(PRFileDesc* fd[], bool aSetRecvBuff) {
44 // this is a replacement for PR_NewTCPSocketPair that manually
45 // sets the recv buffer to 64K. A windows bug (1248358)
46 // can result in using an incompatible rwin and window
47 // scale option on localhost pipes if not set before connect.
49 SOCKET_LOG(("NewTCPSocketPair %s a recv buffer tuning\n",
50 aSetRecvBuff ? "with" : "without"));
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;
59 PRSocketOptionData nodelayOpt;
60 nodelayOpt.option = PR_SockOpt_NoDelay;
61 nodelayOpt.value.no_delay = true;
63 PRSocketOptionData noblockOpt;
64 noblockOpt.option = PR_SockOpt_Nonblocking;
65 noblockOpt.value.non_blocking = true;
67 listener = PR_OpenTCPSocket(PR_AF_INET);
68 if (!listener) {
69 goto failed;
72 if (aSetRecvBuff) {
73 PR_SetSocketOption(listener, &recvBufferOpt);
75 PR_SetSocketOption(listener, &nodelayOpt);
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) ==
82 PR_FAILURE) || // learn the dynamic port
83 (PR_Listen(listener, 5) == PR_FAILURE)) {
84 goto failed;
87 writer = PR_OpenTCPSocket(PR_AF_INET);
88 if (!writer) {
89 goto failed;
91 if (aSetRecvBuff) {
92 PR_SetSocketOption(writer, &recvBufferOpt);
94 PR_SetSocketOption(writer, &nodelayOpt);
95 PR_SetSocketOption(writer, &noblockOpt);
96 PRNetAddr writerAddr;
97 if (PR_InitializeNetAddr(PR_IpAddrLoopback, ntohs(listenAddr.inet.port),
98 &writerAddr) == PR_FAILURE) {
99 goto failed;
102 if (PR_Connect(writer, &writerAddr, PR_INTERVAL_NO_TIMEOUT) == PR_FAILURE) {
103 if ((PR_GetError() != PR_IN_PROGRESS_ERROR) ||
104 (PR_ConnectContinue(writer, PR_POLL_WRITE) == PR_FAILURE)) {
105 goto failed;
108 PR_SetFDInheritable(writer, false);
110 reader = PR_Accept(listener, &listenAddr, PR_MillisecondsToInterval(200));
111 if (!reader) {
112 goto failed;
114 PR_SetFDInheritable(reader, false);
115 if (aSetRecvBuff) {
116 PR_SetSocketOption(reader, &recvBufferOpt);
118 PR_SetSocketOption(reader, &nodelayOpt);
119 PR_SetSocketOption(reader, &noblockOpt);
120 PR_Close(listener);
122 fd[0] = reader;
123 fd[1] = writer;
124 return true;
126 failed:
127 if (listener) {
128 PR_Close(listener);
130 if (reader) {
131 PR_Close(reader);
133 if (writer) {
134 PR_Close(writer);
136 return false;
139 #endif
141 PollableEvent::PollableEvent()
144 MOZ_COUNT_CTOR(PollableEvent);
145 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
146 // create pair of prfiledesc that can be used as a poll()ble
147 // signal. on windows use a localhost socket pair, and on
148 // unix use a pipe.
149 #ifdef USEPIPE
150 SOCKET_LOG(("PollableEvent() using pipe\n"));
151 if (PR_CreatePipe(&mReadFD, &mWriteFD) == PR_SUCCESS) {
152 // make the pipe non blocking. NSPR asserts at
153 // trying to use SockOpt here
154 PROsfd fd = PR_FileDesc2NativeHandle(mReadFD);
155 int flags = fcntl(fd, F_GETFL, 0);
156 (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK);
157 fd = PR_FileDesc2NativeHandle(mWriteFD);
158 flags = fcntl(fd, F_GETFL, 0);
159 (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK);
160 } else {
161 mReadFD = nullptr;
162 mWriteFD = nullptr;
163 SOCKET_LOG(("PollableEvent() pipe failed\n"));
165 #else
166 SOCKET_LOG(("PollableEvent() using socket pair\n"));
167 PRFileDesc* fd[2];
168 LazyInitSocket();
170 // Try with a increased recv buffer first (bug 1248358).
171 if (NewTCPSocketPair(fd, true)) {
172 mReadFD = fd[0];
173 mWriteFD = fd[1];
174 // If the previous fails try without recv buffer increase (bug 1305436).
175 } else if (NewTCPSocketPair(fd, false)) {
176 mReadFD = fd[0];
177 mWriteFD = fd[1];
178 // If both fail, try the old version.
179 } else if (PR_NewTCPSocketPair(fd) == PR_SUCCESS) {
180 mReadFD = fd[0];
181 mWriteFD = fd[1];
183 PRSocketOptionData socket_opt;
184 DebugOnly<PRStatus> status;
185 socket_opt.option = PR_SockOpt_NoDelay;
186 socket_opt.value.no_delay = true;
187 PR_SetSocketOption(mWriteFD, &socket_opt);
188 PR_SetSocketOption(mReadFD, &socket_opt);
189 socket_opt.option = PR_SockOpt_Nonblocking;
190 socket_opt.value.non_blocking = true;
191 status = PR_SetSocketOption(mWriteFD, &socket_opt);
192 MOZ_ASSERT(status == PR_SUCCESS);
193 status = PR_SetSocketOption(mReadFD, &socket_opt);
194 MOZ_ASSERT(status == PR_SUCCESS);
197 if (mReadFD && mWriteFD) {
198 // compatibility with LSPs such as McAfee that assume a NSPR
199 // layer for read ala the nspr Pollable Event - Bug 698882. This layer is a
200 // nop.
201 PRFileDesc* topLayer = PR_CreateIOLayerStub(sPollableEventLayerIdentity,
202 sPollableEventLayerMethodsPtr);
203 if (topLayer) {
204 if (PR_PushIOLayer(fd[0], PR_TOP_IO_LAYER, topLayer) == PR_FAILURE) {
205 topLayer->dtor(topLayer);
206 } else {
207 SOCKET_LOG(("PollableEvent() nspr layer ok\n"));
208 mReadFD = topLayer;
212 } else {
213 SOCKET_LOG(("PollableEvent() socketpair failed\n"));
215 #endif
217 if (mReadFD && mWriteFD) {
218 // prime the system to deal with races invovled in [dc]tor cycle
219 SOCKET_LOG(("PollableEvent() ctor ok\n"));
220 mSignaled = true;
221 MarkFirstSignalTimestamp();
222 PR_Write(mWriteFD, "I", 1);
226 PollableEvent::~PollableEvent() {
227 MOZ_COUNT_DTOR(PollableEvent);
228 if (mWriteFD) {
229 #if defined(XP_WIN)
230 AttachShutdownLayer(mWriteFD);
231 #endif
232 PR_Close(mWriteFD);
234 if (mReadFD) {
235 #if defined(XP_WIN)
236 AttachShutdownLayer(mReadFD);
237 #endif
238 PR_Close(mReadFD);
242 // we do not record signals on the socket thread
243 // because the socket thread can reliably look at its
244 // own runnable queue before selecting a poll time
245 // this is the "service the network without blocking" comment in
246 // nsSocketTransportService2.cpp
247 bool PollableEvent::Signal() {
248 SOCKET_LOG(("PollableEvent::Signal\n"));
250 if (!mWriteFD) {
251 SOCKET_LOG(("PollableEvent::Signal Failed on no FD\n"));
252 return false;
254 #ifndef XP_WIN
255 // On windows poll can hang and this became worse when we introduced the
256 // patch for bug 698882 (see also bug 1292181), therefore we reverted the
257 // behavior on windows to be as before bug 698882, e.g. write to the socket
258 // also if an event dispatch is on the socket thread and writing to the
259 // socket for each event. See bug 1292181.
260 if (OnSocketThread()) {
261 SOCKET_LOG(("PollableEvent::Signal OnSocketThread nop\n"));
262 return true;
264 #endif
266 #ifndef XP_WIN
267 // To wake up the poll writing once is enough, but for Windows that can cause
268 // hangs so we will write for every event.
269 // For non-Windows systems it is enough to write just once.
270 if (mSignaled) {
271 return true;
273 #endif
275 if (!mSignaled) {
276 mSignaled = true;
277 MarkFirstSignalTimestamp();
280 int32_t status = PR_Write(mWriteFD, "M", 1);
281 SOCKET_LOG(("PollableEvent::Signal PR_Write %d\n", status));
282 if (status != 1) {
283 NS_WARNING("PollableEvent::Signal Failed\n");
284 SOCKET_LOG(("PollableEvent::Signal Failed\n"));
285 mSignaled = false;
286 mWriteFailed = true;
287 } else {
288 mWriteFailed = false;
290 return (status == 1);
293 bool PollableEvent::Clear() {
294 // necessary because of the "dont signal on socket thread" optimization
295 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
297 SOCKET_LOG(("PollableEvent::Clear\n"));
299 if (!mFirstSignalAfterClear.IsNull()) {
300 SOCKET_LOG(("PollableEvent::Clear time to signal %ums",
301 (uint32_t)(TimeStamp::NowLoRes() - mFirstSignalAfterClear)
302 .ToMilliseconds()));
305 mFirstSignalAfterClear = TimeStamp();
306 mSignalTimestampAdjusted = false;
307 mSignaled = false;
309 if (!mReadFD) {
310 SOCKET_LOG(("PollableEvent::Clear mReadFD is null\n"));
311 return false;
314 char buf[2048];
315 int32_t status;
316 #ifdef XP_WIN
317 // On Windows we are writing to the socket for each event, to be sure that we
318 // do not have any deadlock read from the socket as much as we can.
319 while (true) {
320 status = PR_Read(mReadFD, buf, 2048);
321 SOCKET_LOG(("PollableEvent::Clear PR_Read %d\n", status));
322 if (status == 0) {
323 SOCKET_LOG(("PollableEvent::Clear EOF!\n"));
324 return false;
326 if (status < 0) {
327 PRErrorCode code = PR_GetError();
328 if (code == PR_WOULD_BLOCK_ERROR) {
329 return true;
330 } else {
331 SOCKET_LOG(("PollableEvent::Clear unexpected error %d\n", code));
332 return false;
336 #else
337 status = PR_Read(mReadFD, buf, 2048);
338 SOCKET_LOG(("PollableEvent::Clear PR_Read %d\n", status));
340 if (status == 1) {
341 return true;
343 if (status == 0) {
344 SOCKET_LOG(("PollableEvent::Clear EOF!\n"));
345 return false;
347 if (status > 1) {
348 MOZ_ASSERT(false);
349 SOCKET_LOG(("PollableEvent::Clear Unexpected events\n"));
350 Clear();
351 return true;
353 PRErrorCode code = PR_GetError();
354 if (code == PR_WOULD_BLOCK_ERROR) {
355 return true;
357 SOCKET_LOG(("PollableEvent::Clear unexpected error %d\n", code));
358 return false;
359 #endif // XP_WIN
362 void PollableEvent::MarkFirstSignalTimestamp() {
363 if (mFirstSignalAfterClear.IsNull()) {
364 SOCKET_LOG(("PollableEvent::MarkFirstSignalTimestamp"));
365 mFirstSignalAfterClear = TimeStamp::NowLoRes();
369 void PollableEvent::AdjustFirstSignalTimestamp() {
370 if (!mSignalTimestampAdjusted && !mFirstSignalAfterClear.IsNull()) {
371 SOCKET_LOG(("PollableEvent::AdjustFirstSignalTimestamp"));
372 mFirstSignalAfterClear = TimeStamp::NowLoRes();
373 mSignalTimestampAdjusted = true;
377 bool PollableEvent::IsSignallingAlive(TimeDuration const& timeout) {
378 if (mWriteFailed) {
379 return false;
382 #ifdef DEBUG
383 // The timeout would be just a disturbance in a debug build.
384 return true;
385 #else
386 if (!mSignaled || mFirstSignalAfterClear.IsNull() ||
387 timeout == TimeDuration()) {
388 return true;
391 TimeDuration delay = (TimeStamp::NowLoRes() - mFirstSignalAfterClear);
392 bool timedOut = delay > timeout;
394 return !timedOut;
395 #endif // DEBUG
398 } // namespace net
399 } // namespace mozilla