RDDProcessHost.cpp (8915B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 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 #include "RDDProcessHost.h" 7 8 #include "RDDChild.h" 9 #include "chrome/common/process_watcher.h" 10 #include "mozilla/Preferences.h" 11 #include "mozilla/StaticPrefs_media.h" 12 #include "mozilla/dom/ContentParent.h" 13 #include "mozilla/ipc/ProcessUtils.h" 14 15 #if defined(XP_MACOSX) && defined(MOZ_SANDBOX) 16 # include "mozilla/Sandbox.h" 17 #endif 18 19 namespace mozilla { 20 21 using namespace ipc; 22 23 #if defined(XP_MACOSX) && defined(MOZ_SANDBOX) 24 bool RDDProcessHost::sLaunchWithMacSandbox = false; 25 #endif 26 27 RDDProcessHost::RDDProcessHost(Listener* aListener) 28 : GeckoChildProcessHost(GeckoProcessType_RDD), 29 mListener(aListener), 30 mLiveToken(new media::Refcountable<bool>(true)) { 31 MOZ_COUNT_CTOR(RDDProcessHost); 32 33 #if defined(XP_MACOSX) && defined(MOZ_SANDBOX) 34 if (!sLaunchWithMacSandbox) { 35 sLaunchWithMacSandbox = (PR_GetEnv("MOZ_DISABLE_RDD_SANDBOX") == nullptr); 36 } 37 mDisableOSActivityMode = sLaunchWithMacSandbox; 38 #endif 39 } 40 41 RDDProcessHost::~RDDProcessHost() { MOZ_COUNT_DTOR(RDDProcessHost); } 42 43 bool RDDProcessHost::Launch(geckoargs::ChildProcessArgs aExtraOpts) { 44 MOZ_ASSERT(NS_IsMainThread()); 45 46 MOZ_ASSERT(mLaunchPhase == LaunchPhase::Unlaunched); 47 MOZ_ASSERT(!mRDDChild); 48 49 mPrefSerializer = MakeUnique<ipc::SharedPreferenceSerializer>(); 50 if (!mPrefSerializer->SerializeToSharedMemory(GeckoProcessType_RDD, 51 /* remoteType */ ""_ns)) { 52 return false; 53 } 54 mPrefSerializer->AddSharedPrefCmdLineArgs(*this, aExtraOpts); 55 56 mLaunchPhase = LaunchPhase::Waiting; 57 mLaunchTime = TimeStamp::Now(); 58 59 int32_t timeoutMs = StaticPrefs::media_rdd_process_startup_timeout_ms(); 60 61 // If one of the following environment variables are set we can 62 // effectively ignore the timeout - as we can guarantee the RDD 63 // process will be terminated 64 if (PR_GetEnv("MOZ_DEBUG_CHILD_PROCESS") || 65 PR_GetEnv("MOZ_DEBUG_CHILD_PAUSE")) { 66 timeoutMs = 0; 67 } 68 if (timeoutMs) { 69 // We queue a delayed task. If that task runs before the 70 // WhenProcessHandleReady promise gets resolved, we will abort the launch. 71 GetMainThreadSerialEventTarget()->DelayedDispatch( 72 NS_NewRunnableFunction( 73 "RDDProcessHost::Launchtimeout", 74 [this, liveToken = mLiveToken]() { 75 if (!*liveToken || mTimerChecked) { 76 // We have been deleted or the runnable has already started, we 77 // can abort. 78 return; 79 } 80 InitAfterConnect(false); 81 MOZ_ASSERT(mTimerChecked, 82 "InitAfterConnect must have acted on the promise"); 83 }), 84 timeoutMs); 85 } 86 87 if (!GeckoChildProcessHost::AsyncLaunch(std::move(aExtraOpts))) { 88 mLaunchPhase = LaunchPhase::Complete; 89 mPrefSerializer = nullptr; 90 return false; 91 } 92 return true; 93 } 94 95 RefPtr<GenericNonExclusivePromise> RDDProcessHost::LaunchPromise() { 96 MOZ_ASSERT(NS_IsMainThread()); 97 98 if (mLaunchPromise) { 99 return mLaunchPromise; 100 } 101 mLaunchPromise = MakeRefPtr<GenericNonExclusivePromise::Private>(__func__); 102 WhenProcessHandleReady()->Then( 103 GetCurrentSerialEventTarget(), __func__, 104 [this, liveToken = mLiveToken]( 105 const ipc::ProcessHandlePromise::ResolveOrRejectValue& aResult) { 106 if (!*liveToken) { 107 // The RDDProcessHost got deleted. Abort. The promise would have 108 // already been rejected. 109 return; 110 } 111 if (mTimerChecked) { 112 // We hit the timeout earlier, abort. 113 return; 114 } 115 mTimerChecked = true; 116 if (aResult.IsReject()) { 117 RejectPromise(); 118 } 119 // If aResult.IsResolve() then we have succeeded in launching the 120 // RDD process. The promise will be resolved once the channel has 121 // connected (or failed to) later. 122 }); 123 return mLaunchPromise; 124 } 125 126 void RDDProcessHost::OnChannelConnected(base::ProcessId peer_pid) { 127 MOZ_ASSERT(!NS_IsMainThread()); 128 129 GeckoChildProcessHost::OnChannelConnected(peer_pid); 130 131 NS_DispatchToMainThread(NS_NewRunnableFunction( 132 "RDDProcessHost::OnChannelConnected", [this, liveToken = mLiveToken]() { 133 if (*liveToken && mLaunchPhase == LaunchPhase::Waiting) { 134 InitAfterConnect(true); 135 } 136 })); 137 } 138 139 static uint64_t sRDDProcessTokenCounter = 0; 140 141 void RDDProcessHost::InitAfterConnect(bool aSucceeded) { 142 MOZ_ASSERT(NS_IsMainThread()); 143 144 MOZ_ASSERT(mLaunchPhase == LaunchPhase::Waiting); 145 MOZ_ASSERT(!mRDDChild); 146 147 mLaunchPhase = LaunchPhase::Complete; 148 149 if (!aSucceeded) { 150 RejectPromise(); 151 return; 152 } 153 mProcessToken = ++sRDDProcessTokenCounter; 154 mRDDChild = MakeRefPtr<RDDChild>(this); 155 DebugOnly<bool> rv = TakeInitialEndpoint().Bind(mRDDChild.get()); 156 MOZ_ASSERT(rv); 157 158 // Only clear mPrefSerializer in the success case to avoid a 159 // possible race in the case case of a timeout on Windows launch. 160 // See Bug 1555076 comment 7: 161 // https://bugzilla.mozilla.org/show_bug.cgi?id=1555076#c7 162 mPrefSerializer = nullptr; 163 164 if (!mRDDChild->Init()) { 165 // Can't just kill here because it will create a timing race that 166 // will crash the tab. We don't really want to crash the tab just 167 // because RDD linux sandbox failed to initialize. In this case, 168 // we'll close the child channel which will cause the RDD process 169 // to shutdown nicely avoiding the tab crash (which manifests as 170 // Bug 1535335). 171 mRDDChild->Close(); 172 RejectPromise(); 173 } else { 174 ResolvePromise(); 175 } 176 } 177 178 void RDDProcessHost::Shutdown() { 179 MOZ_ASSERT(NS_IsMainThread()); 180 MOZ_ASSERT(!mShutdownRequested); 181 182 RejectPromise(); 183 184 if (mRDDChild) { 185 // OnChannelClosed uses this to check if the shutdown was expected or 186 // unexpected. 187 mShutdownRequested = true; 188 189 // The channel might already be closed if we got here unexpectedly. 190 if (!mChannelClosed) { 191 mRDDChild->Close(); 192 } 193 194 #ifndef NS_FREE_PERMANENT_DATA 195 // No need to communicate shutdown, the RDD process doesn't need to 196 // communicate anything back. 197 KillHard("NormalShutdown"); 198 #endif 199 200 // If we're shutting down unexpectedly, we're in the middle of handling an 201 // ActorDestroy for PRDDChild, which is still on the stack. We'll return 202 // back to OnChannelClosed. 203 // 204 // Otherwise, we'll wait for OnChannelClose to be called whenever PRDDChild 205 // acknowledges shutdown. 206 return; 207 } 208 209 DestroyProcess(); 210 } 211 212 void RDDProcessHost::OnChannelClosed() { 213 MOZ_ASSERT(NS_IsMainThread()); 214 215 mChannelClosed = true; 216 RejectPromise(); 217 218 if (!mShutdownRequested && mListener) { 219 // This is an unclean shutdown. Notify our listener that we're going away. 220 mListener->OnProcessUnexpectedShutdown(this); 221 } else { 222 DestroyProcess(); 223 } 224 225 // Release the actor. 226 RDDChild::Destroy(std::move(mRDDChild)); 227 } 228 229 void RDDProcessHost::KillHard(const char* aReason) { 230 MOZ_ASSERT(NS_IsMainThread()); 231 232 ProcessHandle handle = GetChildProcessHandle(); 233 if (!base::KillProcess(handle, base::PROCESS_END_KILLED_BY_USER)) { 234 NS_WARNING("failed to kill subprocess!"); 235 } 236 237 SetAlreadyDead(); 238 } 239 240 uint64_t RDDProcessHost::GetProcessToken() const { 241 MOZ_ASSERT(NS_IsMainThread()); 242 return mProcessToken; 243 } 244 245 void RDDProcessHost::DestroyProcess() { 246 MOZ_ASSERT(NS_IsMainThread()); 247 RejectPromise(); 248 249 // Any pending tasks will be cancelled from now on. 250 *mLiveToken = false; 251 252 NS_DispatchToMainThread( 253 NS_NewRunnableFunction("DestroyProcessRunnable", [this] { Destroy(); })); 254 } 255 256 void RDDProcessHost::ResolvePromise() { 257 MOZ_ASSERT(NS_IsMainThread()); 258 259 if (mLaunchPromise && !mLaunchPromiseSettled) { 260 mLaunchPromise->Resolve(true, __func__); 261 mLaunchPromiseSettled = true; 262 } 263 // We have already acted on the promise; the timeout runnable no longer needs 264 // to interrupt anything. 265 mTimerChecked = true; 266 } 267 268 void RDDProcessHost::RejectPromise() { 269 MOZ_ASSERT(NS_IsMainThread()); 270 271 if (mLaunchPromise && !mLaunchPromiseSettled) { 272 mLaunchPromise->Reject(NS_ERROR_FAILURE, __func__); 273 mLaunchPromiseSettled = true; 274 } 275 // We have already acted on the promise; the timeout runnable no longer needs 276 // to interrupt anything. 277 mTimerChecked = true; 278 } 279 280 #if defined(XP_MACOSX) && defined(MOZ_SANDBOX) 281 bool RDDProcessHost::FillMacSandboxInfo(MacSandboxInfo& aInfo) { 282 GeckoChildProcessHost::FillMacSandboxInfo(aInfo); 283 if (!aInfo.shouldLog && PR_GetEnv("MOZ_SANDBOX_RDD_LOGGING")) { 284 aInfo.shouldLog = true; 285 } 286 return true; 287 } 288 289 /* static */ 290 MacSandboxType RDDProcessHost::GetMacSandboxType() { 291 return MacSandboxType_RDD; 292 } 293 #endif 294 295 } // namespace mozilla