/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ /* * Test various cases of behavior when a synchronous method call times out. */ #include "gtest/gtest.h" #include "mozilla/_ipdltest/IPDLUnitTest.h" #include "mozilla/_ipdltest/PTestHangsChild.h" #include "mozilla/_ipdltest/PTestHangsParent.h" #include "mozilla/ipc/CrossProcessSemaphore.h" using namespace mozilla::ipc; namespace mozilla::_ipdltest { enum class HangMode : uint32_t { /// No hang should occur. None, /// The synchronous call should time out. Hang, /// The synchronous call should time out but the response should still be /// received /// (racing with the reply timeout logic). HangButReceive, /// The synchronous call should time out but the response should still be /// received because the child indicates that processing should continue after /// timeout. HangPermitted }; class TestHangsChild : public PTestHangsChild { NS_INLINE_DECL_REFCOUNTING(TestHangsChild, override) TestHangsChild() : timeout(CrossProcessSemaphore::Create("timeout", 0)) {} private: IPCResult RecvStart(const uint32_t& hangMode, StartResolver&& resolve) final override { // Minimum possible, 2 ms. We want to detect a hang to race with the reply // coming in, as reliably as possible. After 1ms the wait for a response // will be retried. SetReplyTimeoutMs(2); this->hangMode = (HangMode)hangMode; auto result = SendHang(hangMode, timeout->CloneHandle()); if (this->hangMode == HangMode::Hang) { // Only the `Hang` mode should actually fail. EXPECT_FALSE(result); } else { EXPECT_TRUE(result); } resolve(detectedHang); return IPC_OK(); } bool ShouldContinueFromReplyTimeout() final override { timeout->Signal(); detectedHang = true; if (hangMode == HangMode::HangButReceive) { // Wait until the transaction is complete so that we can still receive the // result after returning. while (!GetIPCChannel()->TestOnlyIsTransactionComplete()) { PR_Sleep(ticksPerSecond / 1000); } } // Return true if `HangPermitted` mode (allowing the receive loop to // continue). return hangMode == HangMode::HangPermitted; } ~TestHangsChild() = default; HangMode hangMode = HangMode::None; uint32_t ticksPerSecond = PR_TicksPerSecond(); UniquePtr timeout; public: bool detectedHang = false; }; class TestHangsParent : public PTestHangsParent { NS_INLINE_DECL_REFCOUNTING(TestHangsParent, override) private: IPCResult RecvHang( const uint32_t& hangMode, CrossProcessSemaphoreHandle&& timeout_handle) final override { UniquePtr timeout( CrossProcessSemaphore::Create(std::move(timeout_handle))); if (hangMode != (uint32_t)HangMode::None) { // Wait to ensure the child process has called // ShouldContinueFromReplyTimeout(). timeout->Wait(); } return IPC_OK(); } ~TestHangsParent() = default; }; // We can verify that the Start message callbacks are run with the `Close()` // calls; without a `Close()`, the test will hang. #define TEST_HANGS(mode) \ IPDL_TEST(TestHangs, mode) { \ mActor->SendStart( \ (uint32_t)HangMode::mode, \ [=](bool detectedHang) { \ EXPECT_EQ(detectedHang, HangMode::mode != HangMode::None); \ mActor->Close(); \ }, \ [](auto&& reason) { FAIL() << "failed to send start"; }); \ } TEST_HANGS(None) TEST_HANGS(Hang) TEST_HANGS(HangButReceive) TEST_HANGS(HangPermitted) #undef TEST_HANGS } // namespace mozilla::_ipdltest