1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "mozilla/Services.h"
8 #include "nsIObserverService.h"
9 #include "nsNamedPipeService.h"
11 #include "nsThreadUtils.h"
12 #include "mozilla/ClearOnShutdown.h"
13 #include "mozilla/Logging.h"
18 static mozilla::LazyLogModule
gNamedPipeServiceLog("NamedPipeWin");
19 #define LOG_NPS_DEBUG(...) \
20 MOZ_LOG(gNamedPipeServiceLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
21 #define LOG_NPS_ERROR(...) \
22 MOZ_LOG(gNamedPipeServiceLog, mozilla::LogLevel::Error, (__VA_ARGS__))
24 StaticRefPtr
<NamedPipeService
> NamedPipeService::gSingleton
;
26 NS_IMPL_ISUPPORTS(NamedPipeService
, nsINamedPipeService
, nsIObserver
,
29 NamedPipeService::NamedPipeService()
30 : mIocp(nullptr), mIsShutdown(false), mLock("NamedPipeServiceLock") {}
32 nsresult
NamedPipeService::Init() {
33 MOZ_ASSERT(!mIsShutdown
);
37 // nsIObserverService must be accessed in main thread.
38 // register shutdown event to stop NamedPipeSrv thread.
39 nsCOMPtr
<nsIObserver
> self(this);
40 nsCOMPtr
<nsIRunnable
> r
= NS_NewRunnableFunction(
41 "NamedPipeService::Init", [self
= std::move(self
)]() -> void {
42 MOZ_ASSERT(NS_IsMainThread());
44 nsCOMPtr
<nsIObserverService
> svc
=
45 mozilla::services::GetObserverService();
47 if (NS_WARN_IF(!svc
)) {
51 if (NS_WARN_IF(NS_FAILED(svc
->AddObserver(
52 self
, NS_XPCOM_SHUTDOWN_OBSERVER_ID
, false)))) {
57 if (NS_IsMainThread()) {
60 rv
= NS_DispatchToMainThread(r
);
62 if (NS_WARN_IF(NS_FAILED(rv
))) {
66 mIocp
= CreateIoCompletionPort(INVALID_HANDLE_VALUE
, nullptr, 0, 1);
67 if (NS_WARN_IF(!mIocp
|| mIocp
== INVALID_HANDLE_VALUE
)) {
69 return NS_ERROR_FAILURE
;
72 rv
= NS_NewNamedThread("NamedPipeSrv", getter_AddRefs(mThread
));
73 if (NS_WARN_IF(NS_FAILED(rv
))) {
82 already_AddRefed
<nsINamedPipeService
> NamedPipeService::GetOrCreate() {
83 MOZ_ASSERT(NS_IsMainThread());
85 RefPtr
<NamedPipeService
> inst
;
89 inst
= new NamedPipeService();
90 nsresult rv
= inst
->Init();
91 NS_ENSURE_SUCCESS(rv
, nullptr);
93 ClearOnShutdown(&gSingleton
);
99 void NamedPipeService::Shutdown() {
100 MOZ_ASSERT(NS_IsMainThread());
103 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
105 obs
->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID
);
109 if (mThread
&& !mIsShutdown
) {
112 // invoke ERROR_ABANDONED_WAIT_0 to |GetQueuedCompletionStatus|
119 // close I/O Completion Port
120 if (mIocp
&& mIocp
!= INVALID_HANDLE_VALUE
) {
126 void NamedPipeService::RemoveRetiredObjects() {
127 MOZ_ASSERT(NS_GetCurrentThread() == mThread
);
128 mLock
.AssertCurrentThreadOwns();
130 if (!mRetiredHandles
.IsEmpty()) {
131 for (auto& handle
: mRetiredHandles
) {
134 mRetiredHandles
.Clear();
137 mRetiredObservers
.Clear();
141 * Implement nsINamedPipeService
145 NamedPipeService::AddDataObserver(void* aHandle
,
146 nsINamedPipeDataObserver
* aObserver
) {
147 if (!aHandle
|| aHandle
== INVALID_HANDLE_VALUE
|| !aObserver
) {
148 return NS_ERROR_ILLEGAL_VALUE
;
153 HANDLE h
= CreateIoCompletionPort(aHandle
, mIocp
,
154 reinterpret_cast<ULONG_PTR
>(aObserver
), 1);
155 if (NS_WARN_IF(!h
)) {
156 LOG_NPS_ERROR("CreateIoCompletionPort error (%lu)", GetLastError());
157 return NS_ERROR_FAILURE
;
159 if (NS_WARN_IF(h
!= mIocp
)) {
161 "CreateIoCompletionPort got unexpected value %p (should be %p)", h
,
164 return NS_ERROR_FAILURE
;
168 MutexAutoLock
lock(mLock
);
169 MOZ_ASSERT(!mObservers
.Contains(aObserver
));
171 mObservers
.AppendElement(aObserver
);
174 if (mObservers
.Length() == 1) {
175 rv
= mThread
->Dispatch(this, NS_DISPATCH_NORMAL
);
176 if (NS_WARN_IF(NS_FAILED(rv
))) {
177 LOG_NPS_ERROR("Dispatch to thread failed (%08x)", uint32_t(rv
));
188 NamedPipeService::RemoveDataObserver(void* aHandle
,
189 nsINamedPipeDataObserver
* aObserver
) {
190 MutexAutoLock
lock(mLock
);
191 mObservers
.RemoveElement(aObserver
);
193 mRetiredHandles
.AppendElement(aHandle
);
194 mRetiredObservers
.AppendElement(aObserver
);
200 NamedPipeService::IsOnCurrentThread(bool* aRetVal
) {
209 return mThread
->IsOnCurrentThread(aRetVal
);
213 * Implement nsIObserver
217 NamedPipeService::Observe(nsISupports
* aSubject
, const char* aTopic
,
218 const char16_t
* aData
) {
219 MOZ_ASSERT(NS_IsMainThread());
221 if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID
, aTopic
)) {
229 * Implement nsIRunnable
233 NamedPipeService::Run() {
234 MOZ_ASSERT(NS_GetCurrentThread() == mThread
);
235 MOZ_ASSERT(mIocp
&& mIocp
!= INVALID_HANDLE_VALUE
);
237 while (!mIsShutdown
) {
239 MutexAutoLock
lock(mLock
);
240 if (mObservers
.IsEmpty()) {
241 LOG_NPS_DEBUG("no observer, stop loop");
245 RemoveRetiredObjects();
248 DWORD bytesTransferred
= 0;
250 LPOVERLAPPED overlapped
= nullptr;
252 GetQueuedCompletionStatus(mIocp
, &bytesTransferred
, &key
, &overlapped
,
253 1000); // timeout, 1s
254 auto err
= GetLastError();
256 if (err
== WAIT_TIMEOUT
) {
258 } else if (err
== ERROR_ABANDONED_WAIT_0
) { // mIocp was closed
260 } else if (!overlapped
) {
262 * Did not dequeue a completion packet from the completion port, and
263 * bytesTransferred/key are meaningless.
264 * See remarks of |GetQueuedCompletionStatus| API.
267 LOG_NPS_ERROR("invalid overlapped (%lu)", err
);
275 * Windows doesn't provide a method to remove created I/O Completion Port,
276 * all we can do is just close the handle we monitored before.
277 * In some cases, there's race condition that the monitored handle has an
278 * I/O status after the observer is being removed and destroyed.
279 * To avoid changing the ref-count of a dangling pointer, don't use nsCOMPtr
282 nsINamedPipeDataObserver
* target
=
283 reinterpret_cast<nsINamedPipeDataObserver
*>(key
);
285 nsCOMPtr
<nsINamedPipeDataObserver
> obs
;
287 MutexAutoLock
lock(mLock
);
289 auto idx
= mObservers
.IndexOf(target
);
290 if (idx
== decltype(mObservers
)::NoIndex
) {
291 LOG_NPS_ERROR("observer %p not found", target
);
297 MOZ_ASSERT(obs
.get());
300 LOG_NPS_DEBUG("OnDataAvailable: obs=%p, bytes=%lu", obs
.get(),
302 obs
->OnDataAvailable(bytesTransferred
, overlapped
);
304 LOG_NPS_ERROR("GetQueuedCompletionStatus %p failed, error=%lu", obs
.get(),
306 obs
->OnError(err
, overlapped
);
311 MutexAutoLock
lock(mLock
);
312 RemoveRetiredObjects();
319 } // namespace mozilla