Line data Source code
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 :
107 : reader = PR_Accept(listener, &listenAddr, PR_MillisecondsToInterval(200));
108 : if (!reader) {
109 : goto failed;
110 : }
111 : if (aSetRecvBuff) {
112 : PR_SetSocketOption(reader, &recvBufferOpt);
113 : }
114 : PR_SetSocketOption(reader, &nodelayOpt);
115 : PR_SetSocketOption(reader, &noblockOpt);
116 : PR_Close(listener);
117 :
118 : fd[0] = reader;
119 : fd[1] = writer;
120 : return true;
121 :
122 : failed:
123 : if (listener) {
124 : PR_Close(listener);
125 : }
126 : if (reader) {
127 : PR_Close(reader);
128 : }
129 : if (writer) {
130 : PR_Close(writer);
131 : }
132 : return false;
133 : }
134 :
135 : #endif
136 :
137 3 : PollableEvent::PollableEvent()
138 : : mWriteFD(nullptr)
139 : , mReadFD(nullptr)
140 3 : , mSignaled(false)
141 : {
142 3 : MOZ_COUNT_CTOR(PollableEvent);
143 3 : MOZ_ASSERT(OnSocketThread(), "not on socket thread");
144 : // create pair of prfiledesc that can be used as a poll()ble
145 : // signal. on windows use a localhost socket pair, and on
146 : // unix use a pipe.
147 : #ifdef USEPIPE
148 3 : SOCKET_LOG(("PollableEvent() using pipe\n"));
149 3 : if (PR_CreatePipe(&mReadFD, &mWriteFD) == PR_SUCCESS) {
150 : // make the pipe non blocking. NSPR asserts at
151 : // trying to use SockOpt here
152 3 : PROsfd fd = PR_FileDesc2NativeHandle(mReadFD);
153 3 : int flags = fcntl(fd, F_GETFL, 0);
154 3 : (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK);
155 3 : fd = PR_FileDesc2NativeHandle(mWriteFD);
156 3 : flags = fcntl(fd, F_GETFL, 0);
157 3 : (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK);
158 : } else {
159 0 : mReadFD = nullptr;
160 0 : mWriteFD = nullptr;
161 0 : SOCKET_LOG(("PollableEvent() pipe failed\n"));
162 : }
163 : #else
164 : SOCKET_LOG(("PollableEvent() using socket pair\n"));
165 : PRFileDesc *fd[2];
166 : LazyInitSocket();
167 :
168 : // Try with a increased recv buffer first (bug 1248358).
169 : if (NewTCPSocketPair(fd, true)) {
170 : mReadFD = fd[0];
171 : mWriteFD = fd[1];
172 : // If the previous fails try without recv buffer increase (bug 1305436).
173 : } else if (NewTCPSocketPair(fd, false)) {
174 : mReadFD = fd[0];
175 : mWriteFD = fd[1];
176 : // If both fail, try the old version.
177 : } else if (PR_NewTCPSocketPair(fd) == PR_SUCCESS) {
178 : mReadFD = fd[0];
179 : mWriteFD = fd[1];
180 :
181 : PRSocketOptionData socket_opt;
182 : DebugOnly<PRStatus> status;
183 : socket_opt.option = PR_SockOpt_NoDelay;
184 : socket_opt.value.no_delay = true;
185 : PR_SetSocketOption(mWriteFD, &socket_opt);
186 : PR_SetSocketOption(mReadFD, &socket_opt);
187 : socket_opt.option = PR_SockOpt_Nonblocking;
188 : socket_opt.value.non_blocking = true;
189 : status = PR_SetSocketOption(mWriteFD, &socket_opt);
190 : MOZ_ASSERT(status == PR_SUCCESS);
191 : status = PR_SetSocketOption(mReadFD, &socket_opt);
192 : MOZ_ASSERT(status == PR_SUCCESS);
193 : }
194 :
195 : if (mReadFD && mWriteFD) {
196 : // compatibility with LSPs such as McAfee that assume a NSPR
197 : // layer for read ala the nspr Pollable Event - Bug 698882. This layer is a nop.
198 : PRFileDesc *topLayer =
199 : PR_CreateIOLayerStub(sPollableEventLayerIdentity,
200 : sPollableEventLayerMethodsPtr);
201 : if (topLayer) {
202 : if (PR_PushIOLayer(fd[0], PR_TOP_IO_LAYER, topLayer) == PR_FAILURE) {
203 : topLayer->dtor(topLayer);
204 : } else {
205 : SOCKET_LOG(("PollableEvent() nspr layer ok\n"));
206 : mReadFD = topLayer;
207 : }
208 : }
209 :
210 : } else {
211 : SOCKET_LOG(("PollableEvent() socketpair failed\n"));
212 : }
213 : #endif
214 :
215 3 : if (mReadFD && mWriteFD) {
216 : // prime the system to deal with races invovled in [dc]tor cycle
217 3 : SOCKET_LOG(("PollableEvent() ctor ok\n"));
218 3 : mSignaled = true;
219 3 : PR_Write(mWriteFD, "I", 1);
220 : }
221 3 : }
222 :
223 0 : PollableEvent::~PollableEvent()
224 : {
225 0 : MOZ_COUNT_DTOR(PollableEvent);
226 0 : if (mWriteFD) {
227 : #if defined(XP_WIN)
228 : AttachShutdownLayer(mWriteFD);
229 : #endif
230 0 : PR_Close(mWriteFD);
231 : }
232 0 : if (mReadFD) {
233 : #if defined(XP_WIN)
234 : AttachShutdownLayer(mReadFD);
235 : #endif
236 0 : PR_Close(mReadFD);
237 : }
238 0 : }
239 :
240 : // we do not record signals on the socket thread
241 : // because the socket thread can reliably look at its
242 : // own runnable queue before selecting a poll time
243 : // this is the "service the network without blocking" comment in
244 : // nsSocketTransportService2.cpp
245 : bool
246 43 : PollableEvent::Signal()
247 : {
248 43 : SOCKET_LOG(("PollableEvent::Signal\n"));
249 :
250 43 : if (!mWriteFD) {
251 0 : SOCKET_LOG(("PollableEvent::Signal Failed on no FD\n"));
252 0 : return false;
253 : }
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 43 : if (OnSocketThread()) {
261 0 : SOCKET_LOG(("PollableEvent::Signal OnSocketThread nop\n"));
262 0 : return true;
263 : }
264 : #endif
265 :
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 43 : if (mSignaled) {
271 6 : return true;
272 : }
273 : #endif
274 :
275 37 : mSignaled = true;
276 37 : int32_t status = PR_Write(mWriteFD, "M", 1);
277 37 : SOCKET_LOG(("PollableEvent::Signal PR_Write %d\n", status));
278 37 : if (status != 1) {
279 0 : NS_WARNING("PollableEvent::Signal Failed\n");
280 0 : SOCKET_LOG(("PollableEvent::Signal Failed\n"));
281 0 : mSignaled = false;
282 : }
283 37 : return (status == 1);
284 : }
285 :
286 : bool
287 40 : PollableEvent::Clear()
288 : {
289 : // necessary because of the "dont signal on socket thread" optimization
290 40 : MOZ_ASSERT(OnSocketThread(), "not on socket thread");
291 :
292 40 : SOCKET_LOG(("PollableEvent::Clear\n"));
293 40 : mSignaled = false;
294 40 : if (!mReadFD) {
295 0 : SOCKET_LOG(("PollableEvent::Clear mReadFD is null\n"));
296 0 : return false;
297 : }
298 : char buf[2048];
299 : int32_t status;
300 : #ifdef XP_WIN
301 : // On Windows we are writing to the socket for each event, to be sure that we
302 : // do not have any deadlock read from the socket as much as we can.
303 : while (true) {
304 : status = PR_Read(mReadFD, buf, 2048);
305 : SOCKET_LOG(("PollableEvent::Signal PR_Read %d\n", status));
306 : if (status == 0) {
307 : SOCKET_LOG(("PollableEvent::Clear EOF!\n"));
308 : return false;
309 : }
310 : if (status < 0) {
311 : PRErrorCode code = PR_GetError();
312 : if (code == PR_WOULD_BLOCK_ERROR) {
313 : return true;
314 : } else {
315 : SOCKET_LOG(("PollableEvent::Clear unexpected error %d\n", code));
316 : return false;
317 : }
318 : }
319 : }
320 : #else
321 40 : status = PR_Read(mReadFD, buf, 2048);
322 40 : SOCKET_LOG(("PollableEvent::Signal PR_Read %d\n", status));
323 :
324 40 : if (status == 1) {
325 40 : return true;
326 : }
327 0 : if (status == 0) {
328 0 : SOCKET_LOG(("PollableEvent::Clear EOF!\n"));
329 0 : return false;
330 : }
331 0 : if (status > 1) {
332 0 : MOZ_ASSERT(false);
333 : SOCKET_LOG(("PollableEvent::Clear Unexpected events\n"));
334 : Clear();
335 : return true;
336 : }
337 0 : PRErrorCode code = PR_GetError();
338 0 : if (code == PR_WOULD_BLOCK_ERROR) {
339 0 : return true;
340 : }
341 0 : SOCKET_LOG(("PollableEvent::Clear unexpected error %d\n", code));
342 0 : return false;
343 : #endif //XP_WIN
344 :
345 : }
346 : } // namespace net
347 : } // namespace mozilla
|