測試中的執行緒提示

編寫驅動程式庫時,有幾個非同步類型屬於不安全的執行緒, 使用者會檢查這些裝置,一律從相關聯的同步紀錄中使用 調度工具,確保記憶體安全,例如:

  • {fdf,component}::OutgoingDirectory
  • {fdf,fidl}::Client{fdf,fidl}::WireClient
  • {fdf,fidl}::ServerBinding{fdf,fidl}::ServerBindingGroup

如果您要測試含有這些類型的驅動程式庫非同步物件,請使用 錯誤的執行環境會導致當機,導致堆疊追蹤 「synchronization_checker」。這項安全功能可防止無訊息 損毀。以下是避免當機的幾個提示。

編寫單一執行緒測試

最直接的方法就是執行測試斷言,並使用 來自同一執行緒的物件,通常是主執行緒:

涉及 async::Loop 的示例:

TEST(MyRegister, Read) {
  async::Loop loop{&kAsyncLoopConfigAttachToCurrentThread};
  // For illustration purposes, this is the thread-unsafe type being tested.
  MyRegister reg{loop.dispatcher()};
  // Use the object on the current thread.
  reg.SetValue(123);
  // Run any async work scheduled by the object, also on the current thread.
  ASSERT_OK(loop.RunUntilIdle());
  // Read from the object on the current thread.
  EXPECT_EQ(obj.GetValue(), 123);
}

涉及 fdf_testing::DriverRuntime 前景調度工具的範例:

TEST(MyRegister, Read) {
  // Creates a foreground driver dispatcher.
  fdf_testing::DriverRuntime driver_runtime;
  // For illustration purposes, this is the thread-unsafe type being tested.
  MyRegister reg{dispatcher.dispatcher()};
  // Use the object on the current thread.
  reg.SetValue(123);
  // Run any async work scheduled by the object, also on the current thread.
  driver_runtime.RunUntilIdle();
  // Read from the object on the current thread.
  EXPECT_EQ(obj.GetValue(), 123);
  ASSERT_OK(dispatcher.Stop());
}

請注意,fdf_testing::DriverRuntime 也可以建立背景驅動程式庫 由驅動程式庫執行階段代管執行緒集區驅動的調度器。 這是透過 StartBackgroundDispatcher 方法完成。 與這些背景驅動程式庫調度器相關聯的 Thread 不安全物件 不得直接透過主執行緒存取。

使用單一執行緒中的非同步物件時,這些項目包含 async::synchronization_checker 不會驚慌。

呼叫封鎖函式

如果您需要呼叫阻斷函式,單一執行緒會中斷 調度員處理部分訊息時會封鎖這些訊息。如果是第一次呼叫 封鎖函式,然後執行迴圈,這個迴圈會因為 才會執行到封鎖函式傳回,而封鎖函式才執行 除非執行迴圈,否則不會傳回。

如要呼叫阻斷函式,您必須設法執行該函式並執行迴圈 在不同執行緒上進行學習此外,封鎖函式 存取與調度工具相關聯的物件,而不進行同步處理, 因為這樣可能會與迴圈執行緒競爭

若要解決這兩個問題,您可以將執行緒不安全的非同步物件納入 async_patterns::TestDispatcherBound,可確保 所有存取包裝物件的行為,都會在其關聯的調度器上進行。

涉及 async::Loop 的範例,重複使用前述的 MyRegister 類型:

// Let's say this function blocks and then returns some value we need.
int GetValueInABlockingWay();

TEST(MyRegister, Read) {
  // Configure the loop to register itself as the dispatcher for the
  // loop thread, such that the |MyRegister| constructor may use
  // `async_get_default_dispatcher()` to obtain the loop dispatcher.
  async::Loop loop{&kAsyncLoopConfigNoAttachToCurrentThread};
  loop.StartThread();

  // Construct the |MyRegister| on the loop thread.
  async_patterns::TestDispatcherBound<MyRegister> reg{
      loop.dispatcher(), std::in_place};

  // Schedule a |SetValue| call on the loop thread and wait for it.
  reg.SyncCall(&MyRegister::SetValue, 123);

  // Call the blocking function on the main thread.
  // This will not deadlock, because we have started a loop thread
  // earlier to process messages for the |MyRegister| object.
  int value = GetValueInABlockingWay();
  EXPECT_EQ(value, 123);

  // |GetValue| returns a value; |SyncCall| will proxy that back.
  EXPECT_EQ(reg.SyncCall(&MyRegister::GetValue), 123);
}

在此範例中,MyRegister 物件的存取權會在其對應的 async::Loop 執行緒。這樣就能釋放主執行緒進行阻斷通話。 當主執行緒想要與 MyRegister 互動時,就必須進行這項操作 間接使用 SyncCall

測試的另一個常見模式是在個別執行緒上設定 FIDL 伺服器 與 TestDispatcherBound 搭配使用,並將測試固件類別用於主要元件 測試執行緒。TestDispatcherBound 物件將成為測試的成員 固件類別

涉及 fdf_testing::DriverRuntime 的示例:

在驅動程式中,阻斷工作通常是在驅動程式庫中發生。例如: 造成阻斷工作可能涉及驅動程式庫進行的同步 FIDL 呼叫 在測試期間偽造的 FIDL 通訊協定。在以下範例中, BlockingIO 類別代表驅動程式庫,FakeRegister 類別 代表 BlockingIO 使用的某些 FIDL 通訊協定的虛假實作。

// Here is the bare skeleton of a driver object that makes a synchronous call.
class BlockingIO {
 public:
  BlockingIO(): dispatcher_(fdf_dispatcher_get_current_dispatcher()) {}

  // Let's say this function blocks to update the value stored in a
  // |FakeRegister|.
  void SetValueInABlockingWay(int value);

  /* Other details omitted */
};

TEST(BlockingIO, Read) {
  // Creates a foreground driver dispatcher.
  fdf_testing::DriverRuntime driver_runtime;

  // Create a background dispatcher for the |FakeRegister|.
  // This way it is safe to call into it synchronously from the |BlockingIO|.
  fdf::UnownedSynchronizedDispatcher register_dispatcher =
      driver_runtime.StartBackgroundDispatcher();

  // Construct the |FakeRegister| on the background dispatcher.
  // The |FakeRegister| constructor may use
  // `fdf_dispatcher_get_current_dispatcher()` to obtain the dispatcher.
  async_patterns::TestDispatcherBound<FakeRegister> reg{
      register_dispatcher.async_dispatcher(), std::in_place};

  // Construct the |BlockingIO| on the foreground driver dispatcher.
  BlockingIO io;

  // Call the blocking function. The |register_dispatcher| will respond to it in the
  // background.
  io.SetValueInABlockingWay(123);

  // Check the value from the fake.
  // |GetValue| returns an |int|; |SyncCall| will proxy that back.
  // |PerformBlockingWork| will ensure the foreground dispatcher is running while
  // the blocking work runs in a new temporary background thread.
  EXPECT_EQ(driver_runtime.PerformBlockingWork([&reg]() {
    return reg.SyncCall(&FakeRegister::GetValue);
  }), 123);
}

當主執行緒支援驅動程式庫物件的調度器時, 無需透過 TestDispatcherBound我們可以放心使用 BlockingIO 驅動程式庫物件,包括發出 SetValueInABlockingWay 呼叫 透過主執行緒擷取。

FakeRegister 假物件在 register_dispatcher 上時,我們需要 以便使用 TestDispatcherBound,從主執行緒安全地與其互動。

請注意,我們已使用 driver_runtime.PerformBlockingWork 包裝 SyncCall。 這麼做是在主執行緒上執行前景驅動程式庫調度器 透過在背景的新臨時執行緒執行 SyncCall 時。 如果該方法在 調度工具繫結物件 (在本例中為 GetValue) 涉及與物件通訊 與前景調度工具相關聯,也就是 BlockingIO

是否確定呼叫的方法不需要前景調度工具 才能傳回內容,則可以使用直接 SyncCall

DispatcherBound 物件的精細程度

下列指南適用於 TestDispatcherBound 及其正式版 (DispatcherBound)。

對物件進行序列化存取權處理的特定同步物件時 調度器時必須考量哪些 同一個調度工具搭配組合內部的兩個物件會更有效率 相同的 DispatcherBound

例如,如果您使用 component::OutgoingDirectory, 同步呼叫到 FIDL 伺服器實作方式,例如將繫結新增至 fidl::ServerBindingGroup,必須確保兩個物件都相同 調度器。

如果只將 OutgoingDirectory 置於 [Test]DispatcherBound 中即可 但請將 ServerBindingGroup 留在其他位置 否則,例如透過主執行緒進行當機時,若 OutgoingDirectory 物件呼叫從調度工具執行緒到 ServerBindingGroup,導致三倍 ServerBindingGroup 中的檢查工具。

如要解決這個問題,請將 OutgoingDirectory 和物件放在一起 參照,例如 ServerBindingGroup 或任何伺服器狀態 ,然後將該物件放入 DispatcherBound 中。這麼做 OutgoingDirectoryServerBindingGroup 將使用相同的 否則不會發生任何當機情形。您會看到 使用這項技巧的範例測試

一般來說,將類別沿著並行界線劃分,會很有幫助。變更者: 來確保需要使用的所有物件 調度工具會進行同步,以防止潛在的當機或資料競爭。

另請參閱