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