tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

TestHangs.cpp (4569B)


      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 /*
      8 * Test various cases of behavior when a synchronous method call times out.
      9 */
     10 
     11 #include "gtest/gtest.h"
     12 
     13 #include "mozilla/_ipdltest/IPDLUnitTest.h"
     14 #include "mozilla/_ipdltest/PTestHangsChild.h"
     15 #include "mozilla/_ipdltest/PTestHangsParent.h"
     16 #include "mozilla/ipc/CrossProcessSemaphore.h"
     17 
     18 using namespace mozilla::ipc;
     19 
     20 namespace mozilla::_ipdltest {
     21 
     22 enum class HangMode : uint32_t {
     23  /// No hang should occur.
     24  None,
     25  /// The synchronous call should time out.
     26  Hang,
     27  /// The synchronous call should time out but the response should still be
     28  /// received (racing with the reply timeout logic).
     29  HangButReceive,
     30  /// The synchronous call should time out but the response should still be
     31  /// received because the child indicates that processing should continue after
     32  /// timeout.
     33  HangPermitted
     34 };
     35 
     36 class TestHangsChild : public PTestHangsChild {
     37  NS_INLINE_DECL_REFCOUNTING(TestHangsChild, override)
     38 
     39  TestHangsChild() : timeout(CrossProcessSemaphore::Create("timeout", 0)) {}
     40 
     41 private:
     42  IPCResult RecvStart(const uint32_t& hangMode,
     43                      StartResolver&& resolve) final override {
     44    // Minimum possible, 2 ms. We want to detect a hang to race with the reply
     45    // coming in, as reliably as possible. After 1ms the wait for a response
     46    // will be retried.
     47    SetReplyTimeoutMs(2);
     48 
     49    this->hangMode = (HangMode)hangMode;
     50 
     51    auto result = SendHang(hangMode, timeout->CloneHandle());
     52    // Only the `Hang` mode should actually fail.
     53    if (this->hangMode == HangMode::Hang) {
     54      // See description in parent.
     55      timeout->Signal();
     56      EXPECT_FALSE(result);
     57    } else {
     58      EXPECT_TRUE(result);
     59    }
     60 
     61    resolve(detectedHang);
     62 
     63    return IPC_OK();
     64  }
     65 
     66  bool ShouldContinueFromReplyTimeout() final override {
     67    timeout->Signal();
     68    detectedHang = true;
     69 
     70    if (hangMode == HangMode::HangButReceive) {
     71      // Wait until the transaction is complete so that we can still receive the
     72      // result after returning.
     73      while (!GetIPCChannel()->TestOnlyIsTransactionComplete()) {
     74        PR_Sleep(ticksPerSecond / 1000);
     75      }
     76    }
     77 
     78    // Return true if `HangPermitted` mode (allowing the receive loop to
     79    // continue).
     80    return hangMode == HangMode::HangPermitted;
     81  }
     82 
     83  ~TestHangsChild() = default;
     84 
     85  HangMode hangMode = HangMode::None;
     86  uint32_t ticksPerSecond = PR_TicksPerSecond();
     87  UniquePtr<CrossProcessSemaphore> timeout;
     88 
     89 public:
     90  bool detectedHang = false;
     91 };
     92 
     93 class TestHangsParent : public PTestHangsParent {
     94  NS_INLINE_DECL_REFCOUNTING(TestHangsParent, override)
     95 
     96 private:
     97  IPCResult RecvHang(
     98      const uint32_t& hangMode,
     99      CrossProcessSemaphoreHandle&& timeout_handle) final override {
    100    UniquePtr<CrossProcessSemaphore> timeout(
    101        CrossProcessSemaphore::Create(std::move(timeout_handle)));
    102    if (hangMode != (uint32_t)HangMode::None) {
    103      // Wait to ensure the child process has called
    104      // ShouldContinueFromReplyTimeout().
    105      timeout->Wait();
    106 
    107      if (hangMode == (uint32_t)HangMode::Hang) {
    108        // Wait to ensure the child process has returned from `SendHang()`,
    109        // otherwise the reply message can race with the processing after
    110        // ShouldContinueFromReplyTimeout().
    111        timeout->Wait();
    112      }
    113    }
    114    return IPC_OK();
    115  }
    116 
    117  ~TestHangsParent() = default;
    118 };
    119 
    120 // We can verify that the Start message callbacks are run with the `Close()`
    121 // calls; without a `Close()`, the test will hang.
    122 
    123 #define TEST_HANGS(mode)                                             \
    124  IPDL_TEST(TestHangs, mode) {                                       \
    125    mActor->SendStart(                                               \
    126        (uint32_t)HangMode::mode,                                    \
    127        [=](bool detectedHang) {                                     \
    128          EXPECT_EQ(detectedHang, HangMode::mode != HangMode::None); \
    129          mActor->Close();                                           \
    130        },                                                           \
    131        [](auto&& reason) { FAIL() << "failed to send start"; });    \
    132  }
    133 
    134 TEST_HANGS(None)
    135 TEST_HANGS(Hang)
    136 TEST_HANGS(HangButReceive)
    137 TEST_HANGS(HangPermitted)
    138 
    139 #undef TEST_HANGS
    140 
    141 }  // namespace mozilla::_ipdltest