Source code

Revision control

Other Tools

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
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
34
if (sPollableEventLayerMethodsPtr) {
35
return;
36
}
37
sPollableEventLayerIdentity = PR_GetUniqueIdentity("PollableEvent Layer");
38
sPollableEventLayerMethods = *PR_GetDefaultIOMethods();
39
sPollableEventLayerMethodsPtr = &sPollableEventLayerMethods;
40
}
41
42
static bool NewTCPSocketPair(PRFileDesc* fd[], bool aSetRecvBuff) {
43
// this is a replacement for PR_NewTCPSocketPair that manually
44
// sets the recv buffer to 64K. A windows bug (1248358)
45
// can result in using an incompatible rwin and window
46
// scale option on localhost pipes if not set before connect.
47
48
SOCKET_LOG(("NewTCPSocketPair %s a recv buffer tuning\n",
49
aSetRecvBuff ? "with" : "without"));
50
51
PRFileDesc* listener = nullptr;
52
PRFileDesc* writer = nullptr;
53
PRFileDesc* reader = nullptr;
54
PRSocketOptionData recvBufferOpt;
55
recvBufferOpt.option = PR_SockOpt_RecvBufferSize;
56
recvBufferOpt.value.recv_buffer_size = 65535;
57
58
PRSocketOptionData nodelayOpt;
59
nodelayOpt.option = PR_SockOpt_NoDelay;
60
nodelayOpt.value.no_delay = true;
61
62
PRSocketOptionData noblockOpt;
63
noblockOpt.option = PR_SockOpt_Nonblocking;
64
noblockOpt.value.non_blocking = true;
65
66
listener = PR_OpenTCPSocket(PR_AF_INET);
67
if (!listener) {
68
goto failed;
69
}
70
71
if (aSetRecvBuff) {
72
PR_SetSocketOption(listener, &recvBufferOpt);
73
}
74
PR_SetSocketOption(listener, &nodelayOpt);
75
76
PRNetAddr listenAddr;
77
memset(&listenAddr, 0, sizeof(listenAddr));
78
if ((PR_InitializeNetAddr(PR_IpAddrLoopback, 0, &listenAddr) == PR_FAILURE) ||
79
(PR_Bind(listener, &listenAddr) == PR_FAILURE) ||
80
(PR_GetSockName(listener, &listenAddr) ==
81
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),
97
&writerAddr) == PR_FAILURE) {
98
goto failed;
99
}
100
101
if (PR_Connect(writer, &writerAddr, PR_INTERVAL_NO_TIMEOUT) == PR_FAILURE) {
102
if ((PR_GetError() != PR_IN_PROGRESS_ERROR) ||
103
(PR_ConnectContinue(writer, PR_POLL_WRITE) == PR_FAILURE)) {
104
goto failed;
105
}
106
}
107
PR_SetFDInheritable(writer, false);
108
109
reader = PR_Accept(listener, &listenAddr, PR_MillisecondsToInterval(200));
110
if (!reader) {
111
goto failed;
112
}
113
PR_SetFDInheritable(reader, false);
114
if (aSetRecvBuff) {
115
PR_SetSocketOption(reader, &recvBufferOpt);
116
}
117
PR_SetSocketOption(reader, &nodelayOpt);
118
PR_SetSocketOption(reader, &noblockOpt);
119
PR_Close(listener);
120
121
fd[0] = reader;
122
fd[1] = writer;
123
return true;
124
125
failed:
126
if (listener) {
127
PR_Close(listener);
128
}
129
if (reader) {
130
PR_Close(reader);
131
}
132
if (writer) {
133
PR_Close(writer);
134
}
135
return false;
136
}
137
138
#endif
139
140
PollableEvent::PollableEvent()
141
: mWriteFD(nullptr),
142
mReadFD(nullptr),
143
mSignaled(false),
144
mWriteFailed(false),
145
mSignalTimestampAdjusted(false) {
146
MOZ_COUNT_CTOR(PollableEvent);
147
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
148
// create pair of prfiledesc that can be used as a poll()ble
149
// signal. on windows use a localhost socket pair, and on
150
// unix use a pipe.
151
#ifdef USEPIPE
152
SOCKET_LOG(("PollableEvent() using pipe\n"));
153
if (PR_CreatePipe(&mReadFD, &mWriteFD) == PR_SUCCESS) {
154
// make the pipe non blocking. NSPR asserts at
155
// trying to use SockOpt here
156
PROsfd fd = PR_FileDesc2NativeHandle(mReadFD);
157
int flags = fcntl(fd, F_GETFL, 0);
158
(void)fcntl(fd, F_SETFL, flags | O_NONBLOCK);
159
fd = PR_FileDesc2NativeHandle(mWriteFD);
160
flags = fcntl(fd, F_GETFL, 0);
161
(void)fcntl(fd, F_SETFL, flags | O_NONBLOCK);
162
} else {
163
mReadFD = nullptr;
164
mWriteFD = nullptr;
165
SOCKET_LOG(("PollableEvent() pipe failed\n"));
166
}
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
202
// nop.
203
PRFileDesc* topLayer = 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
if (mReadFD && mWriteFD) {
220
// prime the system to deal with races invovled in [dc]tor cycle
221
SOCKET_LOG(("PollableEvent() ctor ok\n"));
222
mSignaled = true;
223
MarkFirstSignalTimestamp();
224
PR_Write(mWriteFD, "I", 1);
225
}
226
}
227
228
PollableEvent::~PollableEvent() {
229
MOZ_COUNT_DTOR(PollableEvent);
230
if (mWriteFD) {
231
#if defined(XP_WIN)
232
AttachShutdownLayer(mWriteFD);
233
#endif
234
PR_Close(mWriteFD);
235
}
236
if (mReadFD) {
237
#if defined(XP_WIN)
238
AttachShutdownLayer(mReadFD);
239
#endif
240
PR_Close(mReadFD);
241
}
242
}
243
244
// we do not record signals on the socket thread
245
// because the socket thread can reliably look at its
246
// own runnable queue before selecting a poll time
247
// this is the "service the network without blocking" comment in
248
// nsSocketTransportService2.cpp
249
bool PollableEvent::Signal() {
250
SOCKET_LOG(("PollableEvent::Signal\n"));
251
252
if (!mWriteFD) {
253
SOCKET_LOG(("PollableEvent::Signal Failed on no FD\n"));
254
return false;
255
}
256
#ifndef XP_WIN
257
// On windows poll can hang and this became worse when we introduced the
258
// patch for bug 698882 (see also bug 1292181), therefore we reverted the
259
// behavior on windows to be as before bug 698882, e.g. write to the socket
260
// also if an event dispatch is on the socket thread and writing to the
261
// socket for each event. See bug 1292181.
262
if (OnSocketThread()) {
263
SOCKET_LOG(("PollableEvent::Signal OnSocketThread nop\n"));
264
return true;
265
}
266
#endif
267
268
#ifndef XP_WIN
269
// To wake up the poll writing once is enough, but for Windows that can cause
270
// hangs so we will write for every event.
271
// For non-Windows systems it is enough to write just once.
272
if (mSignaled) {
273
return true;
274
}
275
#endif
276
277
if (!mSignaled) {
278
mSignaled = true;
279
MarkFirstSignalTimestamp();
280
}
281
282
int32_t status = PR_Write(mWriteFD, "M", 1);
283
SOCKET_LOG(("PollableEvent::Signal PR_Write %d\n", status));
284
if (status != 1) {
285
NS_WARNING("PollableEvent::Signal Failed\n");
286
SOCKET_LOG(("PollableEvent::Signal Failed\n"));
287
mSignaled = false;
288
mWriteFailed = true;
289
} else {
290
mWriteFailed = false;
291
}
292
return (status == 1);
293
}
294
295
bool PollableEvent::Clear() {
296
// necessary because of the "dont signal on socket thread" optimization
297
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
298
299
SOCKET_LOG(("PollableEvent::Clear\n"));
300
301
if (!mFirstSignalAfterClear.IsNull()) {
302
SOCKET_LOG(("PollableEvent::Clear time to signal %ums",
303
(uint32_t)(TimeStamp::NowLoRes() - mFirstSignalAfterClear)
304
.ToMilliseconds()));
305
}
306
307
mFirstSignalAfterClear = TimeStamp();
308
mSignalTimestampAdjusted = false;
309
mSignaled = false;
310
311
if (!mReadFD) {
312
SOCKET_LOG(("PollableEvent::Clear mReadFD is null\n"));
313
return false;
314
}
315
316
char buf[2048];
317
int32_t status;
318
#ifdef XP_WIN
319
// On Windows we are writing to the socket for each event, to be sure that we
320
// do not have any deadlock read from the socket as much as we can.
321
while (true) {
322
status = PR_Read(mReadFD, buf, 2048);
323
SOCKET_LOG(("PollableEvent::Clear PR_Read %d\n", status));
324
if (status == 0) {
325
SOCKET_LOG(("PollableEvent::Clear EOF!\n"));
326
return false;
327
}
328
if (status < 0) {
329
PRErrorCode code = PR_GetError();
330
if (code == PR_WOULD_BLOCK_ERROR) {
331
return true;
332
} else {
333
SOCKET_LOG(("PollableEvent::Clear unexpected error %d\n", code));
334
return false;
335
}
336
}
337
}
338
#else
339
status = PR_Read(mReadFD, buf, 2048);
340
SOCKET_LOG(("PollableEvent::Clear PR_Read %d\n", status));
341
342
if (status == 1) {
343
return true;
344
}
345
if (status == 0) {
346
SOCKET_LOG(("PollableEvent::Clear EOF!\n"));
347
return false;
348
}
349
if (status > 1) {
350
MOZ_ASSERT(false);
351
SOCKET_LOG(("PollableEvent::Clear Unexpected events\n"));
352
Clear();
353
return true;
354
}
355
PRErrorCode code = PR_GetError();
356
if (code == PR_WOULD_BLOCK_ERROR) {
357
return true;
358
}
359
SOCKET_LOG(("PollableEvent::Clear unexpected error %d\n", code));
360
return false;
361
#endif // XP_WIN
362
}
363
364
void PollableEvent::MarkFirstSignalTimestamp() {
365
if (mFirstSignalAfterClear.IsNull()) {
366
SOCKET_LOG(("PollableEvent::MarkFirstSignalTimestamp"));
367
mFirstSignalAfterClear = TimeStamp::NowLoRes();
368
}
369
}
370
371
void PollableEvent::AdjustFirstSignalTimestamp() {
372
if (!mSignalTimestampAdjusted && !mFirstSignalAfterClear.IsNull()) {
373
SOCKET_LOG(("PollableEvent::AdjustFirstSignalTimestamp"));
374
mFirstSignalAfterClear = TimeStamp::NowLoRes();
375
mSignalTimestampAdjusted = true;
376
}
377
}
378
379
bool PollableEvent::IsSignallingAlive(TimeDuration const& timeout) {
380
if (mWriteFailed) {
381
return false;
382
}
383
384
#ifdef DEBUG
385
// The timeout would be just a disturbance in a debug build.
386
return true;
387
#else
388
if (!mSignaled || mFirstSignalAfterClear.IsNull() ||
389
timeout == TimeDuration()) {
390
return true;
391
}
392
393
TimeDuration delay = (TimeStamp::NowLoRes() - mFirstSignalAfterClear);
394
bool timedOut = delay > timeout;
395
396
return !timedOut;
397
#endif // DEBUG
398
}
399
400
} // namespace net
401
} // namespace mozilla