編寫驅動程式庫時,有幾個非同步類型屬於不安全的執行緒, 使用者會檢查這些裝置,一律從相關聯的同步紀錄中使用 調度工具,確保記憶體安全,例如:
{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([®]() {
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
中。這麼做
OutgoingDirectory
和 ServerBindingGroup
將使用相同的
否則不會發生任何當機情形。您會看到
使用這項技巧的範例測試。
一般來說,將類別沿著並行界線劃分,會很有幫助。變更者: 來確保需要使用的所有物件 調度工具會進行同步,以防止潛在的當機或資料競爭。
另請參閱
- 獨立狀態非同步 C++ RFC:解釋理論架構 系統會執行同步處理作業
- 執行緒安全的非同步程式碼: 不只能測試一般的正式環境程式碼