nsNamedPipeService.cpp (8128B)
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/. */ 5 6 #include "mozilla/Services.h" 7 #include "nsCOMPtr.h" 8 #include "nsIObserverService.h" 9 #include "nsNamedPipeService.h" 10 #include "nsNetCID.h" 11 #include "nsThreadUtils.h" 12 #include "mozilla/ClearOnShutdown.h" 13 #include "mozilla/Logging.h" 14 15 namespace mozilla { 16 namespace net { 17 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__)) 23 24 StaticRefPtr<NamedPipeService> NamedPipeService::gSingleton; 25 26 NS_IMPL_ISUPPORTS(NamedPipeService, nsINamedPipeService, nsIObserver, 27 nsIRunnable) 28 29 NamedPipeService::NamedPipeService() 30 : mIocp(nullptr), mIsShutdown(false), mLock("NamedPipeServiceLock") {} 31 32 nsresult NamedPipeService::Init() { 33 MOZ_ASSERT(!mIsShutdown); 34 35 nsresult rv; 36 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()); 43 44 nsCOMPtr<nsIObserverService> svc = 45 mozilla::services::GetObserverService(); 46 47 if (NS_WARN_IF(!svc)) { 48 return; 49 } 50 51 if (NS_WARN_IF(NS_FAILED(svc->AddObserver( 52 self, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false)))) { 53 return; 54 } 55 }); 56 57 if (NS_IsMainThread()) { 58 rv = r->Run(); 59 } else { 60 rv = NS_DispatchToMainThread(r); 61 } 62 if (NS_WARN_IF(NS_FAILED(rv))) { 63 return rv; 64 } 65 66 mIocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, 1); 67 if (NS_WARN_IF(!mIocp || mIocp == INVALID_HANDLE_VALUE)) { 68 Shutdown(); 69 return NS_ERROR_FAILURE; 70 } 71 72 rv = NS_NewNamedThread("NamedPipeSrv", getter_AddRefs(mThread)); 73 if (NS_WARN_IF(NS_FAILED(rv))) { 74 Shutdown(); 75 return rv; 76 } 77 78 return NS_OK; 79 } 80 81 // static 82 already_AddRefed<nsINamedPipeService> NamedPipeService::GetOrCreate() { 83 MOZ_ASSERT(NS_IsMainThread()); 84 85 RefPtr<NamedPipeService> inst; 86 if (gSingleton) { 87 inst = gSingleton; 88 } else { 89 inst = new NamedPipeService(); 90 nsresult rv = inst->Init(); 91 NS_ENSURE_SUCCESS(rv, nullptr); 92 gSingleton = inst; 93 ClearOnShutdown(&gSingleton); 94 } 95 96 return inst.forget(); 97 } 98 99 void NamedPipeService::Shutdown() { 100 MOZ_ASSERT(NS_IsMainThread()); 101 102 // remove observer 103 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); 104 if (obs) { 105 obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); 106 } 107 108 // stop thread 109 if (mThread && !mIsShutdown) { 110 mIsShutdown = true; 111 112 // invoke ERROR_ABANDONED_WAIT_0 to |GetQueuedCompletionStatus| 113 CloseHandle(mIocp); 114 mIocp = nullptr; 115 116 mThread->Shutdown(); 117 } 118 119 // close I/O Completion Port 120 if (mIocp && mIocp != INVALID_HANDLE_VALUE) { 121 CloseHandle(mIocp); 122 mIocp = nullptr; 123 } 124 } 125 126 void NamedPipeService::RemoveRetiredObjects() { 127 MOZ_ASSERT(NS_GetCurrentThread() == mThread); 128 mLock.AssertCurrentThreadOwns(); 129 130 if (!mRetiredHandles.IsEmpty()) { 131 for (auto& handle : mRetiredHandles) { 132 CloseHandle(handle); 133 } 134 mRetiredHandles.Clear(); 135 } 136 137 mRetiredObservers.Clear(); 138 } 139 140 /** 141 * Implement nsINamedPipeService 142 */ 143 144 NS_IMETHODIMP 145 NamedPipeService::AddDataObserver(void* aHandle, 146 nsINamedPipeDataObserver* aObserver) { 147 if (!aHandle || aHandle == INVALID_HANDLE_VALUE || !aObserver) { 148 return NS_ERROR_ILLEGAL_VALUE; 149 } 150 151 nsresult rv; 152 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; 158 } 159 if (NS_WARN_IF(h != mIocp)) { 160 LOG_NPS_ERROR( 161 "CreateIoCompletionPort got unexpected value %p (should be %p)", h, 162 mIocp); 163 CloseHandle(h); 164 return NS_ERROR_FAILURE; 165 } 166 167 { 168 MutexAutoLock lock(mLock); 169 MOZ_ASSERT(!mObservers.Contains(aObserver)); 170 171 mObservers.AppendElement(aObserver); 172 173 // start event loop 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)); 178 mObservers.Clear(); 179 return rv; 180 } 181 } 182 } 183 184 return NS_OK; 185 } 186 187 NS_IMETHODIMP 188 NamedPipeService::RemoveDataObserver(void* aHandle, 189 nsINamedPipeDataObserver* aObserver) { 190 MutexAutoLock lock(mLock); 191 mObservers.RemoveElement(aObserver); 192 193 mRetiredHandles.AppendElement(aHandle); 194 mRetiredObservers.AppendElement(aObserver); 195 196 return NS_OK; 197 } 198 199 NS_IMETHODIMP 200 NamedPipeService::IsOnCurrentThread(bool* aRetVal) { 201 MOZ_ASSERT(mThread); 202 MOZ_ASSERT(aRetVal); 203 204 if (!mThread) { 205 *aRetVal = false; 206 return NS_OK; 207 } 208 209 return mThread->IsOnCurrentThread(aRetVal); 210 } 211 212 /** 213 * Implement nsIObserver 214 */ 215 216 NS_IMETHODIMP 217 NamedPipeService::Observe(nsISupports* aSubject, const char* aTopic, 218 const char16_t* aData) { 219 MOZ_ASSERT(NS_IsMainThread()); 220 221 if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, aTopic)) { 222 Shutdown(); 223 } 224 225 return NS_OK; 226 } 227 228 /** 229 * Implement nsIRunnable 230 */ 231 232 NS_IMETHODIMP 233 NamedPipeService::Run() { 234 MOZ_ASSERT(NS_GetCurrentThread() == mThread); 235 MOZ_ASSERT(mIocp && mIocp != INVALID_HANDLE_VALUE); 236 237 while (!mIsShutdown) { 238 { 239 MutexAutoLock lock(mLock); 240 if (mObservers.IsEmpty()) { 241 LOG_NPS_DEBUG("no observer, stop loop"); 242 break; 243 } 244 245 RemoveRetiredObjects(); 246 } 247 248 DWORD bytesTransferred = 0; 249 ULONG_PTR key = 0; 250 LPOVERLAPPED overlapped = nullptr; 251 BOOL success = 252 GetQueuedCompletionStatus(mIocp, &bytesTransferred, &key, &overlapped, 253 1000); // timeout, 1s 254 auto err = GetLastError(); 255 if (!success) { 256 if (err == WAIT_TIMEOUT) { 257 continue; 258 } else if (err == ERROR_ABANDONED_WAIT_0) { // mIocp was closed 259 break; 260 } else if (!overlapped) { 261 /** 262 * Did not dequeue a completion packet from the completion port, and 263 * bytesTransferred/key are meaningless. 264 * See remarks of |GetQueuedCompletionStatus| API. 265 */ 266 267 LOG_NPS_ERROR("invalid overlapped (%lu)", err); 268 continue; 269 } 270 271 MOZ_ASSERT(key); 272 } 273 274 /** 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 280 * here. 281 */ 282 nsINamedPipeDataObserver* target = 283 reinterpret_cast<nsINamedPipeDataObserver*>(key); 284 285 nsCOMPtr<nsINamedPipeDataObserver> obs; 286 { 287 MutexAutoLock lock(mLock); 288 289 auto idx = mObservers.IndexOf(target); 290 if (idx == decltype(mObservers)::NoIndex) { 291 LOG_NPS_ERROR("observer %p not found", target); 292 continue; 293 } 294 obs = target; 295 } 296 297 MOZ_ASSERT(obs.get()); 298 299 if (success) { 300 LOG_NPS_DEBUG("OnDataAvailable: obs=%p, bytes=%lu", obs.get(), 301 bytesTransferred); 302 obs->OnDataAvailable(bytesTransferred, overlapped); 303 } else { 304 LOG_NPS_ERROR("GetQueuedCompletionStatus %p failed, error=%lu", obs.get(), 305 err); 306 obs->OnError(err, overlapped); 307 } 308 } 309 310 { 311 MutexAutoLock lock(mLock); 312 RemoveRetiredObjects(); 313 } 314 315 return NS_OK; 316 } 317 318 } // namespace net 319 } // namespace mozilla