Coverage Report

Created: 2018-09-25 14:53

/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