1 // vim:set sw=4 sts=4 et cin:
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
15 * The Original Code is Mozilla.
17 * The Initial Developer of the Original Code is
18 * Netscape Communications Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 2002
20 * the Initial Developer. All Rights Reserved.
23 * Darin Fisher <darin@netscape.com>
25 * Alternatively, the contents of this file may be used under the terms of
26 * either the GNU General Public License Version 2 or later (the "GPL"), or
27 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
37 * ***** END LICENSE BLOCK ***** */
43 #include "nsSocketTransportService2.h"
44 #include "nsSocketTransport2.h"
45 #include "nsReadableUtils.h"
46 #include "nsAutoLock.h"
47 #include "nsNetError.h"
53 #if defined(PR_LOGGING)
54 PRLogModuleInfo
*gSocketTransportLog
= nsnull
;
57 nsSocketTransportService
*gSocketTransportService
= nsnull
;
58 PRThread
*gSocketThread
= nsnull
;
60 //-----------------------------------------------------------------------------
61 // ctor/dtor (called on the main/UI thread by the service manager)
63 nsSocketTransportService::nsSocketTransportService()
65 , mThreadEvent(nsnull
)
66 , mAutodialEnabled(PR_FALSE
)
68 , mInitialized(PR_FALSE
)
69 , mShuttingDown(PR_FALSE
)
73 #if defined(PR_LOGGING)
74 gSocketTransportLog
= PR_NewLogModule("nsSocketTransport");
77 NS_ASSERTION(NS_IsMainThread(), "wrong thread");
79 NS_ASSERTION(!gSocketTransportService
, "must not instantiate twice");
80 gSocketTransportService
= this;
83 nsSocketTransportService::~nsSocketTransportService()
85 NS_ASSERTION(NS_IsMainThread(), "wrong thread");
86 NS_ASSERTION(!mInitialized
, "not shutdown properly");
89 PR_DestroyLock(mLock
);
92 PR_DestroyPollableEvent(mThreadEvent
);
94 gSocketTransportService
= nsnull
;
97 //-----------------------------------------------------------------------------
98 // event queue (any thread)
100 already_AddRefed
<nsIThread
>
101 nsSocketTransportService::GetThreadSafely()
103 nsAutoLock
lock(mLock
);
104 nsIThread
* result
= mThread
;
105 NS_IF_ADDREF(result
);
110 nsSocketTransportService::Dispatch(nsIRunnable
*event
, PRUint32 flags
)
112 LOG(("STS dispatch [%p]\n", event
));
114 nsCOMPtr
<nsIThread
> thread
= GetThreadSafely();
115 NS_ENSURE_TRUE(thread
, NS_ERROR_NOT_INITIALIZED
);
116 nsresult rv
= thread
->Dispatch(event
, flags
);
117 if (rv
== NS_ERROR_UNEXPECTED
) {
118 // Thread is no longer accepting events. We must have just shut it
119 // down on the main thread. Pretend we never saw it.
120 rv
= NS_ERROR_NOT_INITIALIZED
;
126 nsSocketTransportService::IsOnCurrentThread(PRBool
*result
)
128 nsCOMPtr
<nsIThread
> thread
= GetThreadSafely();
129 NS_ENSURE_TRUE(thread
, NS_ERROR_NOT_INITIALIZED
);
130 return thread
->IsOnCurrentThread(result
);
133 //-----------------------------------------------------------------------------
134 // socket api (socket thread only)
137 nsSocketTransportService::NotifyWhenCanAttachSocket(nsIRunnable
*event
)
139 LOG(("nsSocketTransportService::NotifyWhenCanAttachSocket\n"));
141 NS_ASSERTION(PR_GetCurrentThread() == gSocketThread
, "wrong thread");
143 if (CanAttachSocket()) {
144 return Dispatch(event
, NS_DISPATCH_NORMAL
);
147 mPendingSocketQ
.PutEvent(event
);
152 nsSocketTransportService::AttachSocket(PRFileDesc
*fd
, nsASocketHandler
*handler
)
154 LOG(("nsSocketTransportService::AttachSocket [handler=%x]\n", handler
));
156 NS_ASSERTION(PR_GetCurrentThread() == gSocketThread
, "wrong thread");
158 if (!CanAttachSocket()) {
159 return NS_ERROR_NOT_AVAILABLE
;
164 sock
.mHandler
= handler
;
165 sock
.mElapsedTime
= 0;
167 nsresult rv
= AddToIdleList(&sock
);
168 if (NS_SUCCEEDED(rv
))
174 nsSocketTransportService::DetachSocket(SocketContext
*sock
)
176 LOG(("nsSocketTransportService::DetachSocket [handler=%x]\n", sock
->mHandler
));
178 // inform the handler that this socket is going away
179 sock
->mHandler
->OnSocketDetached(sock
->mFD
);
183 NS_RELEASE(sock
->mHandler
);
185 // find out what list this is on.
186 PRUint32 index
= sock
- mActiveList
;
187 if (index
< NS_SOCKET_MAX_COUNT
)
188 RemoveFromPollList(sock
);
190 RemoveFromIdleList(sock
);
192 // NOTE: sock is now an invalid pointer
195 // notify the first element on the pending socket queue...
197 nsCOMPtr
<nsIRunnable
> event
;
198 if (mPendingSocketQ
.GetPendingEvent(getter_AddRefs(event
))) {
199 // move event from pending queue to dispatch queue
200 return Dispatch(event
, NS_DISPATCH_NORMAL
);
206 nsSocketTransportService::AddToPollList(SocketContext
*sock
)
208 LOG(("nsSocketTransportService::AddToPollList [handler=%x]\n", sock
->mHandler
));
210 if (mActiveCount
== NS_SOCKET_MAX_COUNT
) {
211 NS_ERROR("too many active sockets");
212 return NS_ERROR_UNEXPECTED
;
215 mActiveList
[mActiveCount
] = *sock
;
218 mPollList
[mActiveCount
].fd
= sock
->mFD
;
219 mPollList
[mActiveCount
].in_flags
= sock
->mHandler
->mPollFlags
;
220 mPollList
[mActiveCount
].out_flags
= 0;
222 LOG((" active=%u idle=%u\n", mActiveCount
, mIdleCount
));
227 nsSocketTransportService::RemoveFromPollList(SocketContext
*sock
)
229 LOG(("nsSocketTransportService::RemoveFromPollList [handler=%x]\n", sock
->mHandler
));
231 PRUint32 index
= sock
- mActiveList
;
232 NS_ASSERTION(index
< NS_SOCKET_MAX_COUNT
, "invalid index");
234 LOG((" index=%u mActiveCount=%u\n", index
, mActiveCount
));
236 if (index
!= mActiveCount
-1) {
237 mActiveList
[index
] = mActiveList
[mActiveCount
-1];
238 mPollList
[index
+1] = mPollList
[mActiveCount
];
242 LOG((" active=%u idle=%u\n", mActiveCount
, mIdleCount
));
246 nsSocketTransportService::AddToIdleList(SocketContext
*sock
)
248 LOG(("nsSocketTransportService::AddToIdleList [handler=%x]\n", sock
->mHandler
));
250 if (mIdleCount
== NS_SOCKET_MAX_COUNT
) {
251 NS_ERROR("too many idle sockets");
252 return NS_ERROR_UNEXPECTED
;
255 mIdleList
[mIdleCount
] = *sock
;
258 LOG((" active=%u idle=%u\n", mActiveCount
, mIdleCount
));
263 nsSocketTransportService::RemoveFromIdleList(SocketContext
*sock
)
265 LOG(("nsSocketTransportService::RemoveFromIdleList [handler=%x]\n", sock
->mHandler
));
267 PRUint32 index
= sock
- &mIdleList
[0];
268 NS_ASSERTION(index
< NS_SOCKET_MAX_COUNT
, "invalid index");
270 if (index
!= mIdleCount
-1)
271 mIdleList
[index
] = mIdleList
[mIdleCount
-1];
274 LOG((" active=%u idle=%u\n", mActiveCount
, mIdleCount
));
278 nsSocketTransportService::MoveToIdleList(SocketContext
*sock
)
280 nsresult rv
= AddToIdleList(sock
);
284 RemoveFromPollList(sock
);
288 nsSocketTransportService::MoveToPollList(SocketContext
*sock
)
290 nsresult rv
= AddToPollList(sock
);
294 RemoveFromIdleList(sock
);
298 nsSocketTransportService::PollTimeout()
300 if (mActiveCount
== 0)
301 return NS_SOCKET_POLL_TIMEOUT
;
303 // compute minimum time before any socket timeout expires.
304 PRUint32 minR
= PR_UINT16_MAX
;
305 for (PRUint32 i
=0; i
<mActiveCount
; ++i
) {
306 const SocketContext
&s
= mActiveList
[i
];
307 // mPollTimeout could be less than mElapsedTime if setTimeout
308 // was called with a value smaller than mElapsedTime.
309 PRUint32 r
= (s
.mElapsedTime
< s
.mHandler
->mPollTimeout
)
310 ? s
.mHandler
->mPollTimeout
- s
.mElapsedTime
315 LOG(("poll timeout: %lu\n", minR
));
316 return PR_SecondsToInterval(minR
);
320 nsSocketTransportService::Poll(PRBool wait
, PRUint32
*interval
)
322 PRPollDesc
*pollList
;
324 PRIntervalTime pollTimeout
;
326 if (mPollList
[0].fd
) {
327 mPollList
[0].out_flags
= 0;
328 pollList
= mPollList
;
329 pollCount
= mActiveCount
+ 1;
330 pollTimeout
= PollTimeout();
333 // no pollable event, so busy wait...
334 pollCount
= mActiveCount
;
336 pollList
= &mPollList
[1];
339 pollTimeout
= PR_MillisecondsToInterval(25);
343 pollTimeout
= PR_INTERVAL_NO_WAIT
;
345 PRIntervalTime ts
= PR_IntervalNow();
347 LOG((" timeout = %i milliseconds\n",
348 PR_IntervalToMilliseconds(pollTimeout
)));
349 PRInt32 rv
= PR_Poll(pollList
, pollCount
, pollTimeout
);
351 PRIntervalTime passedInterval
= PR_IntervalNow() - ts
;
353 LOG((" ...returned after %i milliseconds\n",
354 PR_IntervalToMilliseconds(passedInterval
)));
356 *interval
= PR_IntervalToSeconds(passedInterval
);
360 //-----------------------------------------------------------------------------
363 NS_IMPL_THREADSAFE_ISUPPORTS5(nsSocketTransportService
,
364 nsISocketTransportService
,
368 nsPISocketTransportService
)
370 // called from main thread only
372 nsSocketTransportService::Init()
374 NS_ENSURE_TRUE(mLock
, NS_ERROR_OUT_OF_MEMORY
);
376 if (!NS_IsMainThread()) {
377 NS_ERROR("wrong thread");
378 return NS_ERROR_UNEXPECTED
;
385 return NS_ERROR_UNEXPECTED
;
388 mThreadEvent
= PR_NewPollableEvent();
390 // NOTE: per bug 190000, this failure could be caused by Zone-Alarm
391 // or similar software.
393 // NOTE: per bug 191739, this failure could also be caused by lack
394 // of a loopback device on Windows and OS/2 platforms (NSPR creates
395 // a loopback socket pair on these platforms to implement a pollable
396 // event object). if we can't create a pollable event, then we'll
397 // have to "busy wait" to implement the socket event queue :-(
400 NS_WARNING("running socket transport thread without a pollable event");
401 LOG(("running socket transport thread without a pollable event"));
405 nsCOMPtr
<nsIThread
> thread
;
406 nsresult rv
= NS_NewThread(getter_AddRefs(thread
), this);
407 if (NS_FAILED(rv
)) return rv
;
410 nsAutoLock
lock(mLock
);
411 // Install our mThread, protecting against concurrent readers
412 thread
.swap(mThread
);
415 mInitialized
= PR_TRUE
;
419 // called from main thread only
421 nsSocketTransportService::Shutdown()
423 LOG(("nsSocketTransportService::Shutdown\n"));
425 NS_ENSURE_STATE(NS_IsMainThread());
431 return NS_ERROR_UNEXPECTED
;
434 nsAutoLock
lock(mLock
);
436 // signal the socket thread to shutdown
437 mShuttingDown
= PR_TRUE
;
440 PR_SetPollableEvent(mThreadEvent
);
441 // else wait for Poll timeout
447 nsAutoLock
lock(mLock
);
448 // Drop our reference to mThread and make sure that any concurrent
449 // readers are excluded
453 mInitialized
= PR_FALSE
;
454 mShuttingDown
= PR_FALSE
;
460 nsSocketTransportService::CreateTransport(const char **types
,
462 const nsACString
&host
,
464 nsIProxyInfo
*proxyInfo
,
465 nsISocketTransport
**result
)
467 NS_ENSURE_TRUE(mInitialized
, NS_ERROR_OFFLINE
);
468 NS_ENSURE_TRUE(port
>= 0 && port
<= 0xFFFF, NS_ERROR_ILLEGAL_VALUE
);
470 nsSocketTransport
*trans
= new nsSocketTransport();
472 return NS_ERROR_OUT_OF_MEMORY
;
475 nsresult rv
= trans
->Init(types
, typeCount
, host
, port
, proxyInfo
);
486 nsSocketTransportService::GetAutodialEnabled(PRBool
*value
)
488 *value
= mAutodialEnabled
;
493 nsSocketTransportService::SetAutodialEnabled(PRBool value
)
495 mAutodialEnabled
= value
;
500 nsSocketTransportService::OnDispatchedEvent(nsIThreadInternal
*thread
)
502 nsAutoLock
lock(mLock
);
504 PR_SetPollableEvent(mThreadEvent
);
509 nsSocketTransportService::OnProcessNextEvent(nsIThreadInternal
*thread
,
510 PRBool mayWait
, PRUint32 depth
)
512 // DoPollIteration doesn't support being called recursively. This case
513 // should only happen when someone (e.g., PSM) is issuing a synchronous
514 // proxy call from this thread to the main thread.
518 // Favor processing existing sockets before other events.
519 DoPollIteration(PR_FALSE
);
522 while (mayWait
&& NS_SUCCEEDED(thread
->HasPendingEvents(&val
)) && !val
)
523 DoPollIteration(PR_TRUE
);
529 nsSocketTransportService::AfterProcessNextEvent(nsIThreadInternal
* thread
,
536 nsSocketTransportService::Run()
538 LOG(("STS thread init\n"));
540 gSocketThread
= PR_GetCurrentThread();
542 // add thread event to poll list (mThreadEvent may be NULL)
543 mPollList
[0].fd
= mThreadEvent
;
544 mPollList
[0].in_flags
= PR_POLL_READ
;
545 mPollList
[0].out_flags
= 0;
547 nsIThread
*thread
= NS_GetCurrentThread();
549 // hook ourselves up to observe event processing for this thread
550 nsCOMPtr
<nsIThreadInternal
> threadInt
= do_QueryInterface(thread
);
551 threadInt
->SetObserver(this);
554 // process all pending events
555 NS_ProcessPendingEvents(thread
);
557 // now that our event queue is empty, check to see if we should exit
559 nsAutoLock
lock(mLock
);
564 // wait for and process the next pending event
565 NS_ProcessNextEvent(thread
);
568 LOG(("STS shutting down thread\n"));
570 // detach any sockets
572 for (i
=mActiveCount
-1; i
>=0; --i
)
573 DetachSocket(&mActiveList
[i
]);
574 for (i
=mIdleCount
-1; i
>=0; --i
)
575 DetachSocket(&mIdleList
[i
]);
577 // Final pass over the event queue. This makes sure that events posted by
578 // socket detach handlers get processed.
579 NS_ProcessPendingEvents(thread
);
581 gSocketThread
= nsnull
;
583 LOG(("STS thread exit\n"));
588 nsSocketTransportService::DoPollIteration(PRBool wait
)
590 LOG(("STS poll iter [%d]\n", wait
));
597 PRBool pollError
= PR_FALSE
;
600 // walk active list backwards to see if any sockets should actually be
601 // idle, then walk the idle list backwards to see if any idle sockets
602 // should become active. take care to check only idle sockets that
603 // were idle to begin with ;-)
606 for (i
=mActiveCount
-1; i
>=0; --i
) {
608 LOG((" active [%u] { handler=%x condition=%x pollflags=%hu }\n", i
,
609 mActiveList
[i
].mHandler
,
610 mActiveList
[i
].mHandler
->mCondition
,
611 mActiveList
[i
].mHandler
->mPollFlags
));
613 if (NS_FAILED(mActiveList
[i
].mHandler
->mCondition
))
614 DetachSocket(&mActiveList
[i
]);
616 PRUint16 in_flags
= mActiveList
[i
].mHandler
->mPollFlags
;
618 MoveToIdleList(&mActiveList
[i
]);
621 mPollList
[i
+1].in_flags
= in_flags
;
622 mPollList
[i
+1].out_flags
= 0;
626 for (i
=count
-1; i
>=0; --i
) {
628 LOG((" idle [%u] { handler=%x condition=%x pollflags=%hu }\n", i
,
629 mIdleList
[i
].mHandler
,
630 mIdleList
[i
].mHandler
->mCondition
,
631 mIdleList
[i
].mHandler
->mPollFlags
));
633 if (NS_FAILED(mIdleList
[i
].mHandler
->mCondition
))
634 DetachSocket(&mIdleList
[i
]);
635 else if (mIdleList
[i
].mHandler
->mPollFlags
!= 0)
636 MoveToPollList(&mIdleList
[i
]);
639 LOG((" calling PR_Poll [active=%u idle=%u]\n", mActiveCount
, mIdleCount
));
641 // Measures seconds spent while blocked on PR_Poll
642 PRUint32 pollInterval
;
644 PRInt32 n
= Poll(wait
, &pollInterval
);
646 LOG((" PR_Poll error [%d]\n", PR_GetError()));
651 // service "active" sockets...
653 for (i
=0; i
<PRInt32(mActiveCount
); ++i
) {
654 PRPollDesc
&desc
= mPollList
[i
+1];
655 SocketContext
&s
= mActiveList
[i
];
656 if (n
> 0 && desc
.out_flags
!= 0) {
658 s
.mHandler
->OnSocketReady(desc
.fd
, desc
.out_flags
);
660 // check for timeout errors unless disabled...
661 else if (s
.mHandler
->mPollTimeout
!= PR_UINT16_MAX
) {
662 // update elapsed time counter
663 if (NS_UNLIKELY(pollInterval
> (PR_UINT16_MAX
- s
.mElapsedTime
)))
664 s
.mElapsedTime
= PR_UINT16_MAX
;
666 s
.mElapsedTime
+= PRUint16(pollInterval
);
667 // check for timeout expiration
668 if (s
.mElapsedTime
>= s
.mHandler
->mPollTimeout
) {
670 s
.mHandler
->OnSocketReady(desc
.fd
, -1);
676 // check for "dead" sockets and remove them (need to do this in
677 // reverse order obviously).
679 for (i
=mActiveCount
-1; i
>=0; --i
) {
680 if (NS_FAILED(mActiveList
[i
].mHandler
->mCondition
))
681 DetachSocket(&mActiveList
[i
]);
684 if (n
!= 0 && mPollList
[0].out_flags
== PR_POLL_READ
) {
685 // acknowledge pollable event (wait should not block)
686 if (PR_WaitForPollableEvent(mThreadEvent
) != PR_SUCCESS
) {
687 // On Windows, the TCP loopback connection in the
688 // pollable event may become broken when a laptop
689 // switches between wired and wireless networks or
690 // wakes up from hibernation. We try to create a
691 // new pollable event. If that fails, we fall back
694 nsAutoLock
lock(mLock
);
695 PR_DestroyPollableEvent(mThreadEvent
);
696 mThreadEvent
= PR_NewPollableEvent();
699 NS_WARNING("running socket transport thread without "
701 LOG(("running socket transport thread without "
702 "a pollable event"));
704 mPollList
[0].fd
= mThreadEvent
;
705 // mPollList[0].in_flags was already set to PR_POLL_READ
707 mPollList
[0].out_flags
= 0;