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"
15 #include "private/pprio.h"
19 # include "ShutdownLayer.h"
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
) {
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
);
73 PR_SetSocketOption(listener
, &recvBufferOpt
);
75 PR_SetSocketOption(listener
, &nodelayOpt
);
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
)) {
87 writer
= PR_OpenTCPSocket(PR_AF_INET
);
92 PR_SetSocketOption(writer
, &recvBufferOpt
);
94 PR_SetSocketOption(writer
, &nodelayOpt
);
95 PR_SetSocketOption(writer
, &noblockOpt
);
97 if (PR_InitializeNetAddr(PR_IpAddrLoopback
, ntohs(listenAddr
.inet
.port
),
98 &writerAddr
) == PR_FAILURE
) {
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
)) {
108 PR_SetFDInheritable(writer
, false);
110 reader
= PR_Accept(listener
, &listenAddr
, PR_MillisecondsToInterval(200));
114 PR_SetFDInheritable(reader
, false);
116 PR_SetSocketOption(reader
, &recvBufferOpt
);
118 PR_SetSocketOption(reader
, &nodelayOpt
);
119 PR_SetSocketOption(reader
, &noblockOpt
);
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
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
);
163 SOCKET_LOG(("PollableEvent() pipe failed\n"));
166 SOCKET_LOG(("PollableEvent() using socket pair\n"));
170 // Try with a increased recv buffer first (bug 1248358).
171 if (NewTCPSocketPair(fd
, true)) {
174 // If the previous fails try without recv buffer increase (bug 1305436).
175 } else if (NewTCPSocketPair(fd
, false)) {
178 // If both fail, try the old version.
179 } else if (PR_NewTCPSocketPair(fd
) == PR_SUCCESS
) {
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
201 PRFileDesc
* topLayer
= PR_CreateIOLayerStub(sPollableEventLayerIdentity
,
202 sPollableEventLayerMethodsPtr
);
204 if (PR_PushIOLayer(fd
[0], PR_TOP_IO_LAYER
, topLayer
) == PR_FAILURE
) {
205 topLayer
->dtor(topLayer
);
207 SOCKET_LOG(("PollableEvent() nspr layer ok\n"));
213 SOCKET_LOG(("PollableEvent() socketpair failed\n"));
217 if (mReadFD
&& mWriteFD
) {
218 // prime the system to deal with races invovled in [dc]tor cycle
219 SOCKET_LOG(("PollableEvent() ctor ok\n"));
221 MarkFirstSignalTimestamp();
222 PR_Write(mWriteFD
, "I", 1);
226 PollableEvent::~PollableEvent() {
227 MOZ_COUNT_DTOR(PollableEvent
);
230 AttachShutdownLayer(mWriteFD
);
236 AttachShutdownLayer(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"));
251 SOCKET_LOG(("PollableEvent::Signal Failed on no FD\n"));
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"));
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.
277 MarkFirstSignalTimestamp();
280 int32_t status
= PR_Write(mWriteFD
, "M", 1);
281 SOCKET_LOG(("PollableEvent::Signal PR_Write %d\n", status
));
283 NS_WARNING("PollableEvent::Signal Failed\n");
284 SOCKET_LOG(("PollableEvent::Signal Failed\n"));
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
)
305 mFirstSignalAfterClear
= TimeStamp();
306 mSignalTimestampAdjusted
= false;
310 SOCKET_LOG(("PollableEvent::Clear mReadFD is null\n"));
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.
320 status
= PR_Read(mReadFD
, buf
, 2048);
321 SOCKET_LOG(("PollableEvent::Clear PR_Read %d\n", status
));
323 SOCKET_LOG(("PollableEvent::Clear EOF!\n"));
327 PRErrorCode code
= PR_GetError();
328 if (code
== PR_WOULD_BLOCK_ERROR
) {
331 SOCKET_LOG(("PollableEvent::Clear unexpected error %d\n", code
));
337 status
= PR_Read(mReadFD
, buf
, 2048);
338 SOCKET_LOG(("PollableEvent::Clear PR_Read %d\n", status
));
344 SOCKET_LOG(("PollableEvent::Clear EOF!\n"));
349 SOCKET_LOG(("PollableEvent::Clear Unexpected events\n"));
353 PRErrorCode code
= PR_GetError();
354 if (code
== PR_WOULD_BLOCK_ERROR
) {
357 SOCKET_LOG(("PollableEvent::Clear unexpected error %d\n", code
));
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
) {
383 // The timeout would be just a disturbance in a debug build.
386 if (!mSignaled
|| mFirstSignalAfterClear
.IsNull() ||
387 timeout
== TimeDuration()) {
391 TimeDuration delay
= (TimeStamp::NowLoRes() - mFirstSignalAfterClear
);
392 bool timedOut
= delay
> timeout
;
399 } // namespace mozilla