在驅動程式庫編寫作業中使用的非同步類型,大多不安全,因此會檢查這些類型是否一律從相關聯的同步調度器使用,以確保記憶體安全,例如:
{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
方法完成。與這些背景驅動程式庫調度器相關聯的執行緒不安全物件,不應直接從主執行緒存取。
使用單一執行緒的非同步物件時,其所包含的 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);
}
在這個範例中,系統會在對應的 async::Loop
執行緒上存取 MyRegister
物件。這會反過來釋放主執行緒,以便進行阻斷式呼叫。當主執行緒想要與 MyRegister
互動時,就需要間接使用 SyncCall
進行互動。
在測試中另一個常見的模式,是使用 TestDispatcherBound
在個別執行緒上設定 FIDL 伺服器,並在主要測試執行緒上使用測試固定裝置類別。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([®]() {
return reg.SyncCall(&FakeRegister::GetValue);
}), 123);
}
如果驅動程式庫物件的調度器由主執行緒支援,就不需要經過 TestDispatcherBound
。我們可以安全地使用 BlockingIO
驅動程式庫物件,包括從主執行緒發出 SetValueInABlockingWay
呼叫。
當 FakeRegister
假物件位於 register_dispatcher
時,我們需要使用 TestDispatcherBound
,才能安全地從主執行緒與其互動。
請注意,我們已將 SyncCall
包裝在 driver_runtime.PerformBlockingWork
中。這會在主執行緒上執行前景驅動程式庫調度器,同時在背景中的新暫時執行緒上執行 SyncCall
。如果在調度器繫結物件 (在本例中為 GetValue
) 上執行的方法涉及與前景調度器相關聯的物件 (在本例中為 BlockingIO
),就必須執行前景調度器。
如果您確定呼叫的方法不需要在前景調度器執行時才能傳回,那麼可以使用直接 SyncCall
。
DispatcherBound 物件的細緻程度
以下指南適用於 TestDispatcherBound
和其實際執行環境對應項目 DispatcherBound
。
當您要透過特定同步調度器序列化物件的存取權時,請務必考量從該調度器使用哪些其他物件。在同一個 DispatcherBound
中結合兩個物件可能會更有效率。
舉例來說,如果您使用 component::OutgoingDirectory
,並以同步方式呼叫 FIDL 伺服器實作項目 (例如將繫結項目新增至 fidl::ServerBindingGroup
),則必須確保兩個物件位於相同的調度器上。
如果您只將 OutgoingDirectory
放入 [Test]DispatcherBound
中,以便處理其同步檢查器,但將 ServerBindingGroup
留在其他位置 (例如主執行緒),當 OutgoingDirectory
物件從調度器執行緒呼叫 ServerBindingGroup
時,就會發生當機,並觸發 ServerBindingGroup
中的檢查器。
如要解決這個問題,您可以將 OutgoingDirectory
和其參照的物件 (例如 ServerBindingGroup
或任何伺服器狀態) 放入較大的物件中,然後將該物件放入 DispatcherBound
。這樣一來,OutgoingDirectory
和 ServerBindingGroup
都會從同一個同步調度器使用,您就不會遇到任何當機情形。您可以查看使用這項技巧的測試範例。
一般來說,按照並行作業邊界劃分類別會很有幫助。這樣一來,您就能確保所有需要在同一個調度器上使用的物件都會同步處理,避免發生可能的當機或資料競爭情形。
另請參閱
- Isolated state async C++ RFC,說明同步檢查背後的理論架構。
- 執行緒安全的非同步程式碼,提供一般正式版程式碼的指導方針,而非僅限於測試。