BackgroundImpl.cpp (39784B)
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 file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "BackgroundChild.h" 8 #include "BackgroundParent.h" 9 10 #include "BackgroundChildImpl.h" 11 #include "BackgroundParentImpl.h" 12 #include "MainThreadUtils.h" 13 #include "base/process_util.h" 14 #include "base/task.h" 15 #include "FileDescriptor.h" 16 #include "GeckoProfiler.h" 17 #include "InputStreamUtils.h" 18 #include "mozilla/Assertions.h" 19 #include "mozilla/Atomics.h" 20 #include "mozilla/ClearOnShutdown.h" 21 #include "mozilla/DebugOnly.h" 22 #include "mozilla/MozPromise.h" 23 #include "mozilla/Services.h" 24 #include "mozilla/SpinEventLoopUntil.h" 25 #include "mozilla/StaticPtr.h" 26 #include "mozilla/dom/ContentChild.h" 27 #include "mozilla/dom/ContentParent.h" 28 #include "mozilla/dom/File.h" 29 #include "mozilla/dom/WorkerPrivate.h" 30 #include "mozilla/dom/WorkerRef.h" 31 #include "mozilla/ipc/BackgroundStarterChild.h" 32 #include "mozilla/ipc/BackgroundStarterParent.h" 33 #include "mozilla/ipc/Endpoint.h" 34 #include "mozilla/ipc/PBackgroundStarter.h" 35 #include "mozilla/ipc/ProtocolTypes.h" 36 #include "nsCOMPtr.h" 37 #include "nsIEventTarget.h" 38 #include "nsIObserver.h" 39 #include "nsIObserverService.h" 40 #include "nsIRunnable.h" 41 #include "nsISupportsImpl.h" 42 #include "nsIThread.h" 43 #include "nsITimer.h" 44 #include "nsTArray.h" 45 #include "nsThreadUtils.h" 46 #include "nsTraceRefcnt.h" 47 #include "nsXULAppAPI.h" 48 #include "nsXPCOMPrivate.h" 49 #include "prthread.h" 50 51 #include <functional> 52 53 #ifdef RELEASE_OR_BETA 54 # define THREADSAFETY_ASSERT MOZ_ASSERT 55 #else 56 # define THREADSAFETY_ASSERT MOZ_RELEASE_ASSERT 57 #endif 58 59 #define CRASH_IN_CHILD_PROCESS(_msg) \ 60 do { \ 61 if (XRE_IsParentProcess()) { \ 62 MOZ_ASSERT(false, _msg); \ 63 } else { \ 64 MOZ_CRASH(_msg); \ 65 } \ 66 } while (0) 67 68 using namespace mozilla; 69 using namespace mozilla::dom; 70 using namespace mozilla::ipc; 71 using namespace mozilla::net; 72 73 namespace { 74 75 // This exists so that [Assert]IsOnBackgroundThread() can continue to work 76 // during shutdown. We can rely on TLS being 0 initialized, so only the 77 // background thread itself needs to ever set this flag. 78 static MOZ_THREAD_LOCAL(bool) sTLSIsOnBackgroundThread; 79 80 class ChildImpl; 81 82 // ----------------------------------------------------------------------------- 83 // Utility Functions 84 // ----------------------------------------------------------------------------- 85 86 void AssertIsOnMainThread() { THREADSAFETY_ASSERT(NS_IsMainThread()); } 87 88 // ----------------------------------------------------------------------------- 89 // ParentImpl Declaration 90 // ----------------------------------------------------------------------------- 91 92 class ParentImpl final : public BackgroundParentImpl { 93 friend class ChildImpl; 94 friend class mozilla::ipc::BackgroundParent; 95 friend class mozilla::ipc::BackgroundStarterParent; 96 97 private: 98 class ShutdownObserver; 99 100 struct MOZ_STACK_CLASS TimerCallbackClosure { 101 nsIThread* mThread; 102 nsTArray<IToplevelProtocol*>* mLiveActors; 103 104 TimerCallbackClosure(nsIThread* aThread, 105 nsTArray<IToplevelProtocol*>* aLiveActors) 106 : mThread(aThread), mLiveActors(aLiveActors) { 107 AssertIsInMainProcess(); 108 AssertIsOnMainThread(); 109 MOZ_ASSERT(aThread); 110 MOZ_ASSERT(aLiveActors); 111 } 112 }; 113 114 // The length of time we will wait at shutdown for all actors to clean 115 // themselves up before forcing them to be destroyed. 116 static const uint32_t kShutdownTimerDelayMS = 10000; 117 118 // This is only modified on the main thread. It is null if the thread does not 119 // exist or is shutting down. 120 static StaticRefPtr<nsIThread> sBackgroundThread; 121 122 // This is created and destroyed on the main thread but only modified on the 123 // background thread. It is specific to each instance of sBackgroundThread. 124 static nsTArray<IToplevelProtocol*>* sLiveActorsForBackgroundThread; 125 126 // This is only modified on the main thread. 127 static StaticRefPtr<nsITimer> sShutdownTimer; 128 129 // Maintains a count of live actors so that the background thread can be shut 130 // down when it is no longer needed. 131 // May be incremented on either the background thread (by an existing actor) 132 // or on the main thread, but must be decremented on the main thread. 133 static Atomic<uint64_t> sLiveActorCount; 134 135 // This is only modified on the main thread. It is true after the shutdown 136 // observer is registered and is never unset thereafter. 137 static bool sShutdownObserverRegistered; 138 139 // This is only modified on the main thread. It prevents us from trying to 140 // create the background thread after application shutdown has started. 141 static bool sShutdownHasStarted; 142 143 // null if this is a same-process actor. 144 const RefPtr<ThreadsafeContentParentHandle> mContent; 145 146 // Set when the actor is opened successfully and used to handle shutdown 147 // hangs. Only touched on the background thread. 148 nsTArray<IToplevelProtocol*>* mLiveActorArray; 149 150 // Set at construction to indicate whether this parent actor corresponds to a 151 // child actor in another process or to a child actor from a different thread 152 // in the same process. 153 const bool mIsOtherProcessActor; 154 155 // Set after ActorDestroy has been called. Only touched on the background 156 // thread. 157 bool mActorDestroyed; 158 159 public: 160 static already_AddRefed<nsISerialEventTarget> GetBackgroundThread() { 161 AssertIsInMainProcess(); 162 THREADSAFETY_ASSERT(NS_IsMainThread() || IsOnBackgroundThread()); 163 return do_AddRef(sBackgroundThread); 164 } 165 166 static bool IsOnBackgroundThread() { 167 MOZ_ASSERT(sTLSIsOnBackgroundThread.initialized()); 168 return sTLSIsOnBackgroundThread.get(); 169 } 170 171 static void AssertIsOnBackgroundThread() { 172 THREADSAFETY_ASSERT(IsOnBackgroundThread()); 173 } 174 175 // `ParentImpl` instances need to be deleted on the main thread, despite IPC 176 // controlling them on a background thread. Use `_WITH_DELETE_ON_MAIN_THREAD` 177 // to force destruction to occur on the desired thread. 178 NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DELETE_ON_MAIN_THREAD(ParentImpl, 179 override) 180 181 void Destroy(); 182 183 private: 184 // Forwarded from BackgroundParent. 185 static bool IsOtherProcessActor(PBackgroundParent* aBackgroundActor); 186 187 // Forwarded from BackgroundParent. 188 static ThreadsafeContentParentHandle* GetContentParentHandle( 189 PBackgroundParent* aBackgroundActor); 190 191 // Forwarded from BackgroundParent. 192 static uint64_t GetChildID(PBackgroundParent* aBackgroundActor); 193 194 // Forwarded from BackgroundParent. 195 static void KillHardAsync(PBackgroundParent* aBackgroundActor, 196 const nsACString& aReason); 197 198 // Forwarded from BackgroundParent. 199 static bool AllocStarter(ContentParent* aContent, 200 Endpoint<PBackgroundStarterParent>&& aEndpoint, 201 bool aCrossProcess = true); 202 203 static bool CreateBackgroundThread(); 204 205 static void ShutdownBackgroundThread(); 206 207 static void ShutdownTimerCallback(nsITimer* aTimer, void* aClosure); 208 209 // NOTE: ParentImpl could be used in 2 cases below. 210 // 1. Within the parent process. 211 // 2. Between parent process and content process. 212 // |aContent| should be not null for case 2. For cases 1, it's null. 213 explicit ParentImpl(ThreadsafeContentParentHandle* aContent, 214 bool aIsOtherProcessActor) 215 : mContent(aContent), 216 mLiveActorArray(nullptr), 217 mIsOtherProcessActor(aIsOtherProcessActor), 218 mActorDestroyed(false) { 219 AssertIsInMainProcess(); 220 MOZ_ASSERT_IF(!aIsOtherProcessActor, XRE_IsParentProcess()); 221 } 222 223 ~ParentImpl() { 224 AssertIsInMainProcess(); 225 AssertIsOnMainThread(); 226 } 227 228 void MainThreadActorDestroy(); 229 230 void SetLiveActorArray(nsTArray<IToplevelProtocol*>* aLiveActorArray) { 231 AssertIsInMainProcess(); 232 AssertIsOnBackgroundThread(); 233 MOZ_ASSERT(aLiveActorArray); 234 MOZ_ASSERT(!aLiveActorArray->Contains(this)); 235 MOZ_ASSERT(!mLiveActorArray); 236 MOZ_ASSERT(mIsOtherProcessActor); 237 238 mLiveActorArray = aLiveActorArray; 239 mLiveActorArray->AppendElement(this); 240 } 241 242 // These methods are only called by IPDL. 243 virtual void ActorDestroy(ActorDestroyReason aWhy) override; 244 }; 245 246 // ----------------------------------------------------------------------------- 247 // ChildImpl Declaration 248 // ----------------------------------------------------------------------------- 249 250 class ChildImpl final : public BackgroundChildImpl { 251 friend class mozilla::ipc::BackgroundChild; 252 friend class mozilla::ipc::BackgroundChildImpl; 253 friend class mozilla::ipc::BackgroundStarterChild; 254 255 typedef base::ProcessId ProcessId; 256 257 class ShutdownObserver; 258 259 public: 260 struct ThreadLocalInfo { 261 ThreadLocalInfo() 262 #ifdef DEBUG 263 : mClosed(false) 264 #endif 265 { 266 } 267 268 RefPtr<ChildImpl> mActor; 269 UniquePtr<BackgroundChildImpl::ThreadLocal> mConsumerThreadLocal; 270 #ifdef DEBUG 271 bool mClosed; 272 #endif 273 }; 274 275 private: 276 // A thread-local index that is not valid. 277 static constexpr unsigned int kBadThreadLocalIndex = 278 static_cast<unsigned int>(-1); 279 280 // ThreadInfoWrapper encapsulates ThreadLocalInfo and ThreadLocalIndex and 281 // also provides some common functions for creating PBackground IPC actor. 282 class ThreadInfoWrapper final { 283 friend class ChildImpl; 284 285 public: 286 using ActorCreateFunc = void (*)(ThreadLocalInfo*, unsigned int, 287 nsIEventTarget*, ChildImpl**); 288 289 ThreadInfoWrapper() = default; 290 291 void Startup() { 292 MOZ_ASSERT(mThreadLocalIndex == kBadThreadLocalIndex, 293 "ThreadInfoWrapper::Startup() called more than once!"); 294 295 PRStatus status = 296 PR_NewThreadPrivateIndex(&mThreadLocalIndex, ThreadLocalDestructor); 297 MOZ_RELEASE_ASSERT(status == PR_SUCCESS, 298 "PR_NewThreadPrivateIndex failed!"); 299 300 MOZ_ASSERT(mThreadLocalIndex != kBadThreadLocalIndex); 301 } 302 303 void Shutdown() { 304 if (sShutdownHasStarted) { 305 MOZ_ASSERT_IF(mThreadLocalIndex != kBadThreadLocalIndex, 306 !PR_GetThreadPrivate(mThreadLocalIndex)); 307 return; 308 } 309 310 if (mThreadLocalIndex == kBadThreadLocalIndex) { 311 return; 312 } 313 314 RefPtr<BackgroundStarterChild> starter; 315 { 316 auto lock = mStarter.Lock(); 317 starter = lock->forget(); 318 } 319 if (starter) { 320 CloseStarter(starter); 321 } 322 323 ThreadLocalInfo* threadLocalInfo; 324 #ifdef DEBUG 325 threadLocalInfo = 326 static_cast<ThreadLocalInfo*>(PR_GetThreadPrivate(mThreadLocalIndex)); 327 MOZ_ASSERT(!threadLocalInfo); 328 #endif 329 330 threadLocalInfo = mMainThreadInfo; 331 if (threadLocalInfo) { 332 #ifdef DEBUG 333 MOZ_ASSERT(!threadLocalInfo->mClosed); 334 threadLocalInfo->mClosed = true; 335 #endif 336 337 ThreadLocalDestructor(threadLocalInfo); 338 mMainThreadInfo = nullptr; 339 } 340 } 341 342 template <typename Actor> 343 void InitStarter(Actor* aActor) { 344 AssertIsOnMainThread(); 345 346 // Create a pair of endpoints and send them to the other process. 347 Endpoint<PBackgroundStarterParent> parent; 348 Endpoint<PBackgroundStarterChild> child; 349 MOZ_ALWAYS_SUCCEEDS(PBackgroundStarter::CreateEndpoints( 350 aActor->OtherEndpointProcInfo(), EndpointProcInfo::Current(), &parent, 351 &child)); 352 MOZ_ALWAYS_TRUE(aActor->SendInitBackground(std::move(parent))); 353 354 InitStarter(std::move(child)); 355 } 356 357 void InitStarter(Endpoint<PBackgroundStarterChild>&& aEndpoint) { 358 AssertIsOnMainThread(); 359 360 EndpointProcInfo otherProcInfo = aEndpoint.OtherEndpointProcInfo(); 361 362 nsCOMPtr<nsISerialEventTarget> taskQueue; 363 MOZ_ALWAYS_SUCCEEDS(NS_CreateBackgroundTaskQueue( 364 "PBackgroundStarter Queue", getter_AddRefs(taskQueue))); 365 366 RefPtr<BackgroundStarterChild> starter = 367 new BackgroundStarterChild(otherProcInfo, taskQueue); 368 369 taskQueue->Dispatch(NS_NewRunnableFunction( 370 "PBackgroundStarterChild Init", 371 [starter, endpoint = std::move(aEndpoint)]() mutable { 372 MOZ_ALWAYS_TRUE(endpoint.Bind(starter)); 373 })); 374 375 // Swap in the newly initialized `BackgroundStarterChild`, and close the 376 // previous one if we're replacing an existing PBackgroundStarterChild 377 // instance. 378 RefPtr<BackgroundStarterChild> prevStarter; 379 { 380 auto lock = mStarter.Lock(); 381 prevStarter = lock->forget(); 382 *lock = starter.forget(); 383 } 384 if (prevStarter) { 385 CloseStarter(prevStarter); 386 } 387 } 388 389 void CloseForCurrentThread() { 390 MOZ_ASSERT(!NS_IsMainThread()); 391 392 if (mThreadLocalIndex == kBadThreadLocalIndex) { 393 return; 394 } 395 396 auto* threadLocalInfo = 397 static_cast<ThreadLocalInfo*>(PR_GetThreadPrivate(mThreadLocalIndex)); 398 399 if (!threadLocalInfo) { 400 return; 401 } 402 403 #ifdef DEBUG 404 MOZ_ASSERT(!threadLocalInfo->mClosed); 405 threadLocalInfo->mClosed = true; 406 #endif 407 408 // Clearing the thread local will synchronously close the actor. 409 DebugOnly<PRStatus> status = 410 PR_SetThreadPrivate(mThreadLocalIndex, nullptr); 411 MOZ_ASSERT(status == PR_SUCCESS); 412 } 413 414 PBackgroundChild* GetOrCreateForCurrentThread() { 415 // Processes can be told to do final CC's during shutdown even though 416 // they never finished starting (and thus call this), because they 417 // hadn't gotten far enough to call Startup() before shutdown began. 418 if (mThreadLocalIndex == kBadThreadLocalIndex) { 419 NS_ERROR("BackgroundChild::Startup() was never called"); 420 return nullptr; 421 } 422 if (NS_IsMainThread() && ChildImpl::sShutdownHasStarted) { 423 return nullptr; 424 } 425 426 auto* threadLocalInfo = NS_IsMainThread() 427 ? mMainThreadInfo 428 : static_cast<ThreadLocalInfo*>( 429 PR_GetThreadPrivate(mThreadLocalIndex)); 430 431 if (!threadLocalInfo) { 432 auto newInfo = MakeUnique<ThreadLocalInfo>(); 433 434 if (NS_IsMainThread()) { 435 mMainThreadInfo = newInfo.get(); 436 } else { 437 if (PR_SetThreadPrivate(mThreadLocalIndex, newInfo.get()) != 438 PR_SUCCESS) { 439 CRASH_IN_CHILD_PROCESS("PR_SetThreadPrivate failed!"); 440 return nullptr; 441 } 442 } 443 444 threadLocalInfo = newInfo.release(); 445 } 446 447 if (threadLocalInfo->mActor) { 448 return threadLocalInfo->mActor; 449 } 450 451 RefPtr<BackgroundStarterChild> starter; 452 { 453 auto lock = mStarter.Lock(); 454 starter = *lock; 455 } 456 if (!starter) { 457 CRASH_IN_CHILD_PROCESS("No BackgroundStarterChild"); 458 return nullptr; 459 } 460 461 Endpoint<PBackgroundParent> parent; 462 Endpoint<PBackgroundChild> child; 463 nsresult rv; 464 rv = PBackground::CreateEndpoints(starter->mOtherProcInfo, 465 EndpointProcInfo::Current(), &parent, 466 &child); 467 if (NS_FAILED(rv)) { 468 NS_WARNING("Failed to create top level actor!"); 469 return nullptr; 470 } 471 472 RefPtr<ChildImpl> strongActor = new ChildImpl(); 473 if (!child.Bind(strongActor)) { 474 CRASH_IN_CHILD_PROCESS("Failed to bind ChildImpl!"); 475 return nullptr; 476 } 477 strongActor->SetActorAlive(); 478 threadLocalInfo->mActor = strongActor; 479 480 // Dispatch to the background task queue to create the relevant actor in 481 // the remote process. 482 starter->mTaskQueue->Dispatch(NS_NewRunnableFunction( 483 "PBackground GetOrCreateForCurrentThread", 484 [starter, endpoint = std::move(parent)]() mutable { 485 if (!starter->SendInitBackground(std::move(endpoint))) { 486 NS_WARNING("Failed to create toplevel actor"); 487 } 488 })); 489 return strongActor; 490 } 491 492 private: 493 static void CloseStarter(BackgroundStarterChild* aStarter) { 494 aStarter->mTaskQueue->Dispatch(NS_NewRunnableFunction( 495 "PBackgroundStarterChild Close", 496 [starter = RefPtr{aStarter}] { starter->Close(); })); 497 } 498 499 // This is only modified on the main thread. It is the thread-local index 500 // that we use to store the BackgroundChild for each thread. 501 unsigned int mThreadLocalIndex = kBadThreadLocalIndex; 502 503 // On the main thread, we store TLS in this global instead of in 504 // mThreadLocalIndex. That way, cooperative main threads all share the same 505 // thread info. 506 ThreadLocalInfo* mMainThreadInfo = nullptr; 507 508 // The starter which will be used to launch PBackground instances of this 509 // type. Only modified on the main thread, but may be read by any thread 510 // wanting to start background actors. 511 StaticDataMutex<StaticRefPtr<BackgroundStarterChild>> mStarter{"mStarter"}; 512 }; 513 514 // For PBackground between parent and content process. 515 static ThreadInfoWrapper sParentAndContentProcessThreadInfo; 516 517 // This is only modified on the main thread. It prevents us from trying to 518 // create the background thread after application shutdown has started. 519 static bool sShutdownHasStarted; 520 521 #if defined(DEBUG) || !defined(RELEASE_OR_BETA) 522 nsISerialEventTarget* mOwningEventTarget; 523 #endif 524 525 #ifdef DEBUG 526 bool mActorWasAlive; 527 bool mActorDestroyed; 528 #endif 529 530 public: 531 static void Shutdown(); 532 533 void AssertIsOnOwningThread() { 534 THREADSAFETY_ASSERT(mOwningEventTarget); 535 536 #ifdef RELEASE_OR_BETA 537 DebugOnly<bool> current; 538 #else 539 bool current; 540 #endif 541 THREADSAFETY_ASSERT( 542 NS_SUCCEEDED(mOwningEventTarget->IsOnCurrentThread(¤t))); 543 THREADSAFETY_ASSERT(current); 544 } 545 546 void AssertActorDestroyed() { 547 MOZ_ASSERT(mActorDestroyed, "ChildImpl::ActorDestroy not called in time"); 548 } 549 550 explicit ChildImpl() 551 #if defined(DEBUG) || !defined(RELEASE_OR_BETA) 552 : mOwningEventTarget(GetCurrentSerialEventTarget()) 553 #endif 554 #ifdef DEBUG 555 , 556 mActorWasAlive(false), 557 mActorDestroyed(false) 558 #endif 559 { 560 AssertIsOnOwningThread(); 561 } 562 563 void SetActorAlive() { 564 AssertIsOnOwningThread(); 565 MOZ_ASSERT(!mActorWasAlive); 566 MOZ_ASSERT(!mActorDestroyed); 567 568 #ifdef DEBUG 569 mActorWasAlive = true; 570 #endif 571 } 572 573 // This type is threadsafe refcounted as actors managed by it may be destroyed 574 // after the thread it is bound to dies, and hold a reference to this object. 575 // 576 // It is _not_ safe to use this type or any methods on it from off of the 577 // thread it was created for. 578 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ChildImpl, override) 579 580 private: 581 // Forwarded from BackgroundChild. 582 static void Startup(); 583 584 // Forwarded from BackgroundChild. 585 static PBackgroundChild* GetForCurrentThread(); 586 587 // Forwarded from BackgroundChild. 588 static PBackgroundChild* GetOrCreateForCurrentThread(); 589 590 static void CloseForCurrentThread(); 591 592 // Forwarded from BackgroundChildImpl. 593 static BackgroundChildImpl::ThreadLocal* GetThreadLocalForCurrentThread(); 594 595 // Forwarded from BackgroundChild. 596 static void InitContentStarter(mozilla::dom::ContentChild* aContent); 597 598 static void ThreadLocalDestructor(void* aThreadLocal); 599 600 // This class is reference counted. 601 ~ChildImpl() { MOZ_ASSERT_IF(mActorWasAlive, mActorDestroyed); } 602 603 // Only called by IPDL. 604 virtual void ActorDestroy(ActorDestroyReason aWhy) override; 605 }; 606 607 // ----------------------------------------------------------------------------- 608 // ParentImpl Helper Declarations 609 // ----------------------------------------------------------------------------- 610 611 class ParentImpl::ShutdownObserver final : public nsIObserver { 612 public: 613 ShutdownObserver() { AssertIsOnMainThread(); } 614 615 NS_DECL_ISUPPORTS 616 NS_DECL_NSIOBSERVER 617 618 private: 619 ~ShutdownObserver() { AssertIsOnMainThread(); } 620 }; 621 622 // ----------------------------------------------------------------------------- 623 // ChildImpl Helper Declarations 624 // ----------------------------------------------------------------------------- 625 626 class ChildImpl::ShutdownObserver final : public nsIObserver { 627 public: 628 ShutdownObserver() { AssertIsOnMainThread(); } 629 630 NS_DECL_ISUPPORTS 631 NS_DECL_NSIOBSERVER 632 633 private: 634 ~ShutdownObserver() { AssertIsOnMainThread(); } 635 }; 636 637 } // namespace 638 639 namespace mozilla { 640 namespace ipc { 641 642 bool IsOnBackgroundThread() { return ParentImpl::IsOnBackgroundThread(); } 643 644 #ifdef DEBUG 645 646 void AssertIsOnBackgroundThread() { ParentImpl::AssertIsOnBackgroundThread(); } 647 648 #endif // DEBUG 649 650 } // namespace ipc 651 } // namespace mozilla 652 653 // ----------------------------------------------------------------------------- 654 // BackgroundParent Public Methods 655 // ----------------------------------------------------------------------------- 656 657 // static 658 already_AddRefed<nsISerialEventTarget> BackgroundParent::GetBackgroundThread() { 659 return ParentImpl::GetBackgroundThread(); 660 } 661 662 // static 663 bool BackgroundParent::IsOtherProcessActor( 664 PBackgroundParent* aBackgroundActor) { 665 return ParentImpl::IsOtherProcessActor(aBackgroundActor); 666 } 667 668 // static 669 ThreadsafeContentParentHandle* BackgroundParent::GetContentParentHandle( 670 PBackgroundParent* aBackgroundActor) { 671 return ParentImpl::GetContentParentHandle(aBackgroundActor); 672 } 673 674 // static 675 uint64_t BackgroundParent::GetChildID(PBackgroundParent* aBackgroundActor) { 676 return ParentImpl::GetChildID(aBackgroundActor); 677 } 678 679 // static 680 void BackgroundParent::KillHardAsync(PBackgroundParent* aBackgroundActor, 681 const nsACString& aReason) { 682 ParentImpl::KillHardAsync(aBackgroundActor, aReason); 683 } 684 685 // static 686 bool BackgroundParent::AllocStarter( 687 ContentParent* aContent, Endpoint<PBackgroundStarterParent>&& aEndpoint) { 688 return ParentImpl::AllocStarter(aContent, std::move(aEndpoint)); 689 } 690 691 // ----------------------------------------------------------------------------- 692 // BackgroundChild Public Methods 693 // ----------------------------------------------------------------------------- 694 695 // static 696 void BackgroundChild::Startup() { ChildImpl::Startup(); } 697 698 // static 699 PBackgroundChild* BackgroundChild::GetForCurrentThread() { 700 return ChildImpl::GetForCurrentThread(); 701 } 702 703 // static 704 PBackgroundChild* BackgroundChild::GetOrCreateForCurrentThread() { 705 return ChildImpl::GetOrCreateForCurrentThread(); 706 } 707 708 // static 709 void BackgroundChild::CloseForCurrentThread() { 710 ChildImpl::CloseForCurrentThread(); 711 } 712 713 // static 714 void BackgroundChild::InitContentStarter(ContentChild* aContent) { 715 ChildImpl::InitContentStarter(aContent); 716 } 717 718 // ----------------------------------------------------------------------------- 719 // BackgroundChildImpl Public Methods 720 // ----------------------------------------------------------------------------- 721 722 // static 723 BackgroundChildImpl::ThreadLocal* 724 BackgroundChildImpl::GetThreadLocalForCurrentThread() { 725 return ChildImpl::GetThreadLocalForCurrentThread(); 726 } 727 728 // ----------------------------------------------------------------------------- 729 // ParentImpl Static Members 730 // ----------------------------------------------------------------------------- 731 732 StaticRefPtr<nsIThread> ParentImpl::sBackgroundThread; 733 734 nsTArray<IToplevelProtocol*>* ParentImpl::sLiveActorsForBackgroundThread; 735 736 StaticRefPtr<nsITimer> ParentImpl::sShutdownTimer; 737 738 Atomic<uint64_t> ParentImpl::sLiveActorCount; 739 740 bool ParentImpl::sShutdownObserverRegistered = false; 741 742 bool ParentImpl::sShutdownHasStarted = false; 743 744 // ----------------------------------------------------------------------------- 745 // ChildImpl Static Members 746 // ----------------------------------------------------------------------------- 747 748 MOZ_RUNINIT ChildImpl::ThreadInfoWrapper 749 ChildImpl::sParentAndContentProcessThreadInfo; 750 751 bool ChildImpl::sShutdownHasStarted = false; 752 753 // ----------------------------------------------------------------------------- 754 // ParentImpl Implementation 755 // ----------------------------------------------------------------------------- 756 757 // static 758 bool ParentImpl::IsOtherProcessActor(PBackgroundParent* aBackgroundActor) { 759 AssertIsOnBackgroundThread(); 760 MOZ_ASSERT(aBackgroundActor); 761 762 return static_cast<ParentImpl*>(aBackgroundActor)->mIsOtherProcessActor; 763 } 764 765 // static 766 ThreadsafeContentParentHandle* ParentImpl::GetContentParentHandle( 767 PBackgroundParent* aBackgroundActor) { 768 AssertIsOnBackgroundThread(); 769 MOZ_ASSERT(aBackgroundActor); 770 771 return static_cast<ParentImpl*>(aBackgroundActor)->mContent.get(); 772 } 773 774 // static 775 uint64_t ParentImpl::GetChildID(PBackgroundParent* aBackgroundActor) { 776 AssertIsOnBackgroundThread(); 777 MOZ_ASSERT(aBackgroundActor); 778 779 auto actor = static_cast<ParentImpl*>(aBackgroundActor); 780 if (actor->mContent) { 781 return actor->mContent->ChildID(); 782 } 783 784 return 0; 785 } 786 787 // static 788 void ParentImpl::KillHardAsync(PBackgroundParent* aBackgroundActor, 789 const nsACString& aReason) { 790 AssertIsInMainProcess(); 791 AssertIsOnBackgroundThread(); 792 MOZ_ASSERT(aBackgroundActor); 793 MOZ_ASSERT(BackgroundParent::IsOtherProcessActor(aBackgroundActor)); 794 795 RefPtr<ThreadsafeContentParentHandle> handle = 796 GetContentParentHandle(aBackgroundActor); 797 MOZ_ASSERT(handle); 798 799 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread( 800 NS_NewRunnableFunction( 801 "ParentImpl::KillHardAsync", 802 [handle = std::move(handle), reason = nsCString{aReason}]() { 803 mozilla::AssertIsOnMainThread(); 804 805 if (RefPtr<ContentParent> contentParent = 806 handle->GetContentParent()) { 807 contentParent->KillHard(reason.get()); 808 } 809 }), 810 NS_DISPATCH_NORMAL)); 811 812 // After we've scheduled killing of the remote process, also ensure we induce 813 // a connection error in the IPC channel to immediately stop all IPC 814 // communication on this channel. 815 if (aBackgroundActor->CanSend()) { 816 aBackgroundActor->GetIPCChannel()->InduceConnectionError(); 817 } 818 } 819 820 // static 821 bool ParentImpl::AllocStarter(ContentParent* aContent, 822 Endpoint<PBackgroundStarterParent>&& aEndpoint, 823 bool aCrossProcess) { 824 AssertIsInMainProcess(); 825 AssertIsOnMainThread(); 826 827 MOZ_ASSERT(aEndpoint.IsValid()); 828 829 if (!sBackgroundThread && !CreateBackgroundThread()) { 830 NS_WARNING("Failed to create background thread!"); 831 return false; 832 } 833 834 sLiveActorCount++; 835 836 RefPtr<BackgroundStarterParent> actor = new BackgroundStarterParent( 837 aContent ? aContent->ThreadsafeHandle() : nullptr, aCrossProcess); 838 839 if (NS_FAILED(sBackgroundThread->Dispatch(NS_NewRunnableFunction( 840 "BackgroundStarterParent::ConnectActorRunnable", 841 [actor = std::move(actor), endpoint = std::move(aEndpoint), 842 liveActorArray = sLiveActorsForBackgroundThread]() mutable { 843 MOZ_ASSERT(endpoint.IsValid()); 844 MOZ_ALWAYS_TRUE(endpoint.Bind(actor)); 845 actor->SetLiveActorArray(liveActorArray); 846 })))) { 847 NS_WARNING("Failed to dispatch connect runnable!"); 848 849 MOZ_ASSERT(sLiveActorCount); 850 sLiveActorCount--; 851 } 852 853 return true; 854 } 855 856 // static 857 bool ParentImpl::CreateBackgroundThread() { 858 AssertIsInMainProcess(); 859 AssertIsOnMainThread(); 860 MOZ_ASSERT(!sBackgroundThread); 861 MOZ_ASSERT(!sLiveActorsForBackgroundThread); 862 863 if (sShutdownHasStarted) { 864 NS_WARNING( 865 "Trying to create background thread after shutdown has " 866 "already begun!"); 867 return false; 868 } 869 870 nsCOMPtr<nsITimer> newShutdownTimer; 871 872 if (!sShutdownTimer) { 873 newShutdownTimer = NS_NewTimer(); 874 if (!newShutdownTimer) { 875 return false; 876 } 877 } 878 879 if (!sShutdownObserverRegistered) { 880 nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); 881 if (NS_WARN_IF(!obs)) { 882 return false; 883 } 884 885 nsCOMPtr<nsIObserver> observer = new ShutdownObserver(); 886 887 nsresult rv = obs->AddObserver( 888 observer, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, false); 889 if (NS_WARN_IF(NS_FAILED(rv))) { 890 return false; 891 } 892 893 sShutdownObserverRegistered = true; 894 } 895 896 nsCOMPtr<nsIThread> thread; 897 if (NS_FAILED(NS_NewNamedThread( 898 "IPDL Background", getter_AddRefs(thread), 899 NS_NewRunnableFunction( 900 "Background::ParentImpl::CreateBackgroundThreadRunnable", []() { 901 MOZ_ASSERT(sTLSIsOnBackgroundThread.initialized()); 902 sTLSIsOnBackgroundThread.set(true); 903 })))) { 904 NS_WARNING("NS_NewNamedThread failed!"); 905 return false; 906 } 907 908 sBackgroundThread = thread.forget(); 909 910 sLiveActorsForBackgroundThread = new nsTArray<IToplevelProtocol*>(1); 911 912 if (!sShutdownTimer) { 913 MOZ_ASSERT(newShutdownTimer); 914 sShutdownTimer = newShutdownTimer; 915 } 916 917 return true; 918 } 919 920 // static 921 void ParentImpl::ShutdownBackgroundThread() { 922 AssertIsInMainProcess(); 923 AssertIsOnMainThread(); 924 MOZ_ASSERT(sShutdownHasStarted); 925 MOZ_ASSERT_IF(!sBackgroundThread, !sLiveActorCount); 926 MOZ_ASSERT_IF(sBackgroundThread, sShutdownTimer); 927 928 nsCOMPtr<nsITimer> shutdownTimer = sShutdownTimer.get(); 929 sShutdownTimer = nullptr; 930 931 if (sBackgroundThread) { 932 nsCOMPtr<nsIThread> thread = sBackgroundThread.get(); 933 sBackgroundThread = nullptr; 934 935 UniquePtr<nsTArray<IToplevelProtocol*>> liveActors( 936 sLiveActorsForBackgroundThread); 937 sLiveActorsForBackgroundThread = nullptr; 938 939 MOZ_ASSERT_IF(!sShutdownHasStarted, !sLiveActorCount); 940 941 if (sLiveActorCount) { 942 // We need to spin the event loop while we wait for all the actors to be 943 // cleaned up. We also set a timeout to force-kill any hanging actors. 944 TimerCallbackClosure closure(thread, liveActors.get()); 945 946 MOZ_ALWAYS_SUCCEEDS(shutdownTimer->InitWithNamedFuncCallback( 947 &ShutdownTimerCallback, &closure, kShutdownTimerDelayMS, 948 nsITimer::TYPE_ONE_SHOT, "ParentImpl::ShutdownTimerCallback"_ns)); 949 950 SpinEventLoopUntil("ParentImpl::ShutdownBackgroundThread"_ns, 951 [&]() { return !sLiveActorCount; }); 952 953 MOZ_ASSERT(liveActors->IsEmpty()); 954 955 MOZ_ALWAYS_SUCCEEDS(shutdownTimer->Cancel()); 956 } 957 958 // Shutdown will process all remaining events. 959 MOZ_ALWAYS_SUCCEEDS(thread->Shutdown()); 960 } 961 } 962 963 // static 964 void ParentImpl::ShutdownTimerCallback(nsITimer* aTimer, void* aClosure) { 965 AssertIsInMainProcess(); 966 AssertIsOnMainThread(); 967 MOZ_ASSERT(sShutdownHasStarted); 968 MOZ_ASSERT(sLiveActorCount); 969 970 auto closure = static_cast<TimerCallbackClosure*>(aClosure); 971 MOZ_ASSERT(closure); 972 973 // Don't let the stack unwind until the ForceCloseBackgroundActorsRunnable has 974 // finished. 975 sLiveActorCount++; 976 977 InvokeAsync( 978 closure->mThread, __func__, 979 [liveActors = closure->mLiveActors]() { 980 MOZ_ASSERT(liveActors); 981 982 if (!liveActors->IsEmpty()) { 983 // Copy the array since calling Close() could mutate the 984 // actual array. 985 nsTArray<IToplevelProtocol*> actorsToClose(liveActors->Clone()); 986 for (IToplevelProtocol* actor : actorsToClose) { 987 actor->Close(); 988 } 989 } 990 return GenericPromise::CreateAndResolve(true, __func__); 991 }) 992 ->Then(GetCurrentSerialEventTarget(), __func__, []() { 993 MOZ_ASSERT(sLiveActorCount); 994 sLiveActorCount--; 995 }); 996 } 997 998 void ParentImpl::Destroy() { 999 // May be called on any thread! 1000 1001 AssertIsInMainProcess(); 1002 1003 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread( 1004 NewNonOwningRunnableMethod("ParentImpl::MainThreadActorDestroy", this, 1005 &ParentImpl::MainThreadActorDestroy))); 1006 } 1007 1008 void ParentImpl::MainThreadActorDestroy() { 1009 AssertIsInMainProcess(); 1010 AssertIsOnMainThread(); 1011 MOZ_ASSERT_IF(!mIsOtherProcessActor, !mContent); 1012 1013 MOZ_ASSERT(sLiveActorCount); 1014 sLiveActorCount--; 1015 1016 // This may be the last reference! 1017 Release(); 1018 } 1019 1020 void ParentImpl::ActorDestroy(ActorDestroyReason aWhy) { 1021 AssertIsInMainProcess(); 1022 AssertIsOnBackgroundThread(); 1023 MOZ_ASSERT(!mActorDestroyed); 1024 MOZ_ASSERT_IF(mIsOtherProcessActor, mLiveActorArray); 1025 1026 BackgroundParentImpl::ActorDestroy(aWhy); 1027 1028 mActorDestroyed = true; 1029 1030 if (mLiveActorArray) { 1031 MOZ_ALWAYS_TRUE(mLiveActorArray->RemoveElement(this)); 1032 mLiveActorArray = nullptr; 1033 } 1034 1035 // This is tricky. We should be able to call Destroy() here directly because 1036 // we're not going to touch 'this' or our MessageChannel any longer on this 1037 // thread. Destroy() dispatches the MainThreadActorDestroy runnable and when 1038 // it runs it will destroy 'this' and our associated MessageChannel. However, 1039 // IPDL is about to call MessageChannel::Clear() on this thread! To avoid 1040 // racing with the main thread we must ensure that the MessageChannel lives 1041 // long enough to be cleared in this call stack. 1042 1043 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(NewNonOwningRunnableMethod( 1044 "ParentImpl::Destroy", this, &ParentImpl::Destroy))); 1045 } 1046 1047 NS_IMPL_ISUPPORTS(ParentImpl::ShutdownObserver, nsIObserver) 1048 1049 NS_IMETHODIMP 1050 ParentImpl::ShutdownObserver::Observe(nsISupports* aSubject, const char* aTopic, 1051 const char16_t* aData) { 1052 AssertIsInMainProcess(); 1053 AssertIsOnMainThread(); 1054 MOZ_ASSERT(!sShutdownHasStarted); 1055 MOZ_ASSERT(!strcmp(aTopic, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID)); 1056 1057 sShutdownHasStarted = true; 1058 1059 // Do this first before calling (and spinning the event loop in) 1060 // ShutdownBackgroundThread(). 1061 ChildImpl::Shutdown(); 1062 1063 ShutdownBackgroundThread(); 1064 1065 return NS_OK; 1066 } 1067 1068 BackgroundStarterParent::BackgroundStarterParent( 1069 ThreadsafeContentParentHandle* aContent, bool aCrossProcess) 1070 : mCrossProcess(aCrossProcess), mContent(aContent) { 1071 AssertIsOnMainThread(); 1072 AssertIsInMainProcess(); 1073 MOZ_ASSERT_IF(!mCrossProcess, !mContent); 1074 MOZ_ASSERT_IF(!mCrossProcess, XRE_IsParentProcess()); 1075 } 1076 1077 void BackgroundStarterParent::SetLiveActorArray( 1078 nsTArray<IToplevelProtocol*>* aLiveActorArray) { 1079 AssertIsInMainProcess(); 1080 AssertIsOnBackgroundThread(); 1081 MOZ_ASSERT(aLiveActorArray); 1082 MOZ_ASSERT(!aLiveActorArray->Contains(this)); 1083 MOZ_ASSERT(!mLiveActorArray); 1084 MOZ_ASSERT_IF(!mCrossProcess, OtherPid() == base::GetCurrentProcId()); 1085 1086 mLiveActorArray = aLiveActorArray; 1087 mLiveActorArray->AppendElement(this); 1088 } 1089 1090 IPCResult BackgroundStarterParent::RecvInitBackground( 1091 Endpoint<PBackgroundParent>&& aEndpoint) { 1092 AssertIsOnBackgroundThread(); 1093 1094 if (!aEndpoint.IsValid()) { 1095 return IPC_FAIL(this, 1096 "Cannot initialize PBackground with invalid endpoint"); 1097 } 1098 1099 ParentImpl* actor = new ParentImpl(mContent, mCrossProcess); 1100 1101 // Take a reference on this thread. If Open() fails then we will release this 1102 // reference in Destroy. 1103 NS_ADDREF(actor); 1104 1105 ParentImpl::sLiveActorCount++; 1106 1107 if (!aEndpoint.Bind(actor)) { 1108 actor->Destroy(); 1109 return IPC_OK(); 1110 } 1111 1112 if (mCrossProcess) { 1113 actor->SetLiveActorArray(mLiveActorArray); 1114 } 1115 return IPC_OK(); 1116 } 1117 1118 void BackgroundStarterParent::ActorDestroy(ActorDestroyReason aReason) { 1119 AssertIsOnBackgroundThread(); 1120 1121 if (mLiveActorArray) { 1122 MOZ_ALWAYS_TRUE(mLiveActorArray->RemoveElement(this)); 1123 mLiveActorArray = nullptr; 1124 } 1125 1126 // Make sure to decrement `sLiveActorCount` on the main thread. 1127 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread( 1128 NS_NewRunnableFunction("BackgroundStarterParent::MainThreadDestroy", 1129 [] { ParentImpl::sLiveActorCount--; }))); 1130 } 1131 1132 // ----------------------------------------------------------------------------- 1133 // ChildImpl Implementation 1134 // ----------------------------------------------------------------------------- 1135 1136 // static 1137 void ChildImpl::Startup() { 1138 // This happens on the main thread but before XPCOM has started so we can't 1139 // assert that we're being called on the main thread here. 1140 1141 sParentAndContentProcessThreadInfo.Startup(); 1142 1143 sTLSIsOnBackgroundThread.infallibleInit(); 1144 1145 nsCOMPtr<nsIObserverService> observerService = services::GetObserverService(); 1146 MOZ_RELEASE_ASSERT(observerService); 1147 1148 nsCOMPtr<nsIObserver> observer = new ShutdownObserver(); 1149 1150 nsresult rv = observerService->AddObserver( 1151 observer, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, false); 1152 MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); 1153 1154 // Initialize a starter actor to allow starting PBackground within the parent 1155 // process. 1156 if (XRE_IsParentProcess()) { 1157 Endpoint<PBackgroundStarterParent> parent; 1158 Endpoint<PBackgroundStarterChild> child; 1159 MOZ_ALWAYS_SUCCEEDS(PBackgroundStarter::CreateEndpoints( 1160 EndpointProcInfo::Current(), EndpointProcInfo::Current(), &parent, 1161 &child)); 1162 1163 MOZ_ALWAYS_TRUE(ParentImpl::AllocStarter(nullptr, std::move(parent), 1164 /* aCrossProcess */ false)); 1165 sParentAndContentProcessThreadInfo.InitStarter(std::move(child)); 1166 } 1167 } 1168 1169 // static 1170 void ChildImpl::Shutdown() { 1171 AssertIsOnMainThread(); 1172 1173 sParentAndContentProcessThreadInfo.Shutdown(); 1174 1175 sShutdownHasStarted = true; 1176 } 1177 1178 // static 1179 PBackgroundChild* ChildImpl::GetForCurrentThread() { 1180 MOZ_ASSERT(sParentAndContentProcessThreadInfo.mThreadLocalIndex != 1181 kBadThreadLocalIndex); 1182 1183 auto threadLocalInfo = 1184 NS_IsMainThread() 1185 ? sParentAndContentProcessThreadInfo.mMainThreadInfo 1186 : static_cast<ThreadLocalInfo*>(PR_GetThreadPrivate( 1187 sParentAndContentProcessThreadInfo.mThreadLocalIndex)); 1188 1189 if (!threadLocalInfo) { 1190 return nullptr; 1191 } 1192 1193 return threadLocalInfo->mActor; 1194 } 1195 1196 /* static */ 1197 PBackgroundChild* ChildImpl::GetOrCreateForCurrentThread() { 1198 return sParentAndContentProcessThreadInfo.GetOrCreateForCurrentThread(); 1199 } 1200 1201 // static 1202 void ChildImpl::CloseForCurrentThread() { 1203 MOZ_ASSERT(!NS_IsMainThread(), 1204 "PBackground for the main thread should be shut down via " 1205 "ChildImpl::Shutdown()."); 1206 1207 sParentAndContentProcessThreadInfo.CloseForCurrentThread(); 1208 } 1209 1210 // static 1211 BackgroundChildImpl::ThreadLocal* ChildImpl::GetThreadLocalForCurrentThread() { 1212 MOZ_ASSERT(sParentAndContentProcessThreadInfo.mThreadLocalIndex != 1213 kBadThreadLocalIndex, 1214 "BackgroundChild::Startup() was never called!"); 1215 1216 auto threadLocalInfo = 1217 NS_IsMainThread() 1218 ? sParentAndContentProcessThreadInfo.mMainThreadInfo 1219 : static_cast<ThreadLocalInfo*>(PR_GetThreadPrivate( 1220 sParentAndContentProcessThreadInfo.mThreadLocalIndex)); 1221 1222 if (!threadLocalInfo) { 1223 return nullptr; 1224 } 1225 1226 if (!threadLocalInfo->mConsumerThreadLocal) { 1227 threadLocalInfo->mConsumerThreadLocal = 1228 MakeUnique<BackgroundChildImpl::ThreadLocal>(); 1229 } 1230 1231 return threadLocalInfo->mConsumerThreadLocal.get(); 1232 } 1233 1234 // static 1235 void ChildImpl::InitContentStarter(mozilla::dom::ContentChild* aContent) { 1236 sParentAndContentProcessThreadInfo.InitStarter(aContent); 1237 } 1238 1239 // static 1240 void ChildImpl::ThreadLocalDestructor(void* aThreadLocal) { 1241 auto threadLocalInfo = static_cast<ThreadLocalInfo*>(aThreadLocal); 1242 1243 if (threadLocalInfo) { 1244 MOZ_ASSERT(threadLocalInfo->mClosed); 1245 1246 if (threadLocalInfo->mActor) { 1247 threadLocalInfo->mActor->Close(); 1248 threadLocalInfo->mActor->AssertActorDestroyed(); 1249 } 1250 1251 delete threadLocalInfo; 1252 } 1253 } 1254 1255 void ChildImpl::ActorDestroy(ActorDestroyReason aWhy) { 1256 AssertIsOnOwningThread(); 1257 1258 #ifdef DEBUG 1259 MOZ_ASSERT(!mActorDestroyed); 1260 mActorDestroyed = true; 1261 #endif 1262 1263 BackgroundChildImpl::ActorDestroy(aWhy); 1264 } 1265 1266 NS_IMPL_ISUPPORTS(ChildImpl::ShutdownObserver, nsIObserver) 1267 1268 NS_IMETHODIMP 1269 ChildImpl::ShutdownObserver::Observe(nsISupports* aSubject, const char* aTopic, 1270 const char16_t* aData) { 1271 AssertIsOnMainThread(); 1272 MOZ_ASSERT(!strcmp(aTopic, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID)); 1273 1274 ChildImpl::Shutdown(); 1275 1276 return NS_OK; 1277 }